2👍
The Django template starts by looking for a key in the Context
object (which is a kind of dict). If the key is not there, KeyError
is raised which is caught by the template engine.. and normally, the key would be rendered as blank.
So it seems to me that if you want to bypass this behavior you need to have a missing key raise something other than KeyError. Or, you can catch KeyError
before the template engine does, and save that information before re-raising.
You could possibly do this by subclassing Context
, this would take a little researching of the template engine code.. but could be a really great way to do it. But you could also just wrap your API in a special class that does this.. and put it in the context. The following code HAS NOT BEEN TESTED.. think of it as pseudocode which you could possibly use as a starting point…
# our own KeyError class that won't be caught by django
class APIKeyError(Exception):
pass
class API(object):
def __init__(self, context, exc_class=APIKeyError):
self.context = context
self.exc_class = exc_class
self.invalid_keys = set()
self.used_keys = set()
@property
def unused_keys(self):
return set(self.context.keys()) - self.used_keys
def __getattr__(self, name):
try:
value = self.context.get(name)
self.used_keys.add(name)
return value
except KeyError:
self.invalid_keys.add(name)
if self.exc_class is None:
raise
raise self.exc_class(
'API key "%s" is not valid. Available keys are %s.' %
(name, self.context.keys()))
So then you would check your template something like this:
from django.template.loader import render_to_string
api = API({
'foo': 'foovalue',
'bar': 'barvalue',
'baz': 'bazvalue',
}, None)
render_to_string('template.html', {'api': api }, request=request)
# now the API class instance knows something about the keys (only within
# the 'api' namespace that the template used...
print api.used_keys # set of keys that the template accessed that were valid
print api.invalid_keys # set of keys the template accessed that were invalid
print api.unused_keys # set of keys valid keys that were not used
Now, note that if you don’t want to do any checks at the end but only have an exception thrown if the user uses an invalid key, just don’t pass None
as exc_class
and it’ll throw an APIKeyError
when there’s a bad api.key in the template.
So, hopefully this’ll give you some ideas and a starting point for some code.. as I said this has not been tested at all.. think of it as pseudocode.
Of course, this only protects the keys that are within API.. any key that does not start with api
would behave as normal for Django. But the advantage here is that you’re working just with your own code and not as exposed to breakage from changes to future versions of Django.
1👍
It does IPO (input-processing-output):
I have the same thinking when started to learn Django. Below are my first lines of every template at the time. The initial purpose is to display input values when I want to debug the template, by removing the {% comment %}
in the first line.
{# This comment is to let vim know this is django html template. #}{% comment %}
Purpose: Generate inline add form for intercoolerjs to update single page view.
Input: show_add_form: {{ show_add_form }}, list_path: {{ list_path }}, detail_id: {{ detail_id }}{% if show_add_form %}, default_desc: {{ default_desc }}{% else %}{% endif %}
{% comment %}{% endcomment %}
So for defining API, I think we can start with something like this template header. Your code can then look for doc_string and ValueError in generated template.
{% comment %}
doc_string: You can use these variables: {{foo}}, {{bar}}, {{blue}} (default is yellow) ..
{% comment %}{% endcomment %}
{% load my_tags %}
{% required foo 'foo_needs_to_get_rendered' %}
{% required bar 'bar_needs_to_get_rendered' %}
{% set_with_default blue 'yellow' as blue %}
The set_with_default
and required
are custom tags in <your app>/templatetags/my_tags.py
per document.
from django import template
register = template.Library()
@register.simple_tag
def set_with_default(value, default=''):
if value:
pass
else:
return default
return value
@register.simple_tag
def required(value, message):
if value:
pass
else:
return 'ValueError: "%s"' % message
return ''