Multiple Template Engines > Updates - Funders - DEP - Campaign
Since I achieved all the goals laid out in my Indiegogo campaign, I'm considering this project complete. Once again I'd like to thank all the generous donors who made it possible!
This is the last post in this series. Of course, such a large refactoring creates opportunites for further improvements. If you're interested, watch for discussions on Django's usual communication channels, django-developers and django-updates.
Following the release of 1.8 alpha, I stepped back, mostly because I needed a break, but also because I wanted to get some perspective on what I done during the last four months and what the next steps should be. So here's a retrospective.
Many things went well:
The Indiegogo campaign received exactly the amount of donations I expected. I had promised to donate a percentage back to the DSF and the Ada Initiative above a certain level — which I did. The real purpose of this clause was to define a "soft cap" on donations.
The project roughly fit into the amount of time I had allocated, 120 hours for planned work and 40 hours for unexpected problems, except for the pile of tickets still assigned to me. The feature will be available in Django 1.8 in a few months.
The implementation work was rather tedious. Replacing all references to the TEMPLATE_*
settings in tests was a low point. Creating accurate deprecation paths throughout django.template
wasn't much better. That's why I ran the Indiegogo campaign — to guarantee that I wouldn't give up midway.
Throughout the project I received a lot of feedback and support from other community members, notably Carl Meyer and Tim Graham. Discussions on the DEP brought many worthwile API improvements. Thanks to the reviews, I believe the code and docs are quite good.
The general design laid out in the initial version of the DEP held up well. The template backend API was simplified along the way, which is a good thing, because some responsibilities shifted from template backends to Django.
I made a few interim merges in order to keep my differences with master manageable, while maintaining the backwards-compatibility guarantees at all time.
The stream of commits is fairly clean and auditable. Only a handful of code blocks were changed several times. That took a lot of rebasing and a bit of luck.
A few things didn't go as planned:
When I started the campaign, I didn't understand that refactoring the Django Template Language as a standalone library would be a prerequisite for implementing Multiple Template Engines without horrific hacks. Fortunately the stretch goal was reached.
I hadn't realized to what extent Django relied on its global template engine. Something as innocent as template = Template("Hello {}!")
needs the global engine to compile the template string and later render it. I kept a special case for the unique Django template engine in projects that define exactly one.
Furthermore django.template
wasn't implemented with encapsulation in mind. There wasn't a good way to pass a reference to the engine everywhere. The least awful solution was to attach it to both the template and the context, but that came at the cost of a nasty hack.
The code in django.template
feels noticeably older than many other parts of Django. It hasn't received much maintenance of the years. It's often difficult to perform clean surgery and guarantee backwards compatibility.
I hadn't seen that current_app
coupled the template engine with Django's URL resolver. I moved it to an attribute of the request
but that doesn't help people who reverse namespaced URLs when rendering templates outside of the request-response cycle.
I underestimated the complexity of refactoring the render
and render_to_response
functions as well as the TemplateResponse
and SimpleTemplateResponse
classes.
I didn't deal with internationalization (#24167). The more I think about it, the less I find it sensible to reinvent Babel, which looks like a better implementation of makemessages
.
Only after finishing the project did I realize that I had forgotten to make it possible to select a particular template engine in TemplateResponse
and TemplateView
(#24168).
I used to think that the Django Template Language was a decent template engine despite a few quirks and that it could merely use a slightly better implementation e.g. grow an actual lexer instead of a bunch of regexes. My opinion has changed. Some stories are better left untold ;-)
Don't worry, it's still fine, and it may even be slightly better than it used to. I've taken care of the pieces under the hood so you don't have to!
In addition to polishing and merging changes I submitted in PR 3883 last week, I tied up some loose ends.
Throughout the project, I maintained a todo list of items to figure out later, once the bulk of the work would be done. Some got done along the way, others accumulated. After reviewing them, I pushed some relatively minor code cleanups and I filed a series of Trac tickets. Items that need changes by Django 1.8 final are tagged with the 1.8 keyword.
Collin Anderson and I already completed one of them: trimming the list of template context processors in the default TEMPLATES
setting generated by the project template, according to the discussion on django-developers.
During the cleanup, I audited all uses of Engine.get_default()
. As a reminder, the purpose of this method is to preserve the ability to instantiate a template with template = Template(template_code)
without specifying an engine when exactly one DjangoTemplates
engine is configured. Eventually it's called:
Template.__init__
— this is the original use casedjango.contrib.admin
— see #24116django.contrib.admindocs
— see #24125django.views.debug
— see #24120 and #24119Engine
instance in argument: it would be better to isolate these tests but it doesn't matter muchThese tickets aren't critical. Some functionality isn't available when several DjangoTemplates
engines are configured but I don't expect this to be a common scenario.
In the same vein, I audited all uses of engines['django']
. It's used in tests to create short templates e.g. template = engines['django'].from_string("Hello")
. This happens in Django's test suite, where it's fine because settings are under control, and in the tests for django.contrib.messages
, where it's also fine because the TEMPLATES
settings in overridden. No changes were required.
I could have used template = Template("Hello")
but I prefer the more explicit version. In addition it's robust to the case where several DjangoTemplates
engines are configured.
This week I focused on the items that were blocking the releasing of Django 1.8 alpha: writing the documentation and making the remaining API changes.
I completed my documentation changes and, thanks to Tim Graham's swift but thorough review, I merged PR 3845. The documentation for Django's development version now includes:
I decided that template backends must raise Django's standard TemplateSyntaxError
when a template fails to compile, exactly like they raise the standard TemplateDoesNotExist
when a template cannot be found. Django doesn't take advantage of this but it could be useful for third-party tools.
I deprecated passing a django.template.Context
to django.template.backends.django.Template
. See the previous update for details on this change.
I updated template responses to account for multiple template engines. That meant relying on the generic, backend-independent template APIs and deprecating direct use of the Django template language's APIs. There's a slightly convoluted deprecation path to change the return type of resolve_template
and resolve_context
because they may be overridden in subclasses.
I considered deprecating resolve_template
and resolve_context
. They don't seem very useful and even less so after my changes. However, I'm afraid that some developers could have subclassed TemplateResponse
or SimpleTemplateResponse
and overridden these methods, in an alternative — and debatable — take on class-based view. Since I'm short on time, I preserved them.
I just finished implementing all these changed. I submitted PR 3883 for review.
I'm on track with the plan I described on the django-developers mailing-list last weekend. As you can see there's still a lot of work left for making this feature nice and smooth in Django 1.8!
I put the finishing touches on PR 3770 and merged it last Sunday. It contained the bulk of the implementation of multiple template engines. After a quick self-congratulation I went back to adding the missing pieces.
The most important one is documentation. You can follow my progress in the multiple-template-engines-docs branch. Writing the documentation revealed a few mistakes; thankfully they aren't serious. The first commits of the branch fix them.
In parallel I worked on deprecating passing a django.template.Context
to django.template.backends.django.Template
— not to be confused with django.template.Template
: the former is a wrapper around the latter. I had allowed it to make the refactoring path smoother and to avoid a hard backwards-incompatibility in the following use case:
from django.template import Context
from django.template.loader import get_template
template = get_template('hello.html')
template.render(Context({'name': 'world'}))
This get_template
call returns a django.template.Template
in Django 1.7 and a django.template.backends.django.Template
in Django 1.8, creating a backwards-incompatibility if django.template.backends.django.Template.render
doesn't accept a django.template.Context
. This change is quite large because:
TemplateResponse.resolve_template
and resolve_context
TemplateResponse.resolve_template
and resolve_context
seem rather useless after these changes but I'm going to keep them in order to minimize disruption. I've started a lot of deprecations and I'm wary of making too many changes at once.
The feature freeze for Django 1.8 is in one week. On the bright side, I landed this project two weeks before the deadline. On the negative side, I still have many side effects of the refactoring to deal with. I'm now priorizing what needs to be done by the alpha, what can wait until the beta, and what will be delayed until Django 1.9. I will most likely not have time to redesign the Origin API and certainly not to refactor internationalization in Django 1.8.
As long as you stick with the Django template system there won't be any loss of functionality in Django 1.8 compared to Django 1.7. However support for other template engines won't be completely up to par until Django 1.9.
I also updated my DEP to account for the new requirements added to DEP 1 a few weeks ago. Per the regular process, Carl Meyer submitted it to the technical board who was kind enough to approve it. Carl proceeded to merge it as DEP 182. It isn't final yet because the internationalization APIs may still change and because it's missing the "Reference Implementation" section.
Last Sunday the multiple-template-engines branch reached a point where it could be reviewed meaningfully and I created PR 3770. During the week I integrated Tim Graham's, Carl Meyer's and Preston Timmons' feedback into that pull request.
Since I rewrite commits to keep a clean history, there's no easy way to see what changed. One change worth mentioning is the new docstring in django.template
. It explains which parts of that package belong to "Multiple Template Engines" and which parts belong to the "Django Template Language".
As soon as I created the pull request, the CI server ran the test suite, revealing two problems with the tests for the Jinja2 backend:
I resolved the first problem by not even attempting to import jinja2
on Python 3.2. I thought catching ImportError
and SyntaxError
would suffice but it didn't work on the CI server (and I didn't try locally). Anyway, explicitly checking the Python version works. It's a bit verbose but it doesn't matter much since it only affects Django's test suite so I didn't spend more time investigating.
The second problem sent me deep into Django's autoescaping framework. I took this opportunity to improve Django's support for the __html__
convention, which provides interoperability between libraries that deal with HTML-escaped data. Since Django 1.7, objects marked as HTML-safe by Django were recognized by other libraries that support this convention. However the other way didn't work. I fixed that.
Then I wrote tests for the support of multiple template engines in django.template.loader
. They can be found in the template_loader
test application.
These tests revealed a bug in the implementation of select_template
. When several template engines are configured and select_template
is called with several template names, it will try every name with the first engine, then every name with the second engine, etc. until a match is found. In my opinion the expected behavior is to try the first name with every engine, then the second name with every engine, etc. The same bug also exists in render_to_string
when it's called with a list of template names. For the time being I marked the two corresponding tests as expected failures.
It seems that fixing this bug will make the select_template
function of template backends useless. Instead of calling select_template
with all template names for each backend, Django will have, for each template name, to call get_template
for each backend. I could make a special case when only one template engine is configured and delegate to the backend's select_template
. That might be slightly more efficient on backends that optimize calls to select_template
, typically with caching. It's a rather theoretical benefit and I'm not convinced it's worth having two code paths. I'm leaning towards dropping select_template
from the backend API in the DEP. What do you think?
This hitch is delaying the pull request but I would still like to merge it by the end of the year.
Well the title says it all. I deprecated ALLOWED_INCLUDE_ROOTS
, TEMPLATE_CONTEXT_PROCESSORS
, TEMPLATE_DIRS
, TEMPLATE_LOADERS
, and TEMPLATE_STRING_IF_INVALID
.
I had already wiped them from Django except for building a backwards-compatible TEMPLATES
during the deprecation period.
As explained two weeks ago I still had to update tests that overrode them. That's a lot of tests. The five commits that deprecate these five settings amount to 138 files changed, 1382 insertions, and 771 deletions. There are many more lines added than removed because overriding TEMPLATES
is more verbose.
Besides I inserted some tests for template backends at the point in the branch where I added the backends.
Now I plan to add a few tests to ensure that django.template.loader
dispatches to multiple template engines correctly. That's only 80 lines of straightforward code, more than half of which only provides backwards-compatibility and will be removed in Django 2.0. I'm not anticipating any problems.
Once this is done I'll try to have someone look at the branch. In my experience reviewing such a large refactoring is almost impossible. Assuming I get positive feedback I'll merge it.
Then, in order to complete this project, I'll still have to:
django.contrib.admindocs
at least doesn't crashI jotted down some more or less related ideas while working on this project:
current_app
argument of the auth viewsrequest.current_app
default to request.resolver_match.namespace
I can't say if they're good. We'll talk when multiple template engines are done.
I solved the problem I was describing last week by reordering commits and by temporarily commenting out the deprecation warning raised when a template engine is initialized based on the legacy TEMPLATE_*
settings.
Then I read the DEP again and checked if I had made all the changes I planned, except for origin handling and internationalization which I'm purposefully leaving aside at this time. There were a few items left, mostly API cleanups and deprecations.
I moved built-in context processors from django.core
to django.template
. I updated the signature of django.shortcuts.render
and django.shortcuts.render_to_response
. I deprecated the current_app
argument in these shortcuts and also in django.template.response.TemplateResponse
.
I'm now going to focus on changes that are absolutely required to merge the multiple-template-engines branch:
TEMPLATE_*
settings — most of the remaining ones are in tests. Replace them with TEMPLATES
or refactor to use an appropriate django.template.engine.Engine
.django.template.engine.Engine.get_default()
. Make changes if necessary. Since the patch is getting large — 146 changed files with 2,336 additions and 1,541 deletions at this time — I'd like to merge it as soon as possible, most likely by the end of the year.
I've documented deprecations with each commit. The DEP provides some background information for users who would like to try the new features. Therefore I think it's acceptable to write the documentation right after the feature is merged.
The current planning gives me about two weeks of headroom before the release of Django 1.8 alpha, according to the current schedule. I'll use this time to figure out a solution to problems I haven't dealt with yet.
This week I had to stay on top of two changes in Django's master branch that interfered with my branch:
I'm still struggling to figure out a refactoring path that allows reasonably small steps and avoids deprecation warnings. As a consequence I haven't made much progress this week. Hopefully I'll be back with better news next week!
I merged the refactor-template-engine-as-library branch last Sunday after addressing Carl Meyer's comments on PR 3605.
Then I rebased the multiple-template-engines branch on top of master. I had put it on hold to refactor the implementation of Django Templates when I was about to add the Django Templates backend. I resumed there.
It seemed easy enough but I hit surprising import errors on Python 2 after creating django/templates/backends/django.py
. I had to add from __future__ import absolute_import
to each submodule of django.templates.backends
to prevent Python 2 from importing django.template.backends.django
instead of django
. Even though I've been using Python for years and I'm quite familiar with the differences between Python 2 and Python 3, it took me a bit of time to realize what was happening. If anyone needed confirmation that Python 3 gets this right, there it is.
At that point, all the template loading APIs in django.template.loader
were still based on a single global instance of the Django template engine, django.template.engine.Engine.get_default()
, configured with the usual settings (TEMPLATE_CONTEXT_PROCESSORS
, TEMPLATE_DIRS
, TEMPLATE_LOADERS
, etc.) I introduced Engine.get_default()
last week so as to make the refactoring incremental and auditable.
I changed Engine.get_default()
to return the instance of the Django template engine if TEMPLATES
defines exactly one and raise an error otherwise. I also changed several uses of the global loaders defined in django.template.loader
that could be replaced by local APIs because the current engine is known at the point of invocation.
Finally I was ready to rewrite the functions in django.template.loader
— get_template
, select_template
, and render_to_string
— to stop calling Engine.get_default()
and account for multiple template engines instead. Since I planned to simplify these functions' signatures, I had to deprecate some arguments at the same time. Otherwise I couldn't support both Django Templates and other backends cleanly.
If you're only using the Django Template Language, the functions in django.template.loader
will still accept the same arguments in Django 1.8 and 1.9 but they'll raise a warning if you're using deprecated arguments. You have until Django 2.0 to upgrade your code. If you're using another template engine, only the new arguments are supported, although I might relax this rule later.
One change I couldn't avoid is the return type of get_template
and select_template
. They used to return a django.template.Template
. Now they return a backend-specific Template
class, depending on the backend that found and loaded the template. For instance, if it's a Django template, get_template
returns a django.template.backends.django.Template
which wraps the underlying django.template.Template
. This matters if you're loading templates with get_template
or select_template
and accessing internals directly — essentially, doing anything other than calling render()
.
There's still one use of Engine.get_default()
I can't get rid of in django.template.Template.__init__()
. Let's look at a very simple example:
>>> from django.template import Template
>>> template = Template("{% extends 'base.html' %}")
In this example it appears that loading base.html
will require properly configured template loaders. More generally a Template
needs an Engine
to be fully functional. This API doesn't provide any way to pass an engine. Since it has existed forever, I can't change it. I have no solution but to use an implicit global engine. It will be provided by Engine.get_default()
according to the rules explained above.
Note how Jinja2's explicit environment avoid this problem:
>>> from jinja2 import Environment, FileSystemLoader
>>> env = Environment(loader=FileSystemLoader(TEMPLATE_DIRS))
>>> template = env.from_string("{% extends 'base.html' %}")
Finally, before someone asks — yes, I'm aware that I haven't written any documentation nor any tests yet, and yes, this stuff is less straightforward that I anticipated.
I merged the cleanup-template-loaders branch last Sunday after Marc Tamlyn and Tim Graham reviewed PR 3555.
In the wake of this refactoring, Preston Timmons documented the locmem template loader. It's a convenient API for defining small templates directly in Python code when writing tests.
Then I resumed working on the refactor-template-engine-as-library branch. I had to backtrack and push changes to master twice to clear the ground for this refactoring:
lru_cache
decorator. That helped because I wanted to refactor module-level functions into methods of the Engine
class and lru_cache
works on methods just like on functions.render_to_response
to minimize nesting of contexts. The complexity of the implementation of Context
combined with the template engine's tendency to silence errors made debugging unpleasant. That change helped.After getting these hindrances out of the way, I could complete the refactoring and I submitted PR 3605 which is now awaiting review. Since it's a refactoring, it doesn't add any new features. It tried to restrain myself when cleaning up code that shows its age. In some cases I added code to keep supporting undocumented behavior which some users were likely to depend on.
Overall that refactoring wasn't easy. I kept bumping into non-obvious test failures and design issues. The crux of theses issues was to pass a reference to the current Engine
instance while preserving this API:
>>> from django.template import Context, Template
>>> template = Template("Hello {{ name }}!")
>>> context = Context({'name': "world"})
>>> template.render(context)
Hello world!
I added an optional keyword argument engine
to the Template
constructor. At this time, when engine
isn't provided, a default engine is instanciated based on the settings you've always been using (TEMPLATE_DIRS
, TEMPLATE_LOADERS
, etc.). When I add support for multiple template engines, the default engine will be the first Django template engine configured in TEMPLATES
. I may choose to raise an exception if there's more than one Django template engine configured in TEMPLATES
.
The good news is that parsing never needs to access engine
; only rendering does. The bad news is that there's no clean way to pass engine
to nodes in the parsed tree. Node
is a public API. Changing its constructor would break every custom template tag.
I settled for passing engine
as an attribute of the context which is available everywhere. I don't know any good reasons for using something other than Django's standard Context
and RequestContext
. Even if someone subclassed them, their code should keep working. I wouldn't describe the implementation as elegant and I expect to regret it but that's the best idea I found.
I could further refactor the template engine but I think the current patch provides a good balance between cleaning the implementation and not making too many changes. Next steps might include:
DEBUG
, DATE_FORMAT
, TIME_FORMAT
, LANGUAGES
, USE_I18N
, USE_L10N
, and USE_TZ
. This could go quite deep into django.utils
with decreasing benefits. At this point, I believe all filters except |date
and |time
and all tags except {% csrf_token %}
and {% ssi ... %}
are usable without configuring Django settings. (This is hard to tell for sure; I would have to follow all code paths.) I deem this good enough.template_dirs
argument in the Loader
API. I don't think it serves any purpose and it certainly doesn't do what a developer expects.django.views.debug
. I'll have to do that anyway to provide debugging hooks when I implement support for multiple template engines.I started working on the implementation this week as planned. In terms of process, I'll rebase my work-in-progress branches on top of master and force-push them regularly. When a branch is ready, I'll make a pull request, take reviews into account, and rebase it just before merging it.
At first I tried to dive in head-first and build the infrastructure for supporting multiple template engines from the ground up. The code is in the multiple-template-engines branch.
I reached the point where I could run:
>>> from django.template import engines
>>> engine = engines['jinja2']
>>> template = engine.get_template("hello.txt")
>>> template.render({name: "world"})
Hello world!
Instant gratification! (Truth be said, that wasn't a lot of work.)
My enthusiasm faded when I started writing the backend for the Django Template Language and realized that it couldn't work until I had refactored the DTL as a standalone library not relying on global settings.
I could have ignored this problem and continued working on that branch until it had proper support for various template engines except Django's. I chose not to pursue this path because Multiple Template Engines (MTE) and the Django Template Language (DTL) both live in the django.template
namespace. As a consequence it's more convenient to rework the implementation of the DTL before moving on with MTE.
I tried removing the DTL's dependency on django.conf.settings
in the refactor-template-engine-as-library branch. That effort quickly hit a wall because of the rather sad state of django.template.base
and django.template.loader
. The implementation of the DTL was less-than-optimal by today's standards in ways that made the refactoring needlessly complex. Should I have done it anyway, it would have been very hard to review.
Up to that point I had avoided digging into the implementation of the DTL. I hoped I could simply work on the periphery. Since it was becoming clear that this strategy wouldn't work, I started a cleanup-template-loaders branch. It moves lots of code out of django.template.loader
, freeing this namespace for the code loading templates from multiple engines, which is good.
That third branch is valuable regardless of the remainder of my project. I created PR 3555 and I believe it's ready for merging. Would someone be kind enough to review it?
Finally, writing code shed a new light on design decisions I made in the abstract. As a consequence I made some minor adjustments to the DEP and I'll most likely keep making more. I also received additional feedback that resulted in further improvements. None of these changes have a significant impact on the general design. They're more about the quality and the consistency of the implementation.
A healthy discussion has been taking place on the django-developers mailing-list since I announced the DEP was ready for review.
Half a dozen people have provided feedback. Considering that it can be hard to have ambitious proposals reviewed, this is excellent. I'm lucky to work with such support from the community!
I'm especially grateful to fellow core developer and technical board member Carl Meyer who wrote a thorough review and suggested almost twenty worthwhile improvements.
Feedback on the proposal has been positive overall. Comments have focused on requiring clarifications and suggesting API improvements.
I made three significant changes as a result:
TEMPLATES
setttingrender
and render_to_response
shortcutsxgettext
or Babel for extracting translatable stringsI also made a few smaller adjustments and clarified many details.
I'm very happy with the result. The DEP looks much better now.
At this point I'm confident that I can start implementing it!
Since I was on holidays this week, I could dedicate two full days to the project, which allowed me to complete the initial version of the DEP.
I listed all template APIs and reviewed all dependencies on django.template
to make sure I hadn't missed something that could get in the way. (That's just as boring as it sounds.) I summarized the results in an appendix.
I realized that I hadn't dealt with template responses and template management commands. I added a paragraph about each of them.
Then I turned to the last major topic left: internationalization. It turned out to be a tough nut to crack because the current design is strongly tied to Django templates. I'll have to refactor makemessages
heavily to provide appropriate extensibility.
Finally I reviewed the entire document. I corrected a few inconsistencies that I had introduced by gradually making up my mind and sometimes changing it. I hope that the current version is reasonably clear and consistent. Let me know if you spot problems.
Now my priorities for the next two weeks will be to improve the DEP according to the feedback I receive and to build consensus.
If I have time, I may start working on the implementation in parallel. At first I thought that it would be easier to refactor the Django template engine into a standalone library before building support for multiple template engines. By now I'm leaning towards implementing the infrastructure for template backends first and then fitting the Django template engine in this frame. It seems easier to start from the high-level design I just defined.
Good news — the DEP is ready for public review!
I consider it to be complete and sufficient even though not every implementation detail is described. With twelve pages of content and as many of appendices, it's already long enough to test the persistence of reviewers :-)
It's complete because I've analyzed every source of relevant information I could find. Since I started working on it, I've been maintaining a list of topics to investigate. It peaked at fifteen items last week and reached zero yesterday.
It's sufficient because I've written down enough information to leave only small decisions for the implementation phase. To the best of my knowledge, I've answered every question that may impact the design significantly.
If you'd like to comment, please join the discussion on the django-developers mailing-list!
This week I worked on the API for multiple templates engines. The API includes:
Engine
class that encapsulates the configuration and logic for loading templatesTemplate
class that represents a template and can render itself with a contextI considered the following requirements, in order of decreasing importance:
Since these requirements aren't entirely compatible with one another, I attempted to pick a reasonable and future-proof compromise. This turned out to be more difficult than I anticipated because of oddities in the design of Django templates.
One example is the existence of the Context
and RequestContext
classes. Most other template engines don't expose a dedicated class for rendering contexts; they accept a simple dict
instead. RequestContext
grew from the requirement to access common values in all templates. That requirement was fulfilled by template context processors, but that API only makes sense when handling HTTP requests.
A second example is the fact that django.shortcuts.render
and django.template.response.TemplateResponse
accept a current_app
argument which is related to URL namespaces. This technique artificially avoids storing the current application in a global variable but sacrifices loose coupling. Unfortunately my work depends on loose coupling between the template engine and other parts of Django.
A third example is the implementation of the debug view. When TEMPLATE_DEBUG
is enabled, Django displays relevant template lines in exception tracebacks. The implementation adds debug information for templates in an origin
attribute and for exceptions in a django_template_source
attribute. Sadly there are few comments about these attributes and the code isn't particularly straightforward.
Coupling is a recurring theme in these example. Django templates know a bit too much about HTTP requests and responses. Conversely, global state such as USE_L10N
or USE_TZ
is less of a concern because it's invisible in APIs. Of course, that's a problem in itself, but not one I have to deal with right now :-)
Otherwise I brought two small improvements to this website. Updates are available as a RSS feed. Code samples in the DEP gained syntax highlighting.
This week I focused on learning as much as I could about Jinja2. I started by reading its documentation cover to cover. As I expected from Armin Ronacher, it's well written, precise and honest. It explains technical choices and tradeoffs in detail. I don't always agree with Armin's choices but his frankness makes it perfectly manageable. It's easy to figure out if I'm missing something or if I'm just working with different hypotheses.
To make sure I didn't miss any important idea discussed in the past, I reviewed more than 50 discussions on django-developers. Interesting ones include:
I also reviewed 40 Trac tickets or wiki pages that mention Jinja2. Here are the relevant ones:
TEMPLATE_DEBUG = True
. They proved to be insufficiently specified. The template engine's internals are too tightly coupled with the debug view. I'll have to clean that up.As far as I can tell, the only serious discussion about switching to Jinja2 that ever took place was Christopher Medrela's GSoC proposal. Interestingly, in that thread, Russell Keith-Magee lays out the plan for the project I'm now tackling:
Work on the internals of Django to decouple the template engine, so that (a), Django's template language is a standalone in the same way that Jinja2 is, and/or (b) there's a clean interface so that it's possible to define a clean Jinja2 module that you can drop into your Django stack.
If anyone was under the impression I came up with innovative ideas, I'm sorry — I'm just implementing smart ideas of other people ;-)
In the same thread, Carl Meyer advocates for a more agressive approach than the one I have in mind:
I would immediately make Jinja2 the "blessed" option (i.e. the one used in the tutorial and featured in the documentation), with DTL maintained (in or out of core) only for backwards-compatibility for legacy projects.
That could be worthwhile. What do you think?
In other news, the Indiegogo campaign is over and I've completed the development of this website. Now that's out of the way, I can focus entirely on the technical aspects of the project.
Finally, I added a few paragraphs to the DEP draft and I still plan to submit it for review on week 3 or 4.
Thanks to your generosity, not only did the campaign reach its stretch goal, but it ended up raising twice the original target amount.
Depending on how the project goes, I will either work on a bonus feature or help managing the 1.8 release.
The credits page is live. If you funded the campaign, you can edit your funder profile.
If you're a silver, gold, platinum or diamond sponser, please follow that link to add extra information (logo, link, quote) on the credits page.
Thanks again!
While the Indiegogo campaign is still in progress, I started working on the project as soon as it was funded.
First, in order to fulfill my campaign's promises, I created the website where you're reading this first update. I'll publish a page to thank sponsors as soon as the campaign is over.
Second, I started working on the DEP. I tidied up notes I made while preparing the campaign. Here's the current status of the document:
The campaign mentioned "guidelines for third-party template engines". After working a bit on the DEP, I'm not sure there will be much content for such a section that isn't already covered in other sections, so I've removed it from the plan for now. I'll add it again if it seems useful.
To write a good technical text, I find it best to jot down a draft, wait a few days, come back with a fresh mind and work on a final version. Next week I'd like to finish the design decisions and draft the implementation plan. I hope to complete the implementation plan on week 3 or 4 and submit the DEP to django-developers then.
The full scope of the project is now funded! Thank you so much everyone!
In the mean time, I've been working on my DEP draft. I'll publish what's ready on Sunday, for my first update.
Funded in two days! Thanks everyone! You're a great community.
This secures the core of the project (steps 1 and 3). I'll start working on the DEP this week-end.
There's a stretch goal at €6000 for improving the architecture of Django templates (step 2). Let's see if we can reach it! :-)
Head over to the project page on Indiegogo to learn more and contribute to the campaign!