Show undefined variable errors in Django templates? - django

How can I ask Django to tell me when it encounters, for example, an undefined variable error while it's rendering templates?
I've tried the obvious DEBUG = True and TEMPLATE_DEBUG = True, but they don't help.

Put this in your debug settings:
class InvalidString(str):
def __mod__(self, other):
from django.template.base import TemplateSyntaxError
raise TemplateSyntaxError(
"Undefined variable or unknown value for: \"%s\"" % other)
TEMPLATE_STRING_IF_INVALID = InvalidString("%s")
This should raise an error when the template engine sees or finds an undefined value.

According to the django documentation,
undefined variables are treated as ''(empty string) by default. While in if for regroup, it's None.
If you are going to identify the variable undefined, change TEMPLATE_STRING_IF_INVALID in settings.
'%s' makes the invalid variable to be rendered as its variable name, in this way, u can identify easily.
how-invalid-variables-are-handled

Finding template variables that didn't exist in the context was important to me as several times bugs made it into production because views had changed but templates had not.
I used this technique, implemented in manage.py, to achieve the effect of breaking tests when template variables not found in the context were used. Note that this technique works with for loops and if statements and not just {{ variables }}.
import sys
# sometimes it's OK if a variable is undefined:
allowed_undefined_variables = [
'variable_1',
'variable_2',
]
if 'test' in sys.argv:
import django.template.base as template_base
old_resolve = template_base.Variable.resolve
def new_resolve(self, context):
try:
value = old_resolve(self, context)
except template_base.VariableDoesNotExist as e:
# if it's not a variable that's allowed to not exist then raise a
# base Exception so Nodes can't catch it (which will make the test
# fail)
if self.var not in allowed_undefined_variables:
raise Exception(e)
# re-raise the original and let the individual Nodes deal with it
# however they'd like
raise e
return value
template_base.Variable.resolve = new_resolve

How to log a warning on undefined variable in a template
It seems that Django relies on undefined variables being a simple empty string. So instead of changing this behaviour or making it throw an exception, let's keep it the same but have it log a warning instead!
In your settings.py file:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# ...
'OPTIONS': {
# ...
'string_if_invalid': InvalidStringShowWarning("%s"),
},
}
]
(string_if_invalid replaces TEMPLATE_STRING_IF_INVALID in newer Django versions.)
And further up, you'll need to define the InvalidStringShowWarning class, making it behave while logging a warning:
class InvalidStringShowWarning(str):
def __mod__(self, other):
import logging
logger = logging.getLogger(__name__)
logger.warning("In template, undefined variable or unknown value for: '%s'" % (other,))
return ""
def __bool__(self): # if using Python 2, use __nonzero__ instead
# make the template tag `default` use its fallback value
return False
You should be able to see the warning in the output of python manage.py runserver.

I believe that's a major oversight on Django's part and the primary reason I prefer not to use their default template engine. The sad truth is that, at least for now (Django 1.9), you can't achieve this effect reliably.
You can make Django raise an exception when {{ undefined_variable }} is encountered - by using "the hack" described in slacy's answer.
You can't make Django raise the same exception on {% if undefined_variable %} or {% for x in undefined_variable %} etc. "The hack" doesn't work in such cases.
Even in cases in which you can, it is strongly discouraged by Django authors to use this technique in production environment. Unless you're sure you don't use Django's built-in templates in your app, you should use "the hack" only in DEBUG mode.
However, if you're stuck with Django's templates for now, I would recommend to use slacy's answer, just make sure you're in DEBUG mode.

Read up on how invalid variable are handled in templates. Basically, just set TEMPLATE_STRING_IF_INVALID to something in your settings.py.
TEMPLATE_STRING_IF_INVALID = "He's dead Jim! [%s]"

