I'm implementing a custom permissions application in my Django project, and I'm lost as to how to implement a custom template tag that checks a logged in user's permissions for a specific object instance and shows a piece of HTML based on the outcome of the check.
What I have now is (pseudocode):
{% check_permission request.user "can_edit" on article %}
<form>...</form>
{% endcheck %}
('check_permission' is my custom template tag).
The templatetag takes in the user, the permission and the object instance and returns the enclosed HTML (the form). This currently works fine.
What I would like to do however, is something like:
{% if check_permission request.user "can_edit" on article %}
<form>...</form>
{% else %}
{{ article }}
{% endif %}
I've read about the assignment tag, but my fear is that I would pollute the context variable space with this (meaning I might overwrite previous permission context variables). In other words, as the context variables are being defined on different levels (the view, middleware in my case, and now this assignment template tag), I'm worried about maintainability.
You can use template filters inside if statements. So you could rewrite your tag as a filter:
{% if request.user|check_can_edit:article %}
Note that it's tricky to pass multiple arguments of different types to a filter, so you'll probably want to use one filter per permission, above I've used check_can_edit.
You can definitely do that if you're willing to write some more lines of python code to improve your template readability! :)
You need to parse the tag content yourself, even the parameters it takes and then resolve them, if you want to use variables on them.
The tag implemented below can be used like this:
{% load mytag %}
{% mytag True %}Hi{% else %}Hey{% endmytag %} Bro
Or with a variable:
{% mytag myobject.myflag %}Hi{% else %}Hey{% endmytag %} Bro
So, here's the way I did it:
from django.template import Library, Node, TemplateSyntaxError
register = Library()
#register.tag
def mytag(parser, token):
# Separating the tag name from the "test" parameter.
try:
tag, test = token.contents.split()
except (ValueError, TypeError):
raise TemplateSyntaxError(
"'%s' tag takes two parameters" % tag)
default_states = ['mytag', 'else']
end_tag = 'endmytag'
# Place to store the states and their values
states = {}
# Let's iterate over our context and find our tokens
while token.contents != end_tag:
current = token.contents
states[current.split()[0]] = parser.parse(default_states + [end_tag])
token = parser.next_token()
test_var = parser.compile_filter(test)
return MyNode(states, test_var)
class MyNode(Node):
def __init__(self, states, test_var):
self.states = states
self.test_var = test_var
def render(self, context):
# Resolving variables passed by the user
test_var = self.test_name.resolve(context, True)
# Rendering the right state. You can add a function call, use a
# library or whatever here to decide if the value is true or false.
is_true = bool(test_var)
return self.states[is_true and 'myvar' or 'else'].render(context)
And that's it. HTH.
In Django 2 the assignment tag was replaced by simple_tag() but you could store the custom tag result as a template variable:
# I'm assuming that check_permission receives user and article,
# checks if the user can edit the article and return True or False
{% check_permission user article as permission_cleared %}
{% if permission_cleared %}
<form>...</form>
{% else %}
{{ article }}
{% endif %}
Check the current doc about custom template tags: https://docs.djangoproject.com/en/2.1/howto/custom-template-tags/#simple-tags
inside my_tags.py
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def make_my_variable_true(context):
context['my_variable'] = True
return '' # without this you'll get a "None" in your html
inside my_template.html
{% load my_tags %}
{% make_my_variable_true %}
{% if my_variable %}foo{% endif %}
In this case best solution is to use custom filter. If you don't want write long code for custom tag. Also if you don't want to copy/paste others code.
Here is an example
Inside templatetag
register = template.Library()
def exam_available(user, skill):
skill = get_object_or_404(Skill, id=skill)
return skill.exam_available(user)
register.filter('exam_available', exam_available)
Inside template
{{ request.user|exam:skill.id }}
or
{% if request.user|exam:skill.id %}
Since one of the main common of it is to use request.user or any specific object(id) inside model's custom method, so filtering that individual object or user is the easiest way to make it done. :)
Related
I'm modifying the Wagtail Menus flatmenu template tag to accept a context variable instead of a string for the handle. The use case is that I want to pull in a specific menu based on a slugified version of a user's group name.
The template looks like this:
{% for group in user.groups.all %}
{% with handle=group.name|slugify %}
{% utility_flat_menu handle %}
{% endwith %}
{% endfor %}
The templaet tags looks like:
#register.simple_tag(takes_context=True)
def utility_flat_menu(
context, handle, max_levels=None, show_menu_heading=False,
apply_active_classes=False, allow_repeating_parents=True,
show_multiple_levels=True, template='', sub_menu_template='',
sub_menu_templates=None, fall_back_to_default_site_menus=None,
use_absolute_page_urls=False, add_sub_menus_inline=None, *args,
**kwargs
):
validate_supplied_values('flat_menu', max_levels=max_levels)
if fall_back_to_default_site_menus is None:
fall_back_to_default_site_menus = settings.FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS
if not show_multiple_levels:
max_levels = 1
menu_class = settings.models.FLAT_MENU_MODEL
return menu_class.render_from_tag(
context=context,
handle=context,
fall_back_to_default_site_menus=fall_back_to_default_site_menus,
max_levels=max_levels,
apply_active_classes=apply_active_classes,
allow_repeating_parents=allow_repeating_parents,
use_absolute_page_urls=use_absolute_page_urls,
add_sub_menus_inline=add_sub_menus_inline,
template_name=template,
sub_menu_template_name=sub_menu_template,
sub_menu_template_names=split_if_string(sub_menu_templates),
show_menu_heading=show_menu_heading,
**kwargs
)
That tag is a replacement for teh Wagtail Menus Flat Menu template tag found here: https://github.com/rkhleics/wagtailmenus/blob/1a938576adb801c455bea4b64906fb135c89f65f/wagtailmenus/templatetags/menu_tags.py#L46
So, what I'm trying to do is allow the handle to accept a context variable. I can for the life of me figure out how to get that to work. What you see for the handle = context is my guess at where the change needs to go, but I'm guessing.
Working with Django 1.5.5 I need to call a custom templatetag and somehow store the result in a variable, to check if it contains a non empty empty string. I need something that should look like:
{% load smart_html %}
{% render_html widget.content as widget_content %}
{% if widget_content %}
Do stuff here...
{% endif %}
This is inspired on the {% url %} built-in templatetag that allows calling it storing the result in a variable using the syntax:
{% url 'named_url' as my_named_url %}
My templatetag looks like:
#register.simple_tag(takes_context=True)
def render_html(context, html):
"""Allows executing 'Django code' within the HTML"""
return Template(html).render(context)
I also thought about adding the returned value from the custom templatetag to the context. What do you think about this? Would this be dangerous? This would look like:
#register.simple_tag(takes_context=True)
def render_html(context, html, var_name=None):
"""Allows executing 'Django code' within the HTML"""
html = Template(html).render(context)
if var_name:
context[var_name] = html
html = ''
return html
If the tag is something you control, then perhaps consider using an assignment tag. If the tag isn't something you control, then you might have to wrap it with an assignment tag of your own.
#register.assignment_tag(takes_context=True)
def render_html(context, content):
return Template(content).render(context)
But I don't know what you are trying to achieve? Isn't it better to do this kind of stuff in your view function and based on the result call different templates with TemplateResponse?
Django says there's 3 ways to turn off autoescape:
Use |safe after the variable
Use {% autoescape on %} and {% endautoescape %} within blocks
Use a Context like context = Context({'message': message}, autoescape=False)
(1) and (2) work fine. But I have the situation where I have templates to generate plain-text push notifications, and I have LOADS of templates to build and maintain. I could go through and put the {% autoescape on %} and {% endautoescape %} tags in all of them, but (3) should allow me to do it in one line in the view.
The template:
{% block ios_message %}{{message}}{% endblock %}
The view:
message = u"'&<>"
context = Context({'message': message}, autoescape=False)
render_block_to_string(template_name, 'ios_message', context)
The output:
u''&<>
The code for block_render.py is from here: https://github.com/uniphil/Django-Block-Render/blob/master/block_render.py. I'm using it as is from there.
Anyone know what gives?
Take a closer look to function render_block_to_string():
def render_block_to_string(template_name, block, dictionary=None,
context_instance=None):
"""Return a string
Loads the given template_name and renders the given block with the
given dictionary as context.
"""
dictionary = dictionary or {}
t = _get_template(template_name)
if context_instance:
context_instance.update(dictionary)
else:
context_instance = Context(dictionary)
return render_template_block(t, block, context_instance)
The 3rd arg should be a dict, not context. Otherwise it would use the normal context instance.
So I believe it should be:
render_block_to_string(template_name, 'ios_message', {}, context)
Hope it helps.
I could solve it my doing it like that:
from django.template.context import make_context
from django.template.loader import get_template
# Getting the template either by a path or creating the Template object yourself
template = get_template('your/path/to/the/template.html')
# Note here the 'template.template' and the 'autoescape=False' parameter
subject = template.template.render(make_context(context, autoescape=False))
Found it by doing that myself. Because by default the autoescape setting will be used from the engine
https://github.com/django/django/blob/4b6dfe16226a81fea464ac5f77942f4d6ba266e8/django/template/backends/django.py#L58-L63
Django Version: 2.2.1
Wanted to comment but looks like I don't have enough reputation since this is a newish account
You can turn off the autoescape found here: https://github.com/django/django/blob/529c3f264d99fff0129cb6afbe4be2eb11d8a501/django/template/context.py#L137
i.e.
Context(data_dict, autoescape=False)
Good Afternoon,
How can I use a variable variable name in Django templates?
I have a custom auth system using context, has_perm checks to see if the user has access to the specified section.
deptauth is a variable with a restriction group name i.e SectionAdmin. I think has.perm is actually checking for 'deptauth' instead of the variable value SectionAdmin as I would like.
{%if has_perm.deptauth %}
How can I do that? has_perm.{{depauth}} or something along those lines?
EDIT - Updated code
{% with arg_value="authval" %}
{% lookup has_perm "admintest" %}
{% endwith %}
{%if has_perm.authval %}
window.location = './portal/tickets/admin/add/{{dept}}/'+val;
{% else %}
window.location = './portal/tickets/add/{{dept}}/'+val;
{%endif%}
has_perm isn't an object.. it's in my context processor (permchecker):
class permchecker(object):
def __init__(self, request):
self.request = request
pass
def __getitem__(self, perm_name):
return check_perm(self.request, perm_name)
You're best off writing your own custom template tag for that. It's not difficult to do, and normal for this kind of situation.
I have not tested this, but something along these lines should work. Remember to handle errors properly!
def lookup(object, property):
return getattr(object, property)()
register.simple_tag(lookup)
If you're trying to get a property rather than execute a method, remove those ().
and use it:
{% lookup has_perm "depauth" %}
Note that has_perm is a variable, and "depauth" is a string value. this will pass the string for lookup, i.e. get has_perm.depauth.
You can call it with a variable:
{% with arg_value="depauth_other_value" %}
{% lookup has_perm arg_value %}
{% endwith %}
which means that the value of the variable will be used to look it up, i.e. has_perm.depauth_other_value'.
You can try like this,
{{ dict|key:key_name }}
Filter:
def key(d, key_name):
return d[key_name]
key = register.filter('key', key)
More information, django ticket
Here are my models:
class Activity(models.Model):
title = models.CharField(blank=False, max_length=100)
description = models.TextField(blank=False)
class UserActivityWork(models.Model):
activity = models.ForeignKey(Activity)
user = models.ForeignKey(User)
hours_worked = models.FloatField()
comment = models.TextField()
Example data would be, an Activity of "climbing Mt Everest" and each user would be able to input how long it took them and a comment.
Here's my question: How can I display a list of all the Activities, and if the user has entered data for that Activity, display the pertinent details next to the Activity?
So far, I have considered:
creating a dictionary of
UserActivityWork with a key of the Activity id and a value of the user's UserActivityWork. This would be fine with
me, but I have no idea of how to do
this in django's templating system (ie, how do you say: {{ user_work[activity.id] }})
creating an object that would hold
both the Activity and
UserActivityWork. I haven't done this
one, because I am hoping that django
has a better way to do this.
Any insight would be greatly appreciated!
Assuming you have 2 querysets accessable from within your template (say as activities and user_activities)
A naive way would be to iterate over each activity and then over each user activity.
{% for activity in activities %}
{{ activity.title }}
{% for user_activity in user_activities %}
{% ifequal user_activity.activity activity %}
Display userdata
{% endifequal %}
{% endfor %}
{% endfor %}
Dictionary lookups can be performed in templates by using a dot (.)
Technically, when the template system encounters a dot, it tries the following lookups, in this order:
Dictionary lookup
Attribute lookup
Method call
List-index lookup
Another option would be to create a custom template tag. You could loop over the activity list as before and then pass the activity and either the user_activity list or the user to the tag to perform the lookup and render the required data.
Thanks for the hint, Gerry. I found that writing a custom template tag as you suggested was the way to go.
Here are the gory details, in case anyone stumbles across this.
In the view method, I published a dictionary "user_activity_status" which contains a key of activity.id and value of UserActivityWork object for the logged in user's work on that activity
This is the the relevant section of the template. Basically this going to add a variable "map_value" with a value of
getattr(user_activity_status[activity.id], "comment")
Here's the template:
{% load *file-name-of-the-templatetag-file* %}
{% access_map_method user_activity_status activity.id comment %}
{% if map_value %}
{{ map_value }}
{% else %}
get working sucka!
{% endif %}
here is the section of the templatetag file (see Gerry's links for the details of how to set this up)
from django import template
register = template.Library()
#register.tag(name="access_map_method")
def do_access_map_method(parser, token):
try:
tag_name, dict_name , key_name, method_name = token.contents.split()
except ValueError:
msg = '%r tag requires three arguments' % token.contents[0]
raise template.TemplateSyntaxError(msg)
return MapNode(dict_name , key_name, method_name)
class MapNode(template.Node):
def __init__(self, dict_name, key_name, method_name):
self.dict_var = template.Variable(dict_name)
self.key_var = template.Variable(key_name)
self.method_name = method_name
def render(self, context):
try:
dict_obj = self.dict_var.resolve(context)
key_obj = self.key_var.resolve(context)
if key_obj in dict_obj.keys():
if self.method_name:
context['map_value'] = getattr(dict_obj[key_obj], self.method_name)
else:
context['map_value'] = dict_obj[key_obj]
else:
context['map_value'] = ''
except template.VariableDoesNotExist:
context['map_value'] = ''
return ''