bookshelf/main.py (82 lines of code) (raw):
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import firestore
from flask import current_app, flash, Flask, Markup, redirect, render_template
from flask import request, url_for
from google.cloud import error_reporting
import google.cloud.logging
import storage
# [START upload_image_file]
def upload_image_file(img):
"""
Upload the user-uploaded file to Google Cloud Storage and retrieve its
publicly-accessible URL.
"""
if not img:
return None
public_url = storage.upload_file(
img.read(),
img.filename,
img.content_type
)
current_app.logger.info(
'Uploaded file %s as %s.', img.filename, public_url)
return public_url
# [END upload_image_file]
app = Flask(__name__)
app.config.update(
SECRET_KEY='secret',
MAX_CONTENT_LENGTH=8 * 1024 * 1024,
ALLOWED_EXTENSIONS=set(['png', 'jpg', 'jpeg', 'gif'])
)
app.debug = False
app.testing = False
# Configure logging
if not app.testing:
logging.basicConfig(level=logging.INFO)
client = google.cloud.logging.Client()
# Attaches a Google Stackdriver logging handler to the root logger
client.setup_logging()
@app.route('/')
def list():
start_after = request.args.get('start_after', None)
books, last_title = firestore.next_page(start_after=start_after)
return render_template('list.html', books=books, last_title=last_title)
@app.route('/books/<book_id>')
def view(book_id):
book = firestore.read(book_id)
return render_template('view.html', book=book)
@app.route('/books/add', methods=['GET', 'POST'])
def add():
if request.method == 'POST':
data = request.form.to_dict(flat=True)
# If an image was uploaded, update the data to point to the new image.
image_url = upload_image_file(request.files.get('image'))
if image_url:
data['imageUrl'] = image_url
book = firestore.create(data)
return redirect(url_for('.view', book_id=book['id']))
return render_template('form.html', action='Add', book={})
@app.route('/books/<book_id>/edit', methods=['GET', 'POST'])
def edit(book_id):
book = firestore.read(book_id)
if request.method == 'POST':
data = request.form.to_dict(flat=True)
# If an image was uploaded, update the data to point to the new image.
image_url = upload_image_file(request.files.get('image'))
if image_url:
data['imageUrl'] = image_url
book = firestore.update(data, book_id)
return redirect(url_for('.view', book_id=book['id']))
return render_template('form.html', action='Edit', book=book)
@app.route('/books/<book_id>/delete')
def delete(book_id):
firestore.delete(book_id)
return redirect(url_for('.list'))
@app.route('/logs')
def logs():
logging.info('Hey, you triggered a custom log entry. Good job!')
flash(Markup('''You triggered a custom log entry. You can view it in the
<a href="https://console.cloud.google.com/logs">Cloud Console</a>'''))
return redirect(url_for('.list'))
@app.route('/errors')
def errors():
raise Exception('This is an intentional exception.')
# Add an error handler that reports exceptions to Stackdriver Error
# Reporting. Note that this error handler is only used when debug
# is False
@app.errorhandler(500)
def server_error(e):
client = error_reporting.Client()
client.report_exception(
http_context=error_reporting.build_flask_context(request))
return """
An internal error occurred: <pre>{}</pre>
See logs for full stacktrace.
""".format(e), 500
# This is only used when running locally. When running live, gunicorn runs
# the application.
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080, debug=True)