How To Build A Personal Portal On Google App Engine With Django
1. ………
WECO Seminar Part II
How to build a personal portal on
Google App Engine with Django
Speaker : Jimmy Lu
Advisor : Hsing Mei
Web Computing Laboratory
Computer Science and Information Engineering Department
Fu Jen Catholic University
3. Design your personal portal
• What elements should a personal portal
have?
Text: a simple blog
- An editor
- Posts archive
Photos: upload and resize a profile picture
Videos: fetch latest video you upload to your
Youtube channel by Youtube API and embed
it
Widgets: embed Google Calendar and
WECO Lab, CSIE dept., FJU
March 2, 2010 3
Google Map http://www.weco.net
4. Environment settings (1/5)
main.py
1. import os
2. # point to seetings.py, then django knows how to configure the en
vironment
3. os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
4.
5. from google.appengine.ext.webapp.util import run_wsgi_app
6. from google.appengine.dist import use_library
7.
8. # use_library('library_name', 'version'), impot django library
9. use_library('django', '1.1')
WECO Lab, CSIE dept., FJU
March 2, 2010 4
http://www.weco.net
5. Environment settings (2/5)
10.
11. # django has implemented WSGI
12. import django.core.handlers.wsgi
13.
14. def main():
15. # Create a Django application for WSGI.
16. application = django.core.handlers.wsgi.WSGIHandler()
17.
18. # Run the WSGI CGI handler with that application.
19. run_wsgi_app(application)
20.
21.
22. if __name__ == '__main__':
23. main()
WECO Lab, CSIE dept., FJU
March 2, 2010 5
http://www.weco.net
6. Environment settings (3/5)
settings.py
import os
# Because the django database mapping is not work on Google App
Engine, we need to leave the database settings empty.
DATABASE_ENGINE = ''
DATABASE_NAME = ''
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = '‘
# localization
TIME_ZONE = 'Asia/Taipei’
LANGUAGE_CODE = 'zh-TW '
WECO Lab, CSIE dept., FJU
March 2, 2010 6
http://www.weco.net
7. Environment settings (4/5)
# The path of the project root directory.
ROOT_PATH = os.path.dirname(__file__)
# Set the path where your templates locate.
TEMPLATE_DIRS = (
ROOT_PATH + '/templates',
)
INSTALLED_APPS = (
# 'django.contrib.contenttypes',
# 'django.contrib.sessions',
)
# Leave the others default.
WECO Lab, CSIE dept., FJU
March 2, 2010 7
http://www.weco.net
8. Environment settings (5/5)
• app.yaml
Define the mapping of path to directory of
static files(stylesheets, images, etc.) and the
url.
handlers:
- url: /stylesheets
static_dir: static/stylesheets
- url: .*
script: main.py
WECO Lab, CSIE dept., FJU
March 2, 2010 8
http://www.weco.net
9. Implementation – models (1/2)
• models.py
We define three classes to store posts and
pictures.
1. class BlogPost(db.Model):
2. author = db.StringProperty()
3. title = db.StringProperty()
4. body = db.TextProperty()
5. # add the current time automatically.
6. timestamp = db.DateTimeProperty(auto_now_add = True)
7.
8. # a django form related to BlogPost.
9. class BlogPostForm(djangoforms.ModelForm):
10. class Meta:
March 11. model = BlogPost Lab, CSIE dept., FJU
WECO
2, 2010 9
http://www.weco.net
10. Implementation – models (2/2)
12.
13.class Picture(db.Model):
14. # use BlobProperty to store a photo.
15. profilePicture = db.BlobProperty()
16. timestamp = db.DateTimeProperty(auto_now_add = True)
WECO Lab, CSIE dept., FJU
March 2, 2010 10
http://www.weco.net
11. Implementation – views (1/8)
• views.py
Define a function to have an ability to edit a
blog post and save it to the database.
1. def edit(request):
2. # Use django form API, we can build a form object and display
a form easily.
3. blogPostForm = BlogPostForm(data = request.POST or None)
4. t = loader.get_template("edit.html")
5. c = Context({ 'blogPostForm': blogPostForm })
6. blogPost = None
7. if blogPostForm.is_valid():
WECO Lab, CSIE dept., FJU
March 2, 2010 11
http://www.weco.net
12. Implementation – views (2/8)
8. # Save the BlogPost object without save it to the databas
e(commit = False).
9. blogPost = blogPostForm.save(commit = False)
10. if blogPost:
11. # Save it to the database.
12. blogPost.put()
13. if request.POST:
14. # Redirect to the main page.
15. return http.HttpResponseRedirect('/')
16.
17. return HttpResponse(t.render(c))
WECO Lab, CSIE dept., FJU
March 2, 2010 12
http://www.weco.net
13. Implementation – views (3/8)
Define a function to upload profile pictures.
Notice that we use images API to resize the
uploaded images.
1. def upload(request):
2. if request.method == 'POST':
3. picture = Picture()
4. # Resize the picture by images API provided by Google App
engine.
5. profile_picture = images.resize(request.FILES['img'].read
(), 196, 196)
6. picture.profilePicture = db.Blob(profile_picture)
7. picture.put()
8. return http.HttpResponseRedirect('/')
9.
WECO Lab, CSIE dept., FJU
March 10.
2, 2010 return render_to_response('upload.html') 13
http://www.weco.net
14. Implementation – views (4/8)
When a browser renders main.html, it will
send request to the image source. By using
url mapping, we map the url request to this
function.
1. def image(request):
2. # Get the picture by the key from database.
3. picture = Picture.get(request.GET['img_id'])
4. # Build your response
5. response = HttpResponse(picture.profilePicture)
6. # Set the content type to png because that's what the Google
images api stores modified images as by default
7. response['Content-Type'] = 'image/png'
8.
9. return response WECO Lab, CSIE dept., FJU
March 2, 2010 14
http://www.weco.net
15. Implementation – views (5/8)
Use urlfetch API to fetch the response(XML
format) from Youtube API and parse it to a
DOM object. Then we can get the video id
under “entry” elements.
1. def video(request):
2. # From Youtube API document, you can get the url for getting
your uploaded video.
3. url = "http://gdata.youtube.com/feeds/api/users/gn00023040/up
loads"
4. # Fetch the result from url by urlfetch API provided by Googl
e App engine.
5. result = urlfetch.fetch(url)
6. id = ''
WECO Lab, CSIE dept., FJU
March 2, 2010 15
http://www.weco.net
16. Implementation – views (6/8)
7. if result.status_code == 200:
8. # Parse the XML format by minidom which is a python build-
in library.
9. xmldoc = minidom.parseString(result.content)
10. # An entry represents a video.
11. videos = xmldoc.getElementsByTagName('entry')
12. if videos:
13. # Get the first(latest) entry.
14. latest_video = videos[0]
15. id = latest_video.childNodes[0].childNodes[0].data[42:]
16.
17. return id
WECO Lab, CSIE dept., FJU
March 2, 2010 16
http://www.weco.net
17. Implementation – views (7/8)
• Send parameters to the templates to
display post archive, profile picture, latest
video and widgets.
1. def main(request):
2. # Get all the posts chronologically.
3. posts = BlogPost.all().order('-timestamp')
4. # Store some parameters by Context dictionary.
5. c = Context()
6. c['posts'] = posts
7. c.push()
8. # Get the latest picture.
9. pictures = Picture.all().order('-timestamp').fetch(1)
WECO Lab, CSIE dept., FJU
March 2, 2010 17
http://www.weco.net
18. Implementation – views (8/8)
1. if pictures:
2. picture = pictures[0]
3. c['image_key'] = picture.key()
4. c.push()
5. # Request to the video function.
6. video_id = video(request)
7. if video_id != '':
8. c['id'] = video_id
9. c.push()
10. # Load the template.
11. t = loader.get_template("main.html")
12.
13. # Send the parameters to the template, then the template will
process the result.
14. return HttpResponse(t.render(c))
WECO Lab, CSIE dept., FJU
March 2, 2010 18
http://www.weco.net
19. Implementation –
templates (1/6)
• edit.html
{{blogPostForm}} display a form for editing a
blog post.
1. <form action="{%url views.edit%}" method="post">
2. <table>
3. <!-- Display the form by django's form API -->
4. {{blogPostForm}}
5. <tr>
6. <td><input type="submit" value="Post"></td>
7. </tr>
8. </table>
9. </form>
WECO Lab, CSIE dept., FJU
March 2, 2010 19
http://www.weco.net
20. Implementation –
templates (2/6)
• upload.html
Make sure the enctype attribute should be
multipart/from-data
1. <!--
Remember the enctype attribute of the form shold by multipart/fr
om-data or request.FILES will receive nothing. -->
2. <form action="{%url views.upload%}" method="post" enctype="multip
art/form-data">
3. <input type="file" name="img" />
4. <input type="submit" value="Upload" />
5. </form>
WECO Lab, CSIE dept., FJU
March 2, 2010 20
http://www.weco.net
21. Implementation –
templates (3/6)
• main.html
Extend base.html and display post archive.
1. {% extends "base.html" %}
2. {% block content %}
3. <!-- Display all the posts iteratively -->
4. {% for post in posts %}
5. <h2>{{ post.title }}</h2>
6. <p>{{ post.timestamp|date:"l, F jS" }}</p>
7. <p>{{ post.body }}</p>
8. {% endfor %}
9. {% endblock %}
WECO Lab, CSIE dept., FJU
March 2, 2010 21
http://www.weco.net
22. Implementation –
templates (4/6)
• base.html
Map the src=“image?img_id=…” to the image
function and get the image id by
request.GET[„image_id‟]
1. <h2>My Picture</h2>
2. <!-- If there is no profile picture, we will ask for one. -->
3. {% if image_key %}
4. <!--
Django will map the url, 'image?img_id=...', to the image functi
on and get the image_key by requets.GET -->
5. <img src="image?img_id={{ image_key }}"></img>
6. {% else %}
7. Click <a href="upload">here</a> to upload your profile picture!
8. {% endif %}
WECO Lab, CSIE dept., FJU
March 2, 2010 22
http://www.weco.net
23. Implementation –
templates (5/6)
Embed your latest video by change the video
id, {{id}}, to the embedded code provide by
Youtube.
1. <object width="425" height="344">
2. <param name="movie" value="http://www.youtube.com/v/{{ id }}&hl=z
h_TW&fs=1&"></param>
3. <param name="allowFullScreen" value="true"></param>
4. <param name="allowscriptaccess" value="always"></param>
5. <embed src="http://www.youtube.com/v/{{ id }}&hl=zh_TW&fs=1&" typ
e="application/x-shockwave-
flash" allowscriptaccess="always" allowfullscreen="true" width="4
25" height="344"></embed>
6. </object>
WECO Lab, CSIE dept., FJU
March 2, 2010 23
http://www.weco.net
24. Implementation –
templates (6/6)
Where the result of main.html diplay.
1. {% block content %}
2. {% endblock %}
We can also add some widgets here. Like
Google Calendar or Google Map
1. <h2>My Schedule</h2>
2. <iframe src="http://www.google.com/calendar/embed?showTitle=0&sho
wNav=0&showDate=0&showPrint=0&showTabs=0&showCalendars=0&mode=AGE
NDA&height=400&wkst=1&bgcolor=%23453&src=weco.net_fm86efcblmn4qle
bh9kometb6o%40group.calendar.google.com&color=%232952A3&ctz=Asia%
2FTaipei" style=" border-
width:0 " width="290" height="400" frameborder="0" scrolling="no"
></iframe>
WECO Lab, CSIE dept., FJU
March 2, 2010 24
http://www.weco.net