As of 2016-02-26, there will be no more posts for this blog. s/blog/pba/
Showing posts with label Django. Show all posts

Recently, I upgraded SDK to 1.4.2 from 1.4.1 and added new stuff to lil.btn. I was trying to read the Django 0.96 template documentation on Django’s website, then I found out the page has been removed. Actually, I recalled I have known the removal of the page for some time.

1   Django 1.2.5 Template

Anyway, I decided to use Django 1.2.5 Template since it’s shipped with SDK. Amazingly, it’s so easy to switch to another version from the default version 0.96. This is what I only added before the template import, suggested by the doc,


# os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

from google.appengine.dist import use_library
use_library('django', '1.2')

from google.appengine.ext.webapp import template

I only use template. I don’t know if i18n will be this easy as well, I might do a follow-up later. I need to mention I didn’t want to switch at first and still don’t. But it seems that I have to.

1.1   autoescape

One thing about Django 1.2.5 I like is it automatically enables escaping for preventing code injection on client-side. You know developers sometimes are lazy or careless, because we think will review our codes once everything is settled. But one might be missed or even intentionally skipping reviewing some codes and forget there is always a risk.

When I was using 0.96, I often did {{ varname|striptags|escape }} or just {{ varname|escape }}. I didn’t know if there is one I forgot to filter, now I need to worry less. If you are sure some don’t need, you just {{ varname|safe }} so Django won’t escape it.

I don’t know which version it was started with, but it was commited into code three years ago.

1.2   Loading humanize

Probably two years ago, I wanted to add thousands separator to some statistic integer numbers. You can always do that in your Python code1.

I know there is a set of template filters called humanize in Django, why re-invent? I tried to include it into of my GAE app. I got the code from SDK and removed unwanted filters and put it in the top level of my source.

But I found out there is a really quick and easy way and it works for both 0.96 and 1.2 (1.2.5),


def main():

webapp.template.register_template_library('django.contrib.humanize.templatetags.humanize')
run_wsgi_app(application)

That’s it. You just use that register and there is no need for {% load humanize %} in your template, you just add the filters wherever you want, e.g. {{ varname|intcomma }}. I believe this also works on other contributed Django templating code sitting in /contrib.

When I firstly tried, I even added a Django setting file, but I couldn’t get it to work. Few days ago, I read this question. It gave me an idea, though I can’t just use import to get it work. I use that register function with full module path, that works.

[1]Coding by yourself or using Python 2.7+, but this isn’t the case for Google App Engine. But may be some day, supporting 2.7 is listed in roadmap.

2   Capability name of datastore for CapabilitySet

I read the documentation about the supported capabilities and I had these


CapabilitySet('datastore').is_enabled()
CapabilitySet('datastore_write').is_enabled()
CapabilitySet('memcache').is_enabled()

They run fine on development server, however, after I deployed the code, I got:


Traceback (most recent call last):
File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/webapp/__init__.py", line 515, in __call__
handler.get(*groups)
File "/base/data/home/apps/lilbtn/1.348320236448169656/itchape.py", line 205, in get
if CapabilitySet('datastore').is_enabled():
File "/base/python_runtime/python_lib/versions/1/google/appengine/api/capabilities/__init__.py", line 98, in is_enabled
config = self._get_status()
File "/base/python_runtime/python_lib/versions/1/google/appengine/api/capabilities/__init__.py", line 170, in _get_status
raise UnknownCapabilityError()
UnknownCapabilityError

The memory cache part is fine. I don’t know why that name of capability didn’t work, but I found an alternates,


db.READ_CAPABILITY
db.WRITE_CAPABILITY

But I use these later,


CapabilitySet('datastore_v3', capabilities=['read']).is_enabled()
CapabilitySet('datastore_v3', capabilities=['write']).is_enabled()

I found the second line on Handling downtime: The capabilities API and testing, which I had skimmed when it was published.

Note

I Thank is dead and some links have been removed from this post. (2015-12-13T03:24:41Z)

Last of February, I started a new project “I Thank.” It’s built on Google App Engine. I put a lot of things on it, which I haven’t done before. Such as

  • Google Account authentication,
  • sharding counters for calculating all entities,
  • Django’s i18n, custom template tag and feed generator,
  • pagination,
  • unittest using GAEUnit,
  • and other small bits.

You can access the code (BSD’ed) at Google Code hosting. Believe it or not, this is my most prettiest project in terms of style. It’s nearly Pink! There are also many things that I didn’t take care of, e.g. I think it looks ugly in IE and maybe others non-Firefox web browsers.

It’s still under development stage, hope I can get some feedback from you, and you can go checking it out and thank someone!

Note

This post was written only for Django 0.96.1 in GAE.

Two days ago, I started to create another Google App Engine application. This application will be internationalized when it’s finished. I tried searching for some solution, then I realized that there is no very simple way to achieve.

Normally, you can handle gettext stuff on your own, but our Google App Engine applications usually use templating from the SDK, which is from Django actually. One way or another, we have to incorporate with Django partially.

The goal here is:

  • Use minimal Django stuff, only import the essential stuff in order to get Django’s I18N support to work.
  • Messages in template must be translated, too.
  • Capable to decide the language from the cookie, django_language, or the request header, HTTP_ACCEPT_LANGUAGE.

I have already made a sample code, which you can read here and you can see it at http://yjltest.appspot.com/i18n.

Note

http://yjltest.appspot.com/i18n is gone. (2015-12-14T06:40:18Z)

Before we go into the code, please read the I18N1 and Settings2 of Django.

