Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
*_log.xml
*,cover
sponge_log.xml

Expand Down
2 changes: 2 additions & 0 deletions bookshelf/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
runtime: python37

69 changes: 69 additions & 0 deletions bookshelf/firestore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# 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.

# [START bookshelf_firestore_client_import]
from google.cloud import firestore
# [END bookshelf_firestore_client_import]


def document_to_dict(doc):
if not doc.exists:
return None
doc_dict = doc.to_dict()
doc_dict['id'] = doc.id
return doc_dict


def next_page(limit=10, start_after=None):
db = firestore.Client()

query = db.collection(u'Book').limit(limit).order_by(u'title')

if start_after:
# Construct a new query starting at this document.
query = query.start_after({u'title': start_after})

docs = query.stream()
docs = list(map(document_to_dict, docs))

last_title = None
if limit == len(docs):
# Get the last document from the results and set as the last title.
last_title = docs[-1][u'title']
return docs, last_title


def read(book_id):
# [START bookshelf_firestore_client]
db = firestore.Client()
book_ref = db.collection(u'Book').document(book_id)
snapshot = book_ref.get()
# [END bookshelf_firestore_client]
return document_to_dict(snapshot)


def update(data, book_id=None):
db = firestore.Client()
book_ref = db.collection(u'Book').document(book_id)
book_ref.set(data)
return document_to_dict(book_ref.get())


create = update


def delete(id):
db = firestore.Client()
book_ref = db.collection(u'Book').document(id)
book_ref.delete()
Binary file added bookshelf/images/moby-dick.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
153 changes: 153 additions & 0 deletions bookshelf/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# 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(logging.INFO)


@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={})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should this be at the top, like so?

if request.method != 'POST':
  return render_template(...)

...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more idiomatic for python as is, mostly because this is how the previous bookshelf was set up. So I'd prefer to leave it as-is



@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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above - should this be at the top?



@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)
Loading