|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
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 |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
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. |
|
16 # |
|
17 |
|
18 """A simple wrapper for Django templates. |
|
19 |
|
20 The main purpose of this module is to hide all of the package import pain |
|
21 you normally have to go through to get Django to work. We expose the Django |
|
22 Template and Context classes from this module, handling the import nonsense |
|
23 on behalf of clients. |
|
24 |
|
25 Typical usage: |
|
26 |
|
27 from google.appengine.ext.webapp import template |
|
28 print template.render('templates/index.html', {'foo': 'bar'}) |
|
29 |
|
30 Django uses a global setting for the directory in which it looks for templates. |
|
31 This is not natural in the context of the webapp module, so our load method |
|
32 takes in a complete template path, and we set these settings on the fly |
|
33 automatically. Because we have to set and use a global setting on every |
|
34 method call, this module is not thread safe, though that is not an issue |
|
35 for applications. |
|
36 |
|
37 Django template documentation is available at: |
|
38 http://www.djangoproject.com/documentation/templates/ |
|
39 """ |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 import md5 |
|
46 import os |
|
47 |
|
48 try: |
|
49 from django import v0_96 |
|
50 except ImportError: |
|
51 pass |
|
52 import django |
|
53 |
|
54 import django.conf |
|
55 try: |
|
56 django.conf.settings.configure( |
|
57 DEBUG=False, |
|
58 TEMPLATE_DEBUG=False, |
|
59 TEMPLATE_LOADERS=( |
|
60 'django.template.loaders.filesystem.load_template_source', |
|
61 ), |
|
62 ) |
|
63 except (EnvironmentError, RuntimeError): |
|
64 pass |
|
65 import django.template |
|
66 import django.template.loader |
|
67 |
|
68 from google.appengine.ext import webapp |
|
69 |
|
70 def render(template_path, template_dict, debug=False): |
|
71 """Renders the template at the given path with the given dict of values. |
|
72 |
|
73 Example usage: |
|
74 render("templates/index.html", {"name": "Bret", "values": [1, 2, 3]}) |
|
75 |
|
76 Args: |
|
77 template_path: path to a Django template |
|
78 template_dict: dictionary of values to apply to the template |
|
79 """ |
|
80 t = load(template_path, debug) |
|
81 return t.render(Context(template_dict)) |
|
82 |
|
83 |
|
84 template_cache = {} |
|
85 def load(path, debug=False): |
|
86 """Loads the Django template from the given path. |
|
87 |
|
88 It is better to use this function than to construct a Template using the |
|
89 class below because Django requires you to load the template with a method |
|
90 if you want imports and extends to work in the template. |
|
91 """ |
|
92 abspath = os.path.abspath(path) |
|
93 |
|
94 if not debug: |
|
95 template = template_cache.get(abspath, None) |
|
96 else: |
|
97 template = None |
|
98 |
|
99 if not template: |
|
100 directory, file_name = os.path.split(abspath) |
|
101 new_settings = { |
|
102 'TEMPLATE_DIRS': (directory,), |
|
103 'TEMPLATE_DEBUG': debug, |
|
104 'DEBUG': debug, |
|
105 } |
|
106 old_settings = _swap_settings(new_settings) |
|
107 try: |
|
108 template = django.template.loader.get_template(file_name) |
|
109 finally: |
|
110 _swap_settings(old_settings) |
|
111 |
|
112 if not debug: |
|
113 template_cache[abspath] = template |
|
114 |
|
115 def wrap_render(context, orig_render=template.render): |
|
116 URLNode = django.template.defaulttags.URLNode |
|
117 save_urlnode_render = URLNode.render |
|
118 old_settings = _swap_settings(new_settings) |
|
119 try: |
|
120 URLNode.render = _urlnode_render_replacement |
|
121 return orig_render(context) |
|
122 finally: |
|
123 _swap_settings(old_settings) |
|
124 URLNode.render = save_urlnode_render |
|
125 |
|
126 template.render = wrap_render |
|
127 |
|
128 return template |
|
129 |
|
130 |
|
131 def _swap_settings(new): |
|
132 """Swap in selected Django settings, returning old settings. |
|
133 |
|
134 Example: |
|
135 save = _swap_settings({'X': 1, 'Y': 2}) |
|
136 try: |
|
137 ...new settings for X and Y are in effect here... |
|
138 finally: |
|
139 _swap_settings(save) |
|
140 |
|
141 Args: |
|
142 new: A dict containing settings to change; the keys should |
|
143 be setting names and the values settings values. |
|
144 |
|
145 Returns: |
|
146 Another dict structured the same was as the argument containing |
|
147 the original settings. Original settings that were not set at all |
|
148 are returned as None, and will be restored as None by the |
|
149 'finally' clause in the example above. This shouldn't matter; we |
|
150 can't delete settings that are given as None, since None is also a |
|
151 legitimate value for some settings. Creating a separate flag value |
|
152 for 'unset' settings seems overkill as there is no known use case. |
|
153 """ |
|
154 settings = django.conf.settings |
|
155 old = {} |
|
156 for key, value in new.iteritems(): |
|
157 old[key] = getattr(settings, key, None) |
|
158 setattr(settings, key, value) |
|
159 return old |
|
160 |
|
161 |
|
162 def create_template_register(): |
|
163 """Used to extend the Django template library with custom filters and tags. |
|
164 |
|
165 To extend the template library with a custom filter module, create a Python |
|
166 module, and create a module-level variable named "register", and register |
|
167 all custom filters to it as described at |
|
168 http://www.djangoproject.com/documentation/templates_python/ |
|
169 #extending-the-template-system: |
|
170 |
|
171 templatefilters.py |
|
172 ================== |
|
173 register = webapp.template.create_template_register() |
|
174 |
|
175 def cut(value, arg): |
|
176 return value.replace(arg, '') |
|
177 register.filter(cut) |
|
178 |
|
179 Then, register the custom template module with the register_template_module |
|
180 function below in your application module: |
|
181 |
|
182 myapp.py |
|
183 ======== |
|
184 webapp.template.register_template_module('templatefilters') |
|
185 """ |
|
186 return django.template.Library() |
|
187 |
|
188 |
|
189 def register_template_library(package_name): |
|
190 """Registers a template extension module to make it usable in templates. |
|
191 |
|
192 See the documentation for create_template_register for more information.""" |
|
193 if not django.template.libraries.get(package_name, None): |
|
194 django.template.add_to_builtins(package_name) |
|
195 |
|
196 |
|
197 Template = django.template.Template |
|
198 Context = django.template.Context |
|
199 |
|
200 |
|
201 def _urlnode_render_replacement(self, context): |
|
202 """Replacement for django's {% url %} block. |
|
203 |
|
204 This version uses WSGIApplication's url mapping to create urls. |
|
205 |
|
206 Examples: |
|
207 |
|
208 <a href="{% url MyPageHandler "overview" %}"> |
|
209 {% url MyPageHandler implicit_args=False %} |
|
210 {% url MyPageHandler "calendar" %} |
|
211 {% url MyPageHandler "jsmith","calendar" %} |
|
212 """ |
|
213 args = [arg.resolve(context) for arg in self.args] |
|
214 try: |
|
215 app = webapp.WSGIApplication.active_instance |
|
216 handler = app.get_registered_handler_by_name(self.view_name) |
|
217 return handler.get_url(implicit_args=True, *args) |
|
218 except webapp.NoUrlFoundError: |
|
219 return '' |