Sunday, January 15, 2017

Django - 025 - Method Call Behavior

Method calls are slightly more complex than the other lookup types. Here are some things to keep in mind:

If, during the method lookup, a method raises an exception, the exception will be propagated, unless the exception has an attribute silent_variable_failure whose value is True. If the exception does have a silent_variable_failure attribute, the variable will render as the value of the engine's string_if_invalid configuration option (an empty string by default). For example:

$ python manage.py shell -i python
Python 2.7.10 |Anaconda 2.3.0 (64-bit)| (default, May 28 2015, 17:02:03) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.template import Template,Context
>>> t = Template("My name is {{ person.first_name  }}.")
>>> class PersonClass3:
...    def first_name(self):
...       raise AssertionError("foo")
... 
>>> p = PersonClass3()
>>> t.render(Context({"person":p}))
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 208, in render
    return self._render(context)
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 994, in render
    bit = node.render_annotated(context)
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 961, in render_annotated
    return self.render(context)
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 1044, in render
    output = self.filter_expression.resolve(context)
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 711, in resolve
    obj = self.var.resolve(context)
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 852, in resolve
    value = self._resolve_lookup(context)
  File "/opt/anaconda/lib/python2.7/site-packages/django/template/base.py", line 915, in _resolve_lookup
    current = current()
  File "<console>", line 3, in first_name
AssertionError: foo
>>> class SilentAssertionError(Exception):
...    silent_variable_failure = True
... 
>>> class PersonClass4:
...   def first_name(self):
...       raise SilentAssertionError
... 
>>> p = PersonClass4()
>>> t.render(Context({"person":p}))
u'My name is .'
>>> 
  • A method call will only work if the method has no required arguments. Otherwise, the system will move to the next lookup type (list-index lookup).
  • By design, Django intentionally limits the amount of logic processing available in the template, so it is not possible to pass arguments to method calls accessed from within templates. Data should be calculated in views and then passed to templates for display.
  • Obviously, some methods have side-effects, and it would be foolish at best, and possibly even a security hole, to allow the template system to access them.
  • Say, for instance, you have a BankAccount object that has a delete() method. If a template includes something like {{ account.delete }}, where account is BankAccount object, the object would be deleted when the template is rendered! To prevent this, set the function attribute alters_data on the method:
    >>> def data(self):
    ...   # Delete the account
    ...   delete.alters_data = True
    ...
    >>>
  • The Template system won't execute any method marked in this way. Continuing the preceding example, if a template includes {{ account.delete }} and the delete method has the alters_data = True, then the delete() method will not be executed when the template is rendered, the engine will instead replace the variable with string_if_invalid.
  • NOTE: The dynamically-generated delete() and save() methods on Django model objects get alters_data=true set automatically.


No comments:

Post a Comment