add demonstration of geosearch
[gae-samples.git] / image_sharing / image_sharing.py
blobf305529525d5a4b9010564bd0b9d3ef6b7c6564a
1 #!/usr/bin/python2.5
3 # Copyright 2008 Google Inc. All Rights Reserved.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Image Sharing, a simple picture sharing application running on App Engine.
19 Specific App Engine features demonstrated include:
20 * the Image API, which is used for creating thumbnails of uploaded pictures
21 * the Datastore API, which is used to store both the metadata for and
22 the actual content of pictures
23 * the ListProperty type, which is used for efficient tagging support, and
24 * support for file uploads using the WebOb request object
26 We use the webapp.py WSGI framework and the Django templating
27 language, both of which are documented in the App Engine docs
28 (http://appengine.google.com/docs/).
29 """
31 __author__ = 'Fred Wulff'
34 import cgi
35 import os
37 from google.appengine.api import images
38 from google.appengine.api import users
39 from google.appengine.ext import db
40 from google.appengine.ext import webapp
41 from google.appengine.ext.webapp import template
43 import wsgiref.handlers
46 # Makes the tags defined by templatetags/basetags.py usable
47 # by templates rendered in this file
48 template.register_template_library('templatetags.basetags')
51 class Album(db.Model):
52 """An album is an organizational unit for pictures.
54 Properties:
55 name: sanitized, user entered name for the album
56 creator: Google Account of the person who created the album
57 created_date: DateTime the album was created
58 """
60 name = db.StringProperty()
61 creator = db.UserProperty()
62 created_date = db.DateTimeProperty(auto_now_add=True)
65 class Picture(db.Model):
66 """Storage for a picture and its associated metadata.
68 Properties:
69 submitter: Google Account of the person who submitted the picture
70 submitted_date: DateTime the picture was submitted
71 title: sanitized, user entered title for the picture
72 caption: sanitized, user entered caption for the picture
73 album: reference to album the picture is in
74 tags: a StringListProperty of tags for the picture
75 data: data for the original picture, converted into png format
76 thumbnail_data: png format data for the thumbnail for this picture
77 """
79 submitter = db.UserProperty()
80 submitted_date = db.DateTimeProperty(auto_now_add=True)
81 title = db.StringProperty()
82 caption = db.StringProperty(multiline=True)
83 album = db.ReferenceProperty(Album, collection_name='pictures')
84 tags = db.StringListProperty()
85 data = db.BlobProperty()
86 thumbnail_data = db.BlobProperty()
89 class ImageSharingBaseHandler(webapp.RequestHandler):
90 """Base Image Sharing RequestHandlers with some convenience functions."""
92 def template_path(self, filename):
93 """Returns the full path for a template from its path relative to here."""
94 return os.path.join(os.path.dirname(__file__), filename)
96 def render_to_response(self, filename, template_args):
97 """Renders a Django template and sends it to the client.
99 Args:
100 filename: template path (relative to this file)
101 template_args: argument dict for the template
103 template_args.setdefault('current_uri', self.request.uri)
104 self.response.out.write(
105 template.render(self.template_path(filename), template_args)
109 class ImageSharingAlbumIndex(ImageSharingBaseHandler):
110 """Handler for listing albums."""
112 def get(self):
113 """Lists all available albums."""
114 albums = Album.all().order('-created_date')
115 self.render_to_response('index.html', {
116 'albums': albums,
120 class ImageSharingAlbumCreate(ImageSharingBaseHandler):
121 """Handler for creating a new Album via form."""
123 def get(self):
124 """Displays the album creation form."""
125 self.render_to_response('new.html', {})
127 def post(self):
128 """Processes an album creation request."""
129 Album(name=cgi.escape(self.request.get('albumname')),
130 creator=users.get_current_user()).put()
131 self.redirect('/')
133 PICTURES_PER_ROW = 5
135 class ImageSharingAlbumView(ImageSharingBaseHandler):
136 """Handler for viewing the pictures in a particular album."""
138 def get(self, album_key):
139 """Displays a single album.
141 Note that in this and later handlers, the args come
142 from a capturing group in the WSGIApplication specification.
143 See the webapp framework docs for more info.
145 Args:
146 album_key: the datastore key for the Album to view.
148 album = db.get(album_key)
150 pics = []
151 num_results = 0
153 for picture in album.pictures:
154 if num_results % PICTURES_PER_ROW == 0:
155 current_row = []
156 pics.append(current_row)
157 current_row.append(picture)
158 num_results += 1
160 self.render_to_response('album.html', {
161 'num_results': num_results,
162 'album_key': album.key(),
163 'pics': pics,
164 'album_name': album.name
168 class ImageSharingUploadImage(ImageSharingBaseHandler):
169 """Handler for uploading images."""
171 def get(self, album_key):
172 """Display the image upload form.
174 Args:
175 album_key: datastore key for the album to upload the image to
177 album = db.get(album_key)
178 self.render_to_response('upload.html', {
179 'album_key': album.key(),
180 'album_name': album.name
183 def post(self, album_key):
184 """Process the image upload form.
186 We also generate the thumbnail for the picture at this point.
188 Args:
189 album_key: datastore key for the album to add the image to
191 album = db.get(album_key)
192 if album is None:
193 self.error(400)
194 self.response.out.write('Couldn\'t find specified album')
196 title = cgi.escape(self.request.get('title'))
197 caption = cgi.escape(self.request.get('caption'))
198 tags = cgi.escape(self.request.get('tags')).split(',')
199 tags = [tag.strip() for tag in tags]
200 # Get the actual data for the picture
201 img_data = self.request.POST.get('picfile').file.read()
203 try:
204 img = images.Image(img_data)
205 # Basically, we just want to make sure it's a PNG
206 # since we don't have a good way to determine image type
207 # through the API, but the API throws an exception
208 # if you don't do any transforms, so go ahead and use im_feeling_lucky.
209 img.im_feeling_lucky()
210 png_data = img.execute_transforms(images.PNG)
212 img.resize(60, 100)
213 thumbnail_data = img.execute_transforms(images.PNG)
215 Picture(submitter=users.get_current_user(),
216 title=title,
217 caption=caption,
218 album=album,
219 tags=tags,
220 data=png_data,
221 thumbnail_data=thumbnail_data).put()
223 self.redirect('/album/%s' % album.key())
224 except images.BadImageError:
225 self.error(400)
226 self.response.out.write(
227 'Sorry, we had a problem processing the image provided.')
228 except images.NotImageError:
229 self.error(400)
230 self.response.out.write(
231 'Sorry, we don\'t recognize that image format.'
232 'We can process JPEG, GIF, PNG, BMP, TIFF, and ICO files.')
233 except images.LargeImageError:
234 self.error(400)
235 self.response.out.write(
236 'Sorry, the image provided was too large for us to process.')
238 class ImageSharingShowImage(ImageSharingBaseHandler):
239 """Handler for viewing a single image.
241 Note that this doesn't actually serve the picture, only the page
242 containing it. That happens in ImageSharingServeImage.
245 def get(self, pic_key):
246 """Renders the page for a single picture.
248 Args:
249 pic_key: key for the Picture model for the picture to display
252 pic = db.get(pic_key)
253 self.render_to_response('show_image.html', {
254 'pic': pic,
255 'image_key': pic.key(),
259 class ImageSharingServeImage(webapp.RequestHandler):
260 """Handler for dynamically serving an image from the datastore.
262 Very simple - it just pulls the appropriate data out of the datastore
263 and serves it.
266 def get(self, display_type, pic_key):
267 """Dynamically serves a PNG image from the datastore.
269 Args:
270 type: a string describing the type of image to serve (image or thumbnail)
271 pic_key: the key for a Picture model that holds the image
273 image = db.get(pic_key)
275 if display_type == 'image':
276 self.response.headers['Content-Type'] = 'image/png'
277 self.response.out.write(image.data)
278 elif display_type == 'thumbnail':
279 self.response.headers['Content-Type'] = 'image/png'
280 self.response.out.write(image.thumbnail_data)
281 else:
282 self.error(500)
283 self.response.out.write(
284 'Couldn\'t determine what type of image to serve.')
286 class ImageSharingSearch(ImageSharingBaseHandler):
287 """Handler for searching pictures by tag."""
289 def get(self):
290 """Displays the tag search box and possibly a list of results."""
291 query = cgi.escape(self.request.get('q'))
292 pics = []
293 if query:
294 # ListProperty magically does want we want: search for the occurrence
295 # of the term in any of the tags.
296 pics = Picture.all().filter('tags =', query)
297 else:
298 query = ''
299 self.render_to_response('search.html', {
300 'query': query,
301 'pics': pics,
305 def main():
306 url_map = [('/', ImageSharingAlbumIndex),
307 ('/new', ImageSharingAlbumCreate),
308 ('/album/([-\w]+)', ImageSharingAlbumView),
309 ('/upload/([-\w]+)', ImageSharingUploadImage),
310 ('/show_image/([-\w]+)', ImageSharingShowImage),
311 ('/(thumbnail|image)/([-\w]+)', ImageSharingServeImage),
312 ('/search', ImageSharingSearch)]
313 application = webapp.WSGIApplication(url_map,
314 debug=True)
315 wsgiref.handlers.CGIHandler().run(application)
317 if __name__ == '__main__':
318 main()