I am use next:
import logging
from django.utils.html import format_html
from django.utils.safestring import mark_safe
class InvalidTemplateVariable(str):
"""
Class for override output that the Django template system
determinated as invalid (e.g. misspelled) variables.
"""
# styles for display message in HTML`s pages
styles = mark_safe('style="color: red; font-weight: bold;"')
def __mod__(self, variable):
"""Overide a standart output here."""
# access to current settings
from django.conf import settings
# display the message on page in make log it only on stage development
if settings.DEBUG is True:
# format message with captured variable
msg = 'Attention! A variable "{}" does not exists.'.format(variable)
# get logger and make
logger = self.get_logger()
logger.warning(msg)
# mark text as non-escaped in HTML
return format_html('<i {}>{}</i>', self.styles, msg)
# on production it will be not displayed
return ''
def get_logger(self):
"""Create own logger with advanced error`s details."""
logger = logging.getLogger(self.__class__.__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
Usage in settings file (by default it settings.py):
TEMPLATES = [
{
......
'OPTIONS': {
.....................
'string_if_invalid': InvalidTemplateVariable('%s'),
.....................
},
},
]
or directly
TEMPLATES[0]['OPTIONS']['string_if_invalid'] = InvalidTemplateVariable('%s')
A result if DEBUG = True:
On page
In console
> System check identified 1 issue (0 silenced). October 03, 2016 -
> 12:21:40 Django version 1.10.1, using settings 'settings.development'
> Starting development server at http://127.0.0.1:8000/ Quit the server
> with CONTROL-C. 2016-10-03 12:21:44,472 - InvalidTemplateVariable -
> WARNING - Attention! A variable "form.media" does not exists.

You can use the pytest-django setting FAIL_INVALID_TEMPLATE_VARS
The invaild vars get checked if pytest executes the code.
[pytest]
DJANGO_SETTINGS_MODULE = mysite.settings
FAIL_INVALID_TEMPLATE_VARS = True

If there is a undefined variable in templates, django won't tell you.
You can print this variable in view.

Related

Persian text in url Django

I have some links that include Persian texts, such as:
http://sample.com/fields/طب%20نظامی
And in the view function I want to access to Persian part, so:
url = request.path_info
key = re.findall('/fields/(.+)', url)[0]
But I get the following error:
IndexError at /fields/
list index out of range
Actually, the problem is with the index zero because it can not see anything there! It should be noted that it is a Django project on IIS Server and I have successfully tested it with other servers and the local server. I think it has some thing related to IIS. Moreover I have tried to slugify the url without success. I can encode urls successfully, but I think it is not the actual answer to this question.
Based on the comments:
I checked the request.path too and the same problem. It contains:
/fields/
I implemented a sample django project in local server and here is my views:
def test(request):
t = request.path
return HttpResponse(t)
The results:
http://127.0.0.1:8000/تست/
/تست/
Without any problem.
Based on the #sytech comment, I have created a middlware.py in my app directory:
from django.core.handlers.wsgi import WSGIHandler
class SimpleMiddleware(WSGIHandler):
def __call__(self, environ, start_response):
print(environ['UNENCODED_URL'])
return super().__call__(environ, start_response)
and in settings.py:
MIDDLEWARE = [
...
'apps.middleware.SimpleMiddleware',
]
But I am getting the following error:
__call__() missing 1 required positional argument: 'start_response'
Assuming you don't have another problem in your rewrite configuration, on IIS, depending on your rewrite configuration, you may need to access this through the UNENCODED_URL variable which will contain the unencoded value.
This can be demonstrated in a simple WSGI middleware:
from django.core.handlers.wsgi import WSGIHandler
class MyHandler(WSGIHandler):
def __call__(self, environ, start_response):
print(environ['UNENCODED_URL'])
return super().__call__(environ, start_response)
You would see the unencoded URL and the path part that's in Persian would be passed %D8%B7%D8%A8%2520%D9%86%D8%B8%D8%A7%D9%85%DB%8C. Which you can then decode with urllib.parse.unquote
urllib.parse.unquote('%D8%B7%D8%A8%2520%D9%86%D8%B8%D8%A7%D9%85%DB%8C')
# طب%20نظامی
If you wanted, you could use a middleware to set this as an attribute on the request object or even override the request.path_info.
You must be using URL rewrite v7.1.1980 or higher for this to work.
You could also use the UNENCODED_URL directly in the rewrite rule, but that may result in headaches with routing.
I can encode urls successfully, but I think it is not the actual answer to this question.
Yeah, that is another option, but may result in other issues like this: IIS10 URL Rewrite 2.1 double encoding issue
You can do this by using python split() method
url = "http://sample.com/fields/طب%20نظامی"
url_key = url.split(sep="/", maxsplit=4)
url_key[-1]
output : 'طب%20نظامی'
in this url is splited by / which occurs 4 time in string so it will return a list like this
['http:', '', 'sample.com', 'fields', 'طب%20نظامی']
then extract result like this url_key[-1] from url_key
you can Split the URL by :
string = http://sample.com/fields/طب%20نظامی
last_part = string. Split("/")[-1]
print(last_part)
output :< طب%20نظامی >
slugify(last_part)
or
slugify(last_part, allow_unicode=True)
I guess This Will Help You :)