1   Setting Up

We need to use Django Settings to make I18N work. The reason of using Setting was due to Django’s gettext helper will require Settings module and decide location of message files by the location of Settings module.

If we want to use Django Setting, we must run the following code:

from google.appengine.ext.webapp import template

os.environ['DJANGO_SETTINGS_MODULE'] = 'conf.settings'
from django.conf import settings
# Force Django to reload settings
settings._target = None

Note that you must import the google.appengine.ext.webapp.template module, or you might get error about conf.settings is not able to be imported.

We need to set the environment variable DJANGO_SETTINGS_MODULE to the location of Setting module, conf.settings in this case. conf is the package and settings is a module file, our Settings module.

Why conf? Because when we generate message files from Python scripts and templates — we will see how to generate later, the Django message file generator, make-messages.py, will create files under conf/locale/ from where it’s run.

2   Settings

What do we need in conf/settings.py?

USE_I18N = True

# Valid languages
LANGUAGES = (
    # 'en', 'zh_TW' match the directories in conf/locale/*
    ('en', _('English')),
    ('zh_TW', _('Chinese')),

    # or ('zh-tw', _('Chinese')), # But the directory must still be conf/locale/zh_TW

    )# This is a default languageLANGUAGE_CODE = 'en'

3   Mark the messages

Wraps those need to be translated with _("message") in Python script and {% trans "message" %} in template files. Please read I18N1 for more usages.

4   Generate message files

Before you run the helper script, we need to create conf/locale, the helper won’t create it for us.

Make sure you are at root of Google App Engine application’s directory, then run:

$ PYTHONPATH=/path/to/googleappengine/python/lib/django/ /path/to/googleappengine/python/lib/django/django/bin/make-messages.py -l en

/path/to/googleappengine/ is the Google App Engine SDK’s location. This command should generate the conf/locale/en/LC_MESSAGE/django.po. Now you can open it to translate.

Don’t forget to set CHARSET, Usually UTF-8 will be fine, the line would read like:

"Content-Type: text/plain; charset=UTF-8\n"

Once you finish translating, you need to run:

$ PYTHONPATH=/path/to/python/googleappengine/lib/django/ /path/to/googleappengine/python/lib/django/django/bin/compile-messages.py

It will generate django.mo files in each language directories. You also need to update when you modify scripts or template, run:

$ PYTHONPATH=/path/to/googleappengine/python/lib/django/ /path/to/googleappengine/python/lib/django/django/bin/make-messages.py -a

This will update all languages in conf/locale.

5   Working?

If you run your application, now it should show the language in conf.settings.LANGUAGE_CODE.

This is a per application setting, which is not normally that we want. We will expect each user can choose their own language. Django has a helper that calls LocaleMiddleware can do the job, unfortunately, it needs Django’s request and response class to work normally.

6   Do the dirty job

In order to do what LocaleMiddleware does, we need to make Google App Engine’s request/response objects have same behavior as Djagno’s. For easing the complexity, we create a new class, I18NRequestHandler, which inherits google.ext.webapp.RequestHandler. You only need to replace with it in your handlers.

import os

from google.appengine.ext import webapp
from django.utils import translation

class I18NRequestHandler(webapp.RequestHandler):

  def initialize(self, request, response):

    webapp.RequestHandler.initialize(self, request, response)

    self.request.COOKIES = Cookies(self)
    self.request.META = os.environ
    self.reset_language()

  def reset_language(self):

    # Decide the language from Cookies/Headers
    language = translation.get_language_from_request(self.request)
    translation.activate(language)
    self.request.LANGUAGE_CODE = translation.get_language()

    # Set headers in response
    self.response.headers['Content-Language'] = translation.get_language()
#    translation.deactivate()

Where Cookies is from http://appengine-cookbook.appspot.com/recipe/a-simple-cookie-class/ (dead link with long gone Cookbook). When request comes in, it can automatically activate the language from what Cookies/Headers specify.

7   Caching problem

It’s not so perfect. I have noticed a problem in development server. If you change code and/or the message file, recompile the message file while server still runs, those message in entry script may not be translated for reflecting to cookie django_language‘s change. I believe that is about the caching.

I am not sure the natural problems, so I couldn’t solve it. However, this may not be severe problem.

8   Encoding

If you use unicode string (not str string) in {% blocktrans %} template tag, you may get error, encode it to utf-8 first, e.g. s.encode('utf-8').

9   Language Code

You must use underscore not dash for messages directory, e.g aa_BB, or Django would not recognize directory named as aa-BB or aa-bb. But in conf.settings you can use aa-bb, this means the language code and directory can be different, e.g. zh-tw for the language code in Python and zh_TW as message directory name.

10   Conclusion

Although this will work, but it may be broken if any changes to Django framework within Google App Engine. There isn’t a good solution for I18N in Google App Engine if Google doesn’t natively support it.

11   Updates

  • 2009-11-25: Added not about template module first and encoding issue, and updated the path of Python lib in GAE SDK.
  • 2009-12-24: Added a note about Language Code format, thanks BRAGA, again.
  • 2010-02-04: Added a note about the Language Code and message directory name.
  • 2013-02-17: fix dead links and typos.
  • 2013-07-24: remove “.rst” from title, update link.
[1](1, 2) Django 0.96 documentation http://www.djangoproject.com/documentation/0.96/i18n/ is gone, the link is for Django 1.4.
[2]Django 0.96 documentation http://www.djangoproject.com/documentation/0.96/settings/ is gone, the link is for Django 1.4.