AppEngine: Python 2.7, Django Custom Template Filters without webapp, errors and more
Part of the move from Python 2.5 to Python 2.7 on Google App Engine, included moving from Django 0.96 to Django > 1.2. Another part of moving, for me atleast, included leaving webapp behind.
I moved to Django 1.3, but leaving webapp is a work in progress and a part of the topic of this post.
As of SDK 1.7.3, webapp is located in google.appengine.ext.webapp and the internal django is located at google.appengine._internal.django.
To use templates from webapp, one is expected to do the following
The webapp.template module is just a wrapper for Appengine's Internal Django templates. which are located at google.appengine._internal.django.template
To understand this, let us look at how Django Templates work in general.
How django.template works?
(sample code which shows how django.template works. For demonstration only. Not required for appengine. To test - Download and extract django, cd into django using terminal/cmd, run python. At the python prompt type in the following.)
This will output the following
There is support for providing external files(like "view.html"), to hold your template html, using django.template.loader.get_template.
This however involves, defining DJANGO_SETTINGS_MODULE, defining INSTALLED_APPS, TEMPLATE_DIRS, restructuring your project's folder structure etc.
To make things easier webapp.template acts as a wrapper for the above.
Using webapp.template
(sample code for demonstration, doesn't work at the command line)
and templates/view.html would contain the required html.
google.appengine.ext.webapp.template uses the internal django version at google.appengine._internal.django , to do your work. This means you do not have to directly import google.appengine._internal.django.
(Django is not fully compatible with Google's Datastore, but is with Google Cloud SQL.)
This library version of django, can be used by calling
import django
Which version of Django is used, depends upon app.yaml.
If the file app.yaml includes the following
In the above case, Django version 1.3 is used. The version number can be changed.
( Another method to import is using use_library
from google.appengine.dist import use_library
use_library('django', '1.3')
import django
This works in Python 2.5, but not in Python 2.7
On using in python 2.7, I get the following error
File "google_appengine/google/appengine/dist/_library.py", line 190, in DjangoVersion
import django
ImportError: No module named django
)
The topic of this post is using Custom Template Filters with Django Templates on Google App Engine.
The ways in which this can be done are.
1. Using AppEngine's Internal Django templates (webapp.template)
2. Using Django templates from Library Django (django.template)
Example One: Using AppEngine's Internal Django templates (webapp.template)
(My webapp implementation of custom template filters was based on an example from this 2008 blog post.)
customfilters.py should import webapp, have a variable named register and register the filter
Example Two: Using Django templates from Library Django (django.template)
There was one other way(a mix of the above two ways), which I tried, which actually made me write this post.
webapp.template with technique used in Library Django above.
If you go through, the documentation for Django Custom Tags and Filters for 1.3 or the current version of Django or the old Djnago 0.96 Documentation (put up by someone kind, on appspot), they all mention calling {% load filter_or_tag_name %} in the template. However doing that, when you are using webapp.template to call the internal django, does not seem to work.
A google search of "tried google.appengine._internal.django.templatetags", lead me to this bug report on code.google.com .
The bad source code which lead to the error, and a poor solution follow.
Bad Code:
Directory Structure mimics the structure of Library Django example above, However, running the app returned TemplateDoesNotExist error
app.yaml:
same as Library Django example
main.py is an amalgamation of different versions. One of them is below. The os.environ line is used to pointto settings.py, but it seems to have no effect.
customfilters.py is like the one from Library Django example above. Both file locations default/templatetags/customfilters.py and default/costumfilters.py( both with settings.py pointing to default ) and customfilters.py in the root folder were tried.
Explaining the error:
TemplateSyntaxError: 'customfilters' is not a valid tag library:
Template library customfilters not found, tried google.appengine._internal.django.templatetags.customfilters
On removing the {% load customfilters %} tag from view.html,the error would change to
1. The error is because of no load tag in the beginning or
2. The error is beacuse the filter is not registered.
While developing on appengine with python 2.7, I noticed, that .py files which were used by the server, would get compiled. In this example, main.pyc and appengine_config.pyc files were created. But, the settings.py file and customfilters.py(and its __init__.py file) were not compiled. Perhaps, the files were not being read at all.
If the files are not read at all, the filter would remain unregistered.
On reinstating the {% load customfilters %} tag in view.html, the original error returns.
app.yaml -> asks for django 1.3 from library. and sends control to main.py.
main.py->imports webapp.template, sets os.environ["DJANGO_SETTINGS_MODULE"]="settings"
settings.py->sets INSTALLED APPLICATIONS to default, but is apprently not read at all.
The problem, thus seems to be settings.py having no effect on webapp's internal django version.
The Fix:
The fix was to use
1. Use webapp.template to point to the tag/filter folder(filterdir.customfilters like in example one) in main.py
2. Remove {% load library_name %} from view.html and place view.html and base.html in the root
3. Remove os.environ from main.py and remove settings.py.
4. Keep customfilters.py in a folder named filterdir, like in example one. The contents of customfilters.py is the same as example two.
This essentially meant using webapp for pointing to the filter/tag file, but registering the filter using the library version of django.
If the filter were registered using webapp, then the fix would become the same as example one.
tl;dr:
use example two
Post a comment, if you find errors in the code.
Sources:
1. Custom Template Tags and Filters
2. Third Party libraries in Python 2.5
3. Using Django Custom Helper Elements
4. Using Custom Helper Elements in Django - daily.profeth.de
Webapp Django vs Django
AppEngines's Internal Django:
Webapp uses django to handle templating, however, this django version is an internal version(Let's call this "AppEngine's Internal Django"). This internal version may not support some of the features that a regular installation of Django templates would.As of SDK 1.7.3, webapp is located in google.appengine.ext.webapp and the internal django is located at google.appengine._internal.django.
To use templates from webapp, one is expected to do the following
from google.appengine.ext.webapp import template
The webapp.template module is just a wrapper for Appengine's Internal Django templates. which are located at google.appengine._internal.django.template
To understand this, let us look at how Django Templates work in general.
How django.template works?
(sample code which shows how django.template works. For demonstration only. Not required for appengine. To test - Download and extract django, cd into django using terminal/cmd, run python. At the python prompt type in the following.)
from django.template import Template, Context t = Template("<html><body>This is static data. This is {{ dynamicdata }}</body></html>") somevariable="some value" c = Context({'dynamicdata':somevariable}) t.render(c)
This will output the following
This is static data. This is some value
There is support for providing external files(like "view.html"), to hold your template html, using django.template.loader.get_template.
This however involves, defining DJANGO_SETTINGS_MODULE, defining INSTALLED_APPS, TEMPLATE_DIRS, restructuring your project's folder structure etc.
To make things easier webapp.template acts as a wrapper for the above.
Using webapp.template
(sample code for demonstration, doesn't work at the command line)
from google.appengine.ext.webapp import template somevariable="some value" output = template.render('templates/view.html', {'dynamicdata': somevariable }) print output
and templates/view.html would contain the required html.
google.appengine.ext.webapp.template uses the internal django version at google.appengine._internal.django , to do your work. This means you do not have to directly import google.appengine._internal.django.
Library Django
Apart from the partial internal version of Django, App engine also includes various libraries. As of SDK 1.7.3, Django 0.96 to Django 1.4 versions are included in the library. These versions of Django, apparently include the entire Django framework. Let us call these versions as "Library Django".(Django is not fully compatible with Google's Datastore, but is with Google Cloud SQL.)
This library version of django, can be used by calling
import django
Which version of Django is used, depends upon app.yaml.
If the file app.yaml includes the following
app.yaml ... libraries: - name: django version: "1.3" ...
In the above case, Django version 1.3 is used. The version number can be changed.
( Another method to import is using use_library
from google.appengine.dist import use_library
use_library('django', '1.3')
import django
This works in Python 2.5, but not in Python 2.7
On using in python 2.7, I get the following error
File "google_appengine/google/appengine/dist/_library.py", line 190, in DjangoVersion
import django
ImportError: No module named django
)
The topic of this post is using Custom Template Filters with Django Templates on Google App Engine.
The ways in which this can be done are.
1. Using AppEngine's Internal Django templates (webapp.template)
2. Using Django templates from Library Django (django.template)
Example One: Using AppEngine's Internal Django templates (webapp.template)
(My webapp implementation of custom template filters was based on an example from this 2008 blog post.)
Directory structure: appname-root-directory/ -filterdir/ -customfilters.py -__init__.py -main.py -app.yaml -view.html -base.html
app.yaml application: appname version: 1 runtime: python27 api_version: 1 threadsafe: true handlers: - url: /.* script: main.app #end of app.yaml#main.py should load webapp.template and register the filter by webapp.template.register_template_library('filterdir.customfilters'), where filterdir, is the directory where customfilters.py is located
main.py from google.appengine.ext import webapp from google.appengine.ext.webapp import template webapp.template.register_template_library('filterdir.customfilters') class Manager(webapp.RequestHandler): def template_print(self, template_file, template_values): self.response.headers['Content-Type'] = 'text/html; charset=utf-8' template_file_path = template_file res = template.render(template_file_path, template_values) self.response.out.write(res) class HomePage(Manager): def get(self): template_values = { 'hello' : "Hello World!" } self.template_print("view.html", template_values) app = webapp.WSGIApplication( [('/', HomePage)], debug=True) #end of main.py#
customfilters.py should import webapp, have a variable named register and register the filter
customfilters.py from google.appengine.ext import webapp register = webapp.template.create_template_register() @register.filter('addTuring') def addTuring(element): return str("Alan Turing says " + (element)) #end of customfilters.py#
__init__.py is an empty file
base.html {% block content %} Default Content {% endblock content %} #end of base.html#
view.html {% extends "base.html" %} {% block content %} {{hello|addTuring}} {% endblock content %} #end of view.html#
Example Two: Using Django templates from Library Django (django.template)
Directory structure: appname-root-directory/ -default/ -templates/ -base.html -view.html -templatetags/ -customfilters.py -__init__.py -__init__.py -main.py -app.yaml -view.html -base.html -settings.pyapp.yaml should mention libraries and load the required version of django.
app.yaml application: appname version: 1 runtime: python27 api_version: 1 threadsafe: true handlers: - url: /.* script: main.app libraries: - name: django version: "1.3" # end of app.yaml #main.py should load django.template and set os.environ['DJANGO_SETTINGS_MODULE'] to the name of the file which will contain django settings. In this case the string "settings" is used to refer to file settings.py
main.py import os os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' from google.appengine.ext import webapp from django.template import Template, Context from django.template.loader import get_template class Manager(webapp.RequestHandler): def template_print(self, template_file, template_values): self.response.headers['Content-Type'] = 'text/html; charset=utf-8' t=get_template(template_file) c = Context(template_values) output = t.render(c) self.response.out.write(output) class HomePage(Manager): def get(self): template_values = { 'hello' : "Hello World!" } self.template_print("view.html", template_values) app = webapp.WSGIApplication( [('/', HomePage)], debug=True) #end of main.py#settings.py should mention INSTALLED_APPS. The value should include the name of the folder containing the templatetags folder.Blank __init__.py files are required to avoid getting a "No module named" error
settings.py INSTALLED_APPS = ('default') #end of settings.py#customfilters.py should import django,and register the filter using djnago.template.Library().
customfilters.py from django import template register = template.Library() @register.filter('addTuring') def addTuring(element): return str("Alan Turing says " + (element)) #end of customfilters.py#
__init__.py are blank files
base.html(same in all examples) {% block content %} Default Content {% endblock content %} # end of base.html#view.html is the view/template(which extends a base template in this case. You could paste all the html in view.html with the {{}} tags, to avoid using the extends line. ) In view.html, load the filter library using {% load library_name %, where library_name in our case is customfilters, the name of the .py file containing the filter code.
view.html {% extends "base.html" %} {% load customfilters %} {% block content %} {{hello|addTuring}} {% endblock content %} #end of view.html#
There was one other way(a mix of the above two ways), which I tried, which actually made me write this post.
webapp.template with technique used in Library Django above.
If you go through, the documentation for Django Custom Tags and Filters for 1.3 or the current version of Django or the old Djnago 0.96 Documentation (put up by someone kind, on appspot), they all mention calling {% load filter_or_tag_name %} in the template. However doing that, when you are using webapp.template to call the internal django, does not seem to work.
TemplateSyntaxError: 'customfilter' is not a valid tag library: Template library customfilter not found, tried google.appengine._internal.django.templatetags.customfilter
A google search of "tried google.appengine._internal.django.templatetags", lead me to this bug report on code.google.com .
The bad source code which lead to the error, and a poor solution follow.
Bad Code:
Directory Structure mimics the structure of Library Django example above, However, running the app returned TemplateDoesNotExist error
TemplateDoesNotExist: view.htmlwhich meant that the application folder "default", was not being detected by the app, and hence the templates were not found. Appengine's webapp framework, searches the root of the application for templates unlike Django which searches applicationname/templates for templates and applicationname/templatetags for tags and filters. So, view.html and base.html were copied to the root folder too.
Directory structure: appname-root-directory/ -default/ -templates/ -base.html -view.html -templatetags/ -customfilters.py -__init__.py -__init__.py -main.py -app.yaml -view.html -base.html -settings.py -appengine_config.py -base.html -view.html
app.yaml:
same as Library Django example
appengine_config.py webapp_django_version = '1.3' #end of appengine_onfig.py#
main.py is an amalgamation of different versions. One of them is below. The os.environ line is used to pointto settings.py, but it seems to have no effect.
main.py import os from google.appengine.ext import webapp from google.appengine.ext.webapp import template os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' class Manager(webapp.RequestHandler): def template_print(self, template_file, template_values): self.response.headers['Content-Type'] = 'text/html; charset=utf-8' template_file_path = template_file output = template.render(template_file_path, template_values) self.response.out.write(output) class HomePage(Manager): def get(self): template_values = { 'hello' : "Hello World!" } self.template_print("view.html", template_values) app = webapp.WSGIApplication( [('/', HomePage)], debug=True) #end of main.py
customfilters.py is like the one from Library Django example above. Both file locations default/templatetags/customfilters.py and default/costumfilters.py( both with settings.py pointing to default ) and customfilters.py in the root folder were tried.
customfilters.py from django import template register = template.Library() @register.filter('addTuring') def addTuring(element): return str("Alan Turing says " + (element)) #end of customfilters.py#view.html contains the {% load library_name %} tag, like the Library Django example above
view.html {% extends "base.html" %} {% load customfilters %} {% block content %} {{hello|addTuring}} {% endblock content %} # end of view.html #
Explaining the error:
TemplateSyntaxError: 'customfilters' is not a valid tag library:
Template library customfilters not found, tried google.appengine._internal.django.templatetags.customfilters
On removing the {% load customfilters %} tag from view.html,the error would change to
TemplateSyntaxError: Invalid filter: 'addTuring'Which is expected. This means
1. The error is because of no load tag in the beginning or
2. The error is beacuse the filter is not registered.
While developing on appengine with python 2.7, I noticed, that .py files which were used by the server, would get compiled. In this example, main.pyc and appengine_config.pyc files were created. But, the settings.py file and customfilters.py(and its __init__.py file) were not compiled. Perhaps, the files were not being read at all.
If the files are not read at all, the filter would remain unregistered.
On reinstating the {% load customfilters %} tag in view.html, the original error returns.
app.yaml -> asks for django 1.3 from library. and sends control to main.py.
main.py->imports webapp.template, sets os.environ["DJANGO_SETTINGS_MODULE"]="settings"
settings.py->sets INSTALLED APPLICATIONS to default, but is apprently not read at all.
The problem, thus seems to be settings.py having no effect on webapp's internal django version.
The Fix:
The fix was to use
1. Use webapp.template to point to the tag/filter folder(filterdir.customfilters like in example one) in main.py
main.py ... webapp.template.register_template_library('filterdir.customfilters')
....
2. Remove {% load library_name %} from view.html and place view.html and base.html in the root
3. Remove os.environ from main.py and remove settings.py.
4. Keep customfilters.py in a folder named filterdir, like in example one. The contents of customfilters.py is the same as example two.
This essentially meant using webapp for pointing to the filter/tag file, but registering the filter using the library version of django.
If the filter were registered using webapp, then the fix would become the same as example one.
tl;dr:
use example two
Post a comment, if you find errors in the code.
Sources:
1. Custom Template Tags and Filters
2. Third Party libraries in Python 2.5
3. Using Django Custom Helper Elements
4. Using Custom Helper Elements in Django - daily.profeth.de
I was able to load my custom filters for all templates for solution #2 via:
ReplyDeletetemplate.base.add_to_builtins('default.templatetags')
thanks for the writeup!