Ignore missing variables in Django templates

How can I make Django (1.11) ignore missing variables in template during rendering? I need to render the same template with different data in multiple steps. I need to use the Django template engine for all the features that it includes and I can't modify the templates.
Instead of replacing them with an empty string:
>>> from django.template import Template, Context, TemplateSyntaxError
>>> c = Context({'foo': 'hello'})
>>> t = Template('{{foo}} {{bar}}')
>>> t.render(c)
'hello '
I would like it to just leave them as is
>>> t.render(c)
'hello {{bar}}'
I think string_if_invalid will work for you: https://docs.djangoproject.com/en/1.11/ref/templates/api/#how-invalid-variables-are-handled
Your settings should add something like this:
TEMPLATES = [
{
...
'OPTIONS': {
...
'string_if_invalid': '{{%s}}',
...
},
},
]
You may need to escape the curly braces, but I would be surprised if it didn't do that for you when replacing the string in.
Note that from the docs say:
If string_if_invalid contains a '%s', the format marker will be replaced with the name of the invalid variable.
ALSO note that the docs say:
For debug purposes only!
While string_if_invalid can be a useful debugging tool, it is a bad idea to turn it on as a ‘development default’.
Many templates, including those in the Admin site, rely upon the silence of the template system when a non-existent variable is encountered. If you assign a value other than '' to string_if_invalid, you will experience rendering problems with these templates and sites.
Generally, string_if_invalid should only be enabled in order to debug a specific template problem, then cleared once debugging is complete.
EDIT: The doc's warning is making me a bit wary about using this. Give the above a try for debugging, but I wouldn't rely on it for a production system.
You might want to look into writing a custom template tag: https://docs.djangoproject.com/en/2.1/howto/custom-template-tags/#django.template.Library.simple_tag
It would be defined like this:
#register.simple_tag(takes_context=True)
def preserve_invalid(context, var_name):
return context.get(var_name, '{{%s}}' % var_name)
and used like this:
{% preserve_invalid "some_var" %}

How to fall back to multiple languages in Django at runtime?

