Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 804f4a9

Browse filesBrowse files
author
Jeff Mendoza
committed
Merge pull request GoogleCloudPlatform#40 from GoogleCloudPlatform/jlm/appengine-images
Move sample using appengine images to this repository, add tests.
2 parents bde9556 + 0cd2032 commit 804f4a9
Copy full SHA for 804f4a9

File tree

Expand file treeCollapse file tree

8 files changed

+316
-0
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+316
-0
lines changed

‎appengine/images/README.md

Copy file name to clipboard
+68Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
## Images Guestbook Sample
2+
3+
This is a sample app for Google App Engine that exercises the [images
4+
Python
5+
API](https://cloud.google.com/appengine/docs/python/images/usingimages).
6+
7+
See our other [Google Cloud Platform github
8+
repos](https://github.com/GoogleCloudPlatform) for sample applications
9+
and scaffolding for other python frameworks and use cases.
10+
11+
## Run Locally
12+
13+
1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/),
14+
including the [gcloud tool](https://cloud.google.com/sdk/gcloud/),
15+
and [gcloud app
16+
component](https://cloud.google.com/sdk/gcloud-app).
17+
18+
1. Setup the gcloud tool.
19+
```
20+
gcloud components update app
21+
gcloud auth login
22+
gcloud config set project <your-app-id>
23+
```
24+
You don't need a valid app-id to run locally, but will need a valid id
25+
to deploy below.
26+
27+
1. Clone this repo.
28+
```
29+
git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
30+
cd appengine/images/
31+
```
32+
33+
1. Run this project locally from the command line.
34+
```
35+
gcloud preview app run ./app.yaml
36+
```
37+
38+
1. Visit the application at
39+
[http://localhost:8080](http://localhost:8080).
40+
41+
## Deploying
42+
43+
1. Use the [Cloud Developer
44+
Console](https://console.developer.google.com) to create a
45+
project/app id. (App id and project id are identical)
46+
47+
1. Configure gcloud with your app id.
48+
```
49+
gcloud config set project <your-app-id>
50+
```
51+
52+
1. Use the [Admin Console](https://appengine.google.com) to view data,
53+
queues, and other App Engine specific administration tasks.
54+
55+
1. Use gcloud to deploy your app.
56+
```
57+
gcloud preview app deploy ./app.yaml
58+
```
59+
60+
1. Congratulations! Your application is now live at your-app-id.appspot.com
61+
62+
## Contributing changes
63+
64+
* See [CONTRIBUTING.md](/CONTRIBUTING.md)
65+
66+
## Licensing
67+
68+
* See [LICENSE](/LICENSE)

‎appengine/images/__init__.py

Copy file name to clipboardExpand all lines: appengine/images/__init__.py
Whitespace-only changes.

‎appengine/images/app.yaml

Copy file name to clipboard
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This file specifies your Python application's runtime configuration
2+
# including URL routing, versions, static file uploads, etc. See
3+
# https://developers.google.com/appengine/docs/python/config/appconfig
4+
# for details.
5+
6+
runtime: python27
7+
api_version: 1
8+
threadsafe: yes
9+
10+
# Handlers define how to route requests to your application.
11+
handlers:
12+
13+
# This handler tells app engine how to route requests to a WSGI application.
14+
# The script value is in the format <path.to.module>.<wsgi_application>
15+
# where <wsgi_application> is a WSGI application object.
16+
- url: .* # This regex directs all routes to main.app
17+
script: main.app

‎appengine/images/favicon.ico

Copy file name to clipboard
8.15 KB
Binary file not shown.

‎appengine/images/index.yaml

Copy file name to clipboard
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
indexes:
2+
3+
# AUTOGENERATED
4+
5+
# This index.yaml is automatically updated whenever the dev_appserver
6+
# detects that a new type of query is run. If you want to manage the
7+
# index.yaml file manually, remove the above marker line (the line
8+
# saying "# AUTOGENERATED"). If you want to manage some indexes
9+
# manually, move them above the marker line. The index.yaml file is
10+
# automatically uploaded to the admin console when you next deploy
11+
# your application using appcfg.py.
12+
13+
- kind: Greeting
14+
ancestor: yes
15+
properties:
16+
- name: date
17+
direction: desc

‎appengine/images/main.py

Copy file name to clipboard
+132Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Copyright 2015 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# [START all]
16+
17+
import cgi
18+
import urllib
19+
20+
# [START import_images]
21+
from google.appengine.api import images
22+
# [END import_images]
23+
from google.appengine.api import users
24+
from google.appengine.ext import ndb
25+
26+
import webapp2
27+
28+
29+
# [START model]
30+
class Greeting(ndb.Model):
31+
"""Models a Guestbook entry with an author, content, avatar, and date."""
32+
author = ndb.StringProperty()
33+
content = ndb.TextProperty()
34+
avatar = ndb.BlobProperty()
35+
date = ndb.DateTimeProperty(auto_now_add=True)
36+
# [END model]
37+
38+
39+
def guestbook_key(guestbook_name=None):
40+
"""Constructs a Datastore key for a Guestbook entity with name."""
41+
return ndb.Key('Guestbook', guestbook_name or 'default_guestbook')
42+
43+
44+
class MainPage(webapp2.RequestHandler):
45+
def get(self):
46+
self.response.out.write('<html><body>')
47+
guestbook_name = self.request.get('guestbook_name')
48+
49+
greetings = Greeting.query(
50+
ancestor=guestbook_key(guestbook_name)) \
51+
.order(-Greeting.date) \
52+
.fetch(10)
53+
54+
for greeting in greetings:
55+
if greeting.author:
56+
self.response.out.write(
57+
'<b>%s</b> wrote:' % greeting.author)
58+
else:
59+
self.response.out.write('An anonymous person wrote:')
60+
# [START display_image]
61+
self.response.out.write('<div><img src="/img?img_id=%s"></img>' %
62+
greeting.key.urlsafe())
63+
self.response.out.write('<blockquote>%s</blockquote></div>' %
64+
cgi.escape(greeting.content))
65+
# [END display_image]
66+
67+
# [START form]
68+
self.response.out.write("""
69+
<form action="/sign?%s"
70+
enctype="multipart/form-data"
71+
method="post">
72+
<div>
73+
<textarea name="content" rows="3" cols="60"></textarea>
74+
</div>
75+
<div><label>Avatar:</label></div>
76+
<div><input type="file" name="img"/></div>
77+
<div><input type="submit" value="Sign Guestbook"></div>
78+
</form>
79+
<hr>
80+
<form>Guestbook name: <input value="%s" name="guestbook_name">
81+
<input type="submit" value="switch"></form>
82+
</body>
83+
</html>""" % (urllib.urlencode({'guestbook_name': guestbook_name}),
84+
cgi.escape(guestbook_name)))
85+
# [END form]
86+
87+
88+
# [START image_handler]
89+
class Image(webapp2.RequestHandler):
90+
def get(self):
91+
greeting_key = ndb.Key(urlsafe=self.request.get('img_id'))
92+
greeting = greeting_key.get()
93+
if greeting.avatar:
94+
self.response.headers['Content-Type'] = 'image/png'
95+
self.response.out.write(greeting.avatar)
96+
else:
97+
self.response.out.write('No image')
98+
# [END image_handler]
99+
100+
101+
# [START sign_handler]
102+
class Guestbook(webapp2.RequestHandler):
103+
def post(self):
104+
guestbook_name = self.request.get('guestbook_name')
105+
greeting = Greeting(parent=guestbook_key(guestbook_name))
106+
107+
if users.get_current_user():
108+
greeting.author = users.get_current_user().nickname()
109+
110+
greeting.content = self.request.get('content')
111+
112+
# [START sign_handler_1]
113+
avatar = self.request.get('img')
114+
# [END sign_handler_1]
115+
# [START transform]
116+
avatar = images.resize(avatar, 32, 32)
117+
# [END transform]
118+
# [START sign_handler_2]
119+
greeting.avatar = avatar
120+
greeting.put()
121+
# [END sign_handler_1]
122+
123+
self.redirect('/?' + urllib.urlencode(
124+
{'guestbook_name': guestbook_name}))
125+
# [END sign_handler]
126+
127+
128+
app = webapp2.WSGIApplication([('/', MainPage),
129+
('/img', Image),
130+
('/sign', Guestbook)],
131+
debug=True)
132+
# [END all]

‎appengine/images/tests/__init__.py

Copy file name to clipboardExpand all lines: appengine/images/tests/__init__.py
Whitespace-only changes.
+82Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2015 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# from the app main.py
16+
from appengine.images import main
17+
import mock
18+
from tests import DatastoreTestbedCase
19+
20+
import webapp2
21+
22+
23+
class TestHandlers(DatastoreTestbedCase):
24+
def test_get(self):
25+
# Build a request object passing the URI path to be tested.
26+
# You can also pass headers, query arguments etc.
27+
request = webapp2.Request.blank('/')
28+
# Get a response for that request.
29+
response = request.get_response(main.app)
30+
31+
# Let's check if the response is correct.
32+
self.assertEqual(response.status_int, 200)
33+
34+
@mock.patch('appengine.images.main.images')
35+
def test_post(self, mock_images):
36+
mock_images.resize.return_value = 'asdf'
37+
request = webapp2.Request.blank(
38+
'/sign',
39+
POST={'content': 'asdf'},
40+
)
41+
response = request.get_response(main.app)
42+
mock_images.resize.assert_called_once()
43+
44+
# Correct response is a redirect
45+
self.assertEqual(response.status_int, 302)
46+
47+
def test_img(self):
48+
greeting = main.Greeting(
49+
parent=main.guestbook_key('default_guestbook'),
50+
id=123,
51+
)
52+
greeting.author = 'asdf'
53+
greeting.content = 'asdf'
54+
greeting.put()
55+
56+
request = webapp2.Request.blank(
57+
'/img?img_id=%s' % greeting.key.urlsafe()
58+
)
59+
response = request.get_response(main.app)
60+
61+
self.assertEqual(response.status_int, 200)
62+
63+
def test_img_missing(self):
64+
# Bogus image id, should get error
65+
request = webapp2.Request.blank('/img?img_id=123')
66+
response = request.get_response(main.app)
67+
68+
self.assertEqual(response.status_int, 500)
69+
70+
@mock.patch('appengine.images.main.images')
71+
def test_post_and_get(self, mock_images):
72+
mock_images.resize.return_value = 'asdf'
73+
request = webapp2.Request.blank(
74+
'/sign',
75+
POST={'content': 'asdf'},
76+
)
77+
response = request.get_response(main.app)
78+
79+
request = webapp2.Request.blank('/')
80+
response = request.get_response(main.app)
81+
82+
self.assertEqual(response.status_int, 200)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.