I'm following the Google App Engine tutorial (here's their complete demo code on GitHub) and would like to:
Allow a guestbook user to upload an extra image in addition to 'avatar' when posting a greeting. This could be called 'other'
After posting a greeting, redirect the user to a greeting detail page with a URL like /greeting/numeric-id instead of the main page listing all greetings.
Display the detail page with images using a Jinja2 template called detail.html
I'm having trouble understanding:
A) How to write the redirect code that is called once a greeting is published so it redirects to a URL like /greeting/numeric-id.
B) How to write the Detail view and template page that the user is redirected to so the greeting id and images are displayed.
Here's a diagram showing what I want to do:
Here's guestbook.py:
import os
import urllib
from google.appengine.api import images
from google.appengine.api import users
from google.appengine.ext import ndb
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.ext import blobstore
import jinja2
import webapp2
JINJA_ENVIRONMENT = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
extensions=['jinja2.ext.autoescape'],
autoescape=True)
DEFAULT_GUESTBOOK_NAME = 'default_guestbook'
def guestbook_key(guestbook_name=None):
"""Constructs a Datastore key for a Guestbook entity with name."""
return ndb.Key('Guestbook', guestbook_name or 'default_guestbook')
class Author(ndb.Model):
"""Sub model for representing an author."""
identity = ndb.StringProperty(indexed=False)
email = ndb.StringProperty(indexed=False)
class Greeting(ndb.Model):
"""A model for representing an individual Greeting entry."""
author = ndb.StructuredProperty(Author)
date = ndb.DateTimeProperty(auto_now_add=True)
avatar = ndb.BlobProperty(indexed=False, required=True)
other = ndb.BlobProperty(indexed=False, required=True)
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
guestbook_name = self.request.get('guestbook_name')
greetings = Greeting.query(
ancestor=guestbook_key(guestbook_name)) \
.order(-Greeting.date) \
.fetch(10)
self.response.out.write("""
<form action="/sign?%s"
enctype="multipart/form-data"
method="post">
<label>Avatar:</label>
<input type="file" name="avatar"/><br>
<label>Other Image:</label>
<input type="file" name="other"/><br>
<input type="submit" value="Submit">
</form>
</body>
</html>""" % (urllib.urlencode({'guestbook_name': guestbook_name})))
class Image(webapp2.RequestHandler):
""" Handle image stored as blobs of bytes.
No idea how the template knows to select a particular one. """
def get(self):
avatar_greeting_key = ndb.Key(urlsafe=self.request.get('avatar_id'))
other_greeting_key = ndb.Key(urlsafe=self.request.get('other_id'))
avatar_greeting = avatar_greeting_key.get()
other_greeting = other_greeting_key.get()
if avatar_greeting.avatar:
self.response.headers['Content-Type'] = 'image/png'
self.response.out.write(avatar_greeting.avatar)
elif other_greeting.other:
self.response.headers['Content-Type'] = 'image/png'
self.response.out.write(other_greeting.other)
else:
self.response.out.write('No image')
class Guestbook(webapp2.RequestHandler):
def post(self):
guestbook_name = self.request.get('guestbook_name',
DEFAULT_GUESTBOOK_NAME)
greeting = Greeting(parent=guestbook_key(guestbook_name))
if users.get_current_user():
greeting.author = Author(
identity=users.get_current_user().user_id(),
email=users.get_current_user().email())
avatar = self.request.get('avatar')
avatar = images.resize(avatar, 100, 100)
other = self.request.get('other')
other = images.resize(other, 400, 300)
greeting.avatar = avatar
greeting.other = other
greeting.put()
query_params = {'guestbook_name': guestbook_name}
self.redirect('/greeting/%d' % greeting.key.id())
class Detail(webapp2.RequestHandler):
""" Individual greeting. """
def get(self, *args, **kwargs):
guestbook_name = self.request.get('guestbook_name', DEFAULT_GUESTBOOK_NAME)
greeting = Greeting.get_by_id(args[0],
parent=guestbook_key(guestbook_name))
template_values = {
'greeting': greeting,
}
template = JINJA_ENVIRONMENT.get_template('detail.html')
self.response.write(template.render(template_values))
app = webapp2.WSGIApplication([
('/', MainPage),
('/img', Image),
('/sign', Guestbook),
('/greeting/(\d+)', Detail),
], debug=True)
My detail.html template:
<!DOCTYPE html>
{% autoescape true %}
<html>
<head>
<title>Greeting {{ greeting.id }}</title>
</head>
<body>
<h2>Greeting {{ greeting.id }}</h2>
Avatar: <img src="/img?avatar_id={{ greeting.key.urlsafe() }}">
<br>
Other: <img src="/img?other_id={{ greeting.key.urlsafe() }}">
</body>
</html>
{% endautoescape %}
My app.yaml in case it is useful:
runtime: python27
api_version: 1
threadsafe: true
# Handlers match in order, put above the default handler.
handlers:
- url: /stylesheets
static_dir: stylesheets
- url: /.*
script: guestbook.app
libraries:
- name: webapp2
version: latest
- name: jinja2
version: latest
The Error:
Traceback (most recent call last):
File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1535, in __call__
rv = self.handle_exception(request, response, e)
File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1529, in __call__
rv = self.router.dispatch(request, response)
File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1278, in default_dispatcher
return route.handler_adapter(request, response)
File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1102, in __call__
return handler.dispatch()
File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 572, in dispatch
return self.handle_exception(e, self.app.debug)
File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 570, in dispatch
return method(*args, **kwargs)
File "/Users/simon/Projects/guestbook/guestbook.py", line 111, in get
self.response.write(template.render(template_values))
File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/jinja2-2.6/jinja2/environment.py", line 894, in render
return self.environment.handle_exception(exc_info, True)
File "/Users/simon/Projects/guestbook/detail.html", line 9, in top-level template code
Avatar: <img src="/img?avatar_id={{ greeting.key.urlsafe() }}">
UndefinedError: 'None' has no attribute 'key'
Any help, or even better, example code, would be much appreciated.
A GAE/webapp2 blog tutorial with example code of detail and list views and templates would be great, but perhaps the data structure of the GAE BlobStore is not ideal for a blog?
Update:
If I add the python check code contributed in Dan's answer I get a 500 error instead of the stack trace, and if I try the template checks I get a blank greeting page. I've updated the question with my full code and a diagram explaining what I am trying to do.
I'll start with B:
The error indicates that the greeting value is None, leading to an exception when jinja2 attempts to expand greeting.key in {{ greeting.key.urlsafe() }} during template rendering.
One option is to re-arrange the handler code to prevent rendering that template if the necessary conditions are not met, maybe something along these lines:
...
greeting = Greeting.get_by_id(args[0])
if not greeting or not isinstance(greeting.key, ndb.Key):
# can't render that template, invalid greeting.key.urlsafe()
webapp2.abort(500)
return
template_values = {
'greeting': greeting,
}
template = JINJA_ENVIRONMENT.get_template('detail.html')
self.response.write(template.render(template_values))
...
Alternatively you can wrap the template areas referencing the variables with appropriate checks (IMHO uglier, harder and more fragile, tho - python is way better for this kind of logic than jinja2), something along these lines:
{% if greeting and greeting.key %}<img src="/img?avatar_img_id={{ greeting.key.urlsafe() }}">{% endif %}
Now to A.
In short - not a great idea, primarily because the numeric ID you're trying to use in the URL is not unique except for greetings under the same parent entity!. Which in a way explains why greeting is invalid leading to the error from the B answer.
The greeting = Greeting.get_by_id(args[0]) will return None unless you also created a greeting entity with the ID you're passing in args[0] and no parent!
In order to obtain by ID the greeting you created with:
greeting = Greeting(parent=guestbook_key(guestbook_name))
you'd need to call:
greeting = Greeting.get_by_id(args[0],
parent=guestbook_key(guestbook_name))
You could, if you want to continue in the same direction, also encode the guestbook_name in the greeting URL, which would allow you to also obtain the needed parent key, maybe something along these lines:
/guestbook/<guestbook_name>/greeting/<numeric-id>.
You also need to take a closer look at the image handling. You have architectural inconsistencies: in the diagram and the model you have both the avatar and the other image attached to a single greeting entity, but in the Image handler each of them is attached to a separate greeting. The handler also doesn't map at all to the images' URLs (which also needs additional encodings for the data you need to locate the appropriate image, depending on the architectural decision).
I'm afraid you still have a lot of work to do until you get the entire thing to work, much more than properly fit for a single SO question. Step back, re-think your architecture, split it into smaller pieces, focus on one piece at a time and get it going. After you become familiar with the technology for the various pieces you'll feel better tackling the whole problem at once.
I just follow GAE's document (https://developers.google.com/appengine/docs/python/blobstore/#Python_Uploading_a_blob) to write upload handler to upload blobstore, when I select one file on computer and click Submit button on HTML page, it will show 'The url "/upload" does not match any handlers.'
Any comments is appreciated.
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
upload_url = blobstore.create_upload_url('/upload')
logging.info(upload_url)
self.response.out.write('<html><body>')
self.response.out.write('<form action="%s" method="POST" enctype="multipart/form-data">' % upload_url)
self.response.out.write("""Upload File: <input type="file" name="file"><br> <input type="submit"
name="submit" value="Submit"> </form></body></html>""")
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
logging.info('Upload handler')
upload_files = self.get_uploads('file') # 'file' is file upload field in the form
blob_info = upload_files[0]
logging.info(upload_files)
self.redirect('/serve/%s' % blob_info.key())
class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, resource):
resource = str(urllib.unquote(resource))
logging.info(resource)
blob_info = blobstore.BlobInfo.get(resource)
self.send_blob(blob_info)
application = webapp2.WSGIApplication([
('/', MainPage),
('/upload', UploadHandler),
('/serve/([^/]+)?', ServeHandler),
], debug=True)
[Update1]
After I click submit button, I check dev-server Blobstore Viewer, I found the file has been uploaded there, however, my chrome browser still show 'The url "/upload" does not match any handlers.'. This is why?
I want to ask my question by self that maybe someone encounter similar issue as me.
After I change from
- url: /
script: AppWS.application
to
- url: (/.*)*
script: AppWS.application
everything is OK.
I want the user to be able to upload images to Google App Engine. I have the following (Python):
class ImageData(ndb.Model):
name = ndb.StringProperty(indexed=False)
image = ndb.BlobProperty()
Information is submitted by the user using a form (HTML):
<form name = "input" action = "/register" method = "post">
name: <input type = "text" name = "name">
image: <input type = "file" name = "image">
</form>
Which is then processed by:
class AddProduct(webapp2.RequestHandler):
def post(self):
imagedata = ImageData(parent=image_key(image_name))
imagedata.name = self.request.get('name')
imagedata.image = self.request.get('image')
imagedata.put()
However, when I try to upload an image, lets say "Book.png", I get the error:
BadValueError: Expected str, got u'Book.png'
Any idea what is going on? I have been working with GAE for quite some time, but this is the first time I had to use blobs.
I used this link: https://developers.google.com/appengine/docs/python/images/usingimages
which uses db, not ndb.
I also tried storing the image in a variable first like in the link:
storedInfo = self.request.get('image')
and then storing it:
imagedata.image = ndb.Blob(storedInfo)
Which ALSO gives me an error:
AttributeError: 'module' object has no attribute 'Blob'
Thanks in advance.
Had the same prob.
just replace
imagedata.image = self.request.get('image')
with:
imagedata.image = str(self.request.get('image'))
also your form needs to have enctype="multipart/form-data
<form name = "input" action = "/register" method = "post" enctype="multipart/form-data">
There is a great example in the documentation that describes how to upload files to the Blobstore using a HTML form: https://developers.google.com/appengine/docs/python/blobstore/#Python_Uploading_a_blob
The form should point to a url generated by blobstore.create_upload_url('/foo') and there should be a subclass of the BlobstoreUploadHandler at /foo like this:
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file')
blob_info = upload_files[0]
imagedata = ImageData(parent=image_key(image_name))
imagedata.name = self.request.get('name')
imagedata.image = blob_info.key()
imagedata.put()
For this to work, you should change your data model such that in ImageData, image referes to a ndb.BlobKeyProperty().
You can serve your image simply from a url generated by images.get_serving_url(imagedata.image), optionally resized and cropped.
You must add enctype="multipart/form-data" to your form in order for this to work
<form name = "input" action = "/register" method = "post" enctype="multipart/form-data">
name: <input type = "text" name = "name">
image: <input type = "file" name = "image">
</form>
I am am using Google App Engine in Python and I want users to be able to upload a video, which is functioning properly by following their basic example but then I want to be able to get the user to add additional information about the video, like the title and category and a summary while it is uploading. Is there any way I can make the upload asynchronous so that the user doesn't have to wait the whole time the video is uploading?
I know about the create_upload_url_async() method but that doesn't do what I am trying.
Right now I have the following which uploads and serves but I want to make an intermediate step where the user can add the info preferably on the same screen as the UploadHandler while it is uploading.
class VideoHandler(BlogHandler):
def get(self):
user = self.get_user()
upload_url = blobstore.create_upload_url('/uploadingvideo')
self.render('videohandler.html', user=user, upload_url=upload_url)
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file') # 'file' is file upload field in the form
blob_info = upload_files[0]
self.redirect('/serve/%s' % blob_info.key())
class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, blob_key):
blob_key = str(urllib.unquote(blob_key))
if not blobstore.get(blob_key):
self.error(404)
else:
self.send_blob(blobstore.BlobInfo.get(blob_key))
I'd be glad to provide more information if you need it.
this has been troubling me alot too clifgray. the solution is to build your form around the blob form.
Here's google's example on how to submit data to the blob store.
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file') # 'file' is file upload field in the form
blob_info = upload_files[0]
self.redirect('/serve/%s' % blob_info.key())
Just remove the self redirect bit at the bottom of the code and add some code to handle your form.
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file') # 'file' is file upload field in the form
blob_info = upload_files[0]
greeting = Greeting()
if users.get_current_user():
greeting.author = users.get_current_user()
greeting.video = str(blob_info.key())
greeting.content = self.request.get('content')
greeting.put()
Your form in your django template should look something like this:
form action="{{ upload_url }}" enctype="multipart/form-data" method="post">
textarea name="content" placeholder="write something about this video.." tabindex="1" rows="2" cols="40"></textarea>
input name="file" type="file" >
input name="submit" type="submit" value="Submit" />
/form>
There you have it. hope this helps
I have been banging my head against the wall on this one, for some reason I am having trouble tying the different aspects of Google App Engine together to make this work.
Basically I want to let a user upload a photo to the Blobstore, which I have working in the below code, and then I want to put the BlobKey into a list which will be stored in a database entity. So here is my code to upload the image and where in here can I get the BlobKey so that I can store it?
class MainHandler(BlogHandler):
def get(self):
upload_url = blobstore.create_upload_url('/upload')
self.response.out.write('<html><body>')
self.response.out.write('<form action="%s" method="POST" enctype="multipart/form-data">' % upload_url)
self.response.out.write("""Upload File: <input type="file" name="file"><br> <input type="submit" name="submit" value="Submit"> </form></body></html>""")
#there is a lot more code in here where I get all the following info but it isn't relevant
location_db = Location(
description=description,
submitter=submitter,
user_id=user_id,
title=title,
locationtype = locationtype)
#This is what I would like to do but I don't know where to get thr BlobKey
location_db.blobRefs.append(BlobKey)
location_db.put()
for b in blobstore.BlobInfo.all():
self.response.out.write('<li>' + str(b.filename) + '')
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file')
blob_info = upload_files[0]
self.redirect('/main')
class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, blob_key):
blob_key = str(urllib.unquote(blob_key))
if not blobstore.get(blob_key):
self.error(404)
else:
self.send_blob(blobstore.BlobInfo.get(blob_key), save_as=True)
Here:
blob_info = upload_files[0]
self.redirect('/serve/%s' % blob_info.key())
I think it's that
blob_info.key()
you are missing. Grab that, stuff it into your list. Docs also note:
In this handler, you can store the blob key with the rest of your application's data model. The blob key itself remains accessible from the blob info entity in the datastore.
https://developers.google.com/appengine/docs/python/blobstore/overview#Serving_a_Blob