I am building a Django app that uses Django's translation features to provide localization to multiple languages. But I am also using Django's translation features to translate certain terminology into different industries based on the currently logged in user's settings.
For example, for an English speaking user working in the learning assessment industry, I want the following behavior:
For a given request to a page:
Look up the user's natural language (e.g., German)
Look up the user's industry (e.g., learning assessment)
Activate the German/Learning Assessment language (e.g., translation.activate("learning-assessment-de")
The "learning-assessment-de" .po file will only translate a subset of all the strings in the project, because it's only there to translate certain industry-specific terminology.
This is the question:
When a string is missing, I want Django to fall back to German (determined in step #1 above) rather than English (the default language in my settings.py).
My default English/German .po files will assume a certain industry.
Is this possible?
I think it's possible and one of the fastest ways to do this (even if to test if it works) would be to monkey-patch Django translation module to add fallback language support like this (not tested):
from django.utils.translation import trans_real
... # Import other things
# Added `fallback_language` parameter
def do_translate(message, translation_function, fallback_language=None):
"""
Translates 'message' using the given 'translation_function' name -- which
will be either gettext or ugettext. It uses the current thread to find the
translation object to use. If no current translation is activated, the
message will be run through the default translation object.
"""
global _default
# str() is allowing a bytestring message to remain bytestring on Python 2
eol_message = message.replace(str('\r\n'), str('\n')).replace(str('\r'), str('\n'))
t = getattr(_active, "value", None)
if t is not None:
result = getattr(t, translation_function)(eol_message)
else:
# Use other language as fallback.
if fallback_language is not None:
fallback = translation(fallback_language)
result = getattr(_default, translation_function)(eol_message)
else:
if _default is None:
from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
result = getattr(_default, translation_function)(eol_message)
if isinstance(message, SafeData):
return mark_safe(result)
return result
# Added `fallback_language` parameter
def do_ntranslate(singular, plural, number, translation_function, fallback_language=None):
global _default
t = getattr(_active, "value", None)
if t is not None:
return getattr(t, translation_function)(singular, plural, number)
# Use other language as fallback.
if fallback_language is not None:
fallback = translation(fallback_language)
return getattr(fallback, translation_function)(singular, plural, number)
if _default is None:
from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
return getattr(_default, translation_function)(singular, plural, number)
# Override Django functions with custom ones.
trans_real.do_translate = do_translate
trans_real.do_ntranslate = do_ntranslate
The two above functions are taken from the django.utils.translation.trans_real module. Just added few lines to these functions. You will also need to modify the other functions (e.g. gettext, ngettext, pgettext) to accept the fallback_language parameter and pass it to the two above ones. Please try if this code works.
Note, that monkey-patching is project-wide - it affects Your whole application and third-party applications too. Alternatively You may take the source of the django.utils.translation.trans_real module as a base to create custom translation functions that will be used only in few places in your application.

Make django templates strict

In a django template, a call to {{ var }} will silently fail if var is undefined. That makes templates hard to debug. Is there a setting I can switch so django will throw an exception in this case?
The only hint at a solution I've found online is http://groups.google.com/group/google-appengine/browse_thread/thread/86a5b12ff868038d and that sounds awfully hacky.
Django<=1.9
Set TEMPLATE_STRING_IF_INVALID = 'DEBUG WARNING: undefined template variable [%s] not found' in your settings.py.
See docs:
https://docs.djangoproject.com/en/1.9/ref/settings/#template-string-if-invalid
Django>=1.10
Set string_if_invalid = 'DEBUG WARNING: undefined template variable [%s] not found' template option in your settings.py.
See docs: https://docs.djangoproject.com/en/2.0/topics/templates/#module-django.template.backends.django
Also read:
http://docs.djangoproject.com/en/dev/ref/templates/api/#invalid-template-variables
This hack from djangosnippets will raise an exception when an undefined variable is encountered in a template.
# settings.py
class InvalidVarException(object):
def __mod__(self, missing):
try:
missing_str = unicode(missing)
except:
missing_str = 'Failed to create string representation'
raise Exception('Unknown template variable %r %s' % (missing, missing_str))
def __contains__(self, search):
if search == '%s':
return True
return False
TEMPLATE_DEBUG = True
TEMPLATE_STRING_IF_INVALID = InvalidVarException()
Consider using the django-shouty-templates app: https://pypi.org/project/django-shouty-templates/
This app applies a monkeypatch which forces Django’s template language to error far more loudly about invalid assumptions. Specifically:
chef would raise an exception if the variable were called sous_chef.
chef.can_add_cakes would raise an exception if can_add_cakes was not a valid attribute/property/method of chef
It ain’t compile time safety, but it’s better than silently swallowing errors because you forgot something!
I use this pytest-django config:
[pytest]
FAIL_INVALID_TEMPLATE_VARS = True
This way I get an exception if I run the tests.
That's part of the design. It allows you to provide defaults and switch based on whether or not a variable exists in the context. It also allows templates to be very flexible and promotes re-usability of templates instead of a strict "each view must have it's own template" approach.
More to the point, templates are not really supposed to be "debugged". The idea is to put as much of your logic as possible outside the template, in the views or models. If you want to figure out why a variable that's supposed to be passed to the context isn't, the place to debug that is in your view. Just drop import pdb;pdb.set_trace() somewhere before your view returns and poke around.

Django: Use of DATE_FORMAT, DATETIME_FORMAT, TIME_FORMAT in settings.py?

I would like to globally (through my entire site, admin and front-end) adjust the way dates and time are displayed to my likings, but I cannot figure out what is going on with the DATE_FORMAT, DATETIME_FORMAT and TIME_FORMAT variables in settings.py.
In this question it says that the settings are ignored. The question is over a year old though. In the Django documentation it says they can be used when you have USE_L10N = True and apparently something changed in Django 1.2. According to this however there might be a bug.
I am currently using Django 1.2 and when I have USE_L10N = True it just ignores the date(time) format in settings.py. When I have USE_L10N = False it also seems to ignore them.
Is there a way to globally customize the date and time display? Or should I create my own custom formats file as Karen suggests in the Django Users Google Group post?
Had same problem, solution is simple and documented. Whenever you render a date, you need to specify you want the template to render it as a date/time/short_date/datetime (e.g., {{ some_date_var | date }} and then it will render it as specified with DATE_FORMAT in your settings.py
Example:
>>> from django.conf import settings # imported to show my variables in settings.py
>>> settings.DATE_FORMAT # - showing my values; I modified this value
'm/d/Y'
>>> settings.TIME_FORMAT
'P'
>>> settings.DATETIME_FORMAT
'N j, Y, P'
>>> from django.template import Template, Context
>>> from datetime import datetime
>>> c = Context(dict(moon = datetime(1969, 7, 20, 20, 17, 39))) # Create context with datetime to render in a template
>>> print c['moon'] # This is the default format of a printing datetime object
1969-07-20 20:17:39
>>> print Template("default formatting : {{ moon }}\n"
"use DATE_FORMAT : {{ moon|date }}\n"
"use TIME_FORMAT : {{ moon|time }}\n"
"use DATETIME_FORMAT: {{ moon|date:'DATETIME_FORMAT' }}\n"
"use SHORT_DATETIME_FORMAT: {{ moon|date:'SHORT_DATETIME_FORMAT' }}"
).render(c)
default formatting : 1969-07-20 20:17:39
use DATE_FORMAT : 07/20/1969
use TIME_FORMAT : 8:17 p.m.
use DATETIME_FORMAT: July 20, 1969, 8:17 p.m.
use SHORT_DATETIME_FORMAT: 07/20/1969 8:17 p.m.
This makes sense; e.g., the template needs to know whether it should use the DATE_FORMAT or the SHORT_DATE_FORMAT or whatever.
Searching through the source shows that DATETIME_FORMAT, etc., are only used when django.utils.formats.localize() is called, and that only seems to be called when django.template.VariableNodes are rendered.
I'm not sure when exactly VariableNodes are used in template rendering, but I would guess that if you have settings.USE_L10N turned on and you have a VariableNode, it will be localized.
localize looks like this:
def localize(value):
"""
Checks if value is a localizable type (date, number...) and returns it
formatted as a string using current locale format
"""
if settings.USE_L10N:
if isinstance(value, (decimal.Decimal, float, int)):
return number_format(value)
elif isinstance(value, datetime.datetime):
return date_format(value, 'DATETIME_FORMAT')
elif isinstance(value, datetime.date):
return date_format(value)
elif isinstance(value, datetime.time):
return time_format(value, 'TIME_FORMAT')
return value
To answer your question, I'd probably write a quick context processor that called localize() on everything in the context.
You can override DATE_FORMAT, DATETIME_FORMAT, TIME_FORMAT and other date/time formats when USE_L10N = True by creating custom format files as described in Django documentation.
In summary:
Set FORMAT_MODULE_PATH = 'yourproject.formats' in settings.py
Create directory structure yourproject/formats/en (replacing en with the corresponding ISO 639-1 locale code if you are using other locale than English) and add __init__.py files to all directories to make it a valid Python module
Add formats.py to the leaf directory, containing the format definitions you want to override, e.g. DATE_FORMAT = 'j. F Y'.
Example from an actual project here.
A late response, but hopefully this will help anyone else searching for this.
By setting USE_L10N = True in your settings, Django looks for locale specific formats, giving them precedence over non-locale related settings.
The solution: (to display 30/12/2017 on a DateField)
from django.conf.locale.en import formats as en_formats
en_formats.DATE_FORMAT = "%d/%m/%Y"
and for inputs (to accept 30/12/2017 or 30-12-2017)
en_formats.DATE_INPUT_FORMATS = ['%d/%m/%Y', '%d-%m-%Y']
Reference: https://mounirmesselmeni.github.io/2014/11/06/date-format-in-django-admin/
*tested on Django==1.10.7