Establish templatetag as django variable - django

I am attempting to filter a django queryset in a template tag, like this:
#register.simple_tag
def splice(query, person_id):
query2 = query.filter(personid=person_id)
return query2
Then, in my template, I would like to pass the newly filtered queryset into an includes html file. Here is my attempt:
{% with splice df person_id as x %}
{% include 'includes/file.html' with df=x %}
How can I execute this properly? Or does anyone have ideas how to go about this a more efficient way?

You don't need with there; a simple tag can add its data to the context directly with as.
{% splice df person_id as x %}
However, this is probably not the right approach. Instead of writing a template tag to add context for an included template, you should be using an inclusion tag, which takes care of the whole process of including the template with specific context. So:
#register.inclusion_tag('template/file.html')
def splice_include(query, person_id):
query2 = query.filter(personid=person_id)
return {'df': x}
And now you can use it directly:
{% splice_include df person_id %}
with no need for a separate include at all.

You need to rearrange how you pass the arguments. Using the Django docs provides a nice example. You can then call the templatetag from file.html.
The calling file
{% include 'includes/file.html' with df=df person_id=person_id %}
file.html
{% load my_template_tags %}
{{df|slice:person_id}}

Related

Make a change to a string in django template?

I have a queryset in Django that contains a string field. This is a filename, something like images/photo.jpg or images/photo.20.19.22.jpg. I need to rewrite them in one particular view so that ".thumbnail" is inserted before the extension. The previous names should become images/photo.thumbnail.jpg and images/photo.20.19.22.thumbnail.jpg.
What is the best way to do this? This is part of a queryset so it will look like:
{% for record in list %}
{{ record.image }}
{% endfor %}
Now of course I would love to do this outside of my template. However, I don't see a way in which I can do that. After all, this needs to be done for every record inside my queryset. To complicate things, this record does not come directly from a modal. This record is coming from a subquery, so I don't see a way for me to change the modal itself. Should I use templatetags for this? Any other recommendations?
FYI the subquery is something like this:
>>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef('pk')).order_by('-created_at')
>>> Post.objects.annotate(image=Subquery(newest.values('image')[:1]))
A simple custom template filter could do this.
#register.filter
def add_thumbnail(image):
return image.replace('.jpg', 'thumbnail.jpg')
And in the template:
{% for record in list %}
{{ record.image|add_thumbnail }}
{% endfor %}

How to store the result of a templatetag using Django templates?

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?

Loading template tag variables for use in template

How do I load the results of a templatetag into a a template to iterate over? Basically I am aiming to load the tags on a model object (using django-tagging) and then iterate through the tags to create a list of related products based on those tags. Then I would like to iterate through those product objects to display more information about them.
Ex, my template tag:
#register.simple_tag
def get_rel_from_tag(tag_list):
try:
relproducts = UniPart.objects.filter(part__contains = partbase)
except:
print "no related products"
return None
else:
relproducts = UniPart.objects.filter(part__contains = partbase)
return relproducts
How do I make it so that relproducts is returned as a variable? This is how I call it in the template:
{% tags_for_object design as tag_list %}
{% get_rel_from_tag tag_list %}
Basically now I want to iterate over relatedprod now but it's not working.
The simple_tag helper does not allow you to assign the result to a context variable in this way. Try using assignment_tag instead.
Did you load the template tag file using {% load 'your_file_name %}
Update:Try using 'with' to cache the result from tags_for_object_design
{% with tag_list=tags_for_object design %}

if..else custom template tag

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. :)

Extending Django Admin Templates - altering change list

A (not so) quick question about extending django admin templates.
I'm trying to change the result list (change list in django lingo) of a specific model by adding an intermediary row between the result rows (row1 and row2 classes) that contains some objects related to that object.
I searched the code but haven't found a way to do this. Any pointers are very much appreciated. Code will also help too.
PS: I know I should be designing my own interface, but this is an internal project and I don't have that much time to spare. Also, the django interface is really nice.
Thank you in advance.
To expand on Yuji's answer, here are some specifics on overriding change_list_results.html ...
Override changelist_view as described above in step 1, and also described here at djangoproject. Or auto-override by placing in the appropriate directory as in step 2 above. (Note that the step 2 path shown above is model-specific. App-specific would be /admin/<MyAppName>/change_list.html under any directory defined in the TEMPLATE_DIRS tuple.)
Or (perhaps easier) simply specify ModelAdmin.change_list_template as explained here with any discoverable template filename. (Although, if you retain the name change_list.html, be sure not to deposit directly into the /admin folder, else the extends tag will cause a recursion.)
class MyModelAdmin(admin.ModelAdmin):
change_list_template = 'change_list.html' # definitely not 'admin/change_list.html'
# ...
In your change_list.html template, have at a minimum
{% extends "admin/change_list.html" %}
{% load i18n admin_static admin_list %}
{% load myapptags %}
{% block result_list %}
{% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
{% result_list cl %}
{% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
{% endblock %}
Create a /<MyAppName>/templatetags package (a directory containing __init__.py) with a file corresponding to the load tag above
# MyAppName/templatetags/myapptags.py
from django import template
from django.contrib.admin.templatetags.admin_list import result_list
register = template.Library()
register.inclusion_tag('my_change_list_results.html')(result_list)
Copy and edit Django's change_list_results.html (as e.g. my_change_list_results.html above) to use your added functionality.
Note that these steps do not include extra context for the template, but can easily be expanded as such. (My reason for doing this was to add classes for CSS and a leading <tbody> that was not sorted with the results list.)
ADDITIONAL:
To include extra context, change your templatetags module as follows:
# MyAppName/templatetags/myapptags.py
from django import template
from django.contrib.admin.templatetags.admin_list import result_list as admin_list_result_list
def result_list(cl):
mycl = {'myextracontext': 'something extra'}
mycl.update(foo_extra())
mycl.update(admin_list_result_list(cl))
return mycl
register = template.Library()
register.inclusion_tag('my_change_list_results.html')(result_list)
Then, the value of myextracontext or whatever foo_extra returns can be included in your results template (as e.g. {{ myextracontext }})
Step 1: Overriding changelist view:
You'll have to override a template as opposed to specifying one like you can with add_view / change_view.
First things first, override
def changelist_view(self, request, extra_context=None): in your ModelAdmin. Remember to call super(foo, self).changelist_view(request, extra_context) and to return that.
Step 2: Overriding templates:
Next, override the app-specific changelist template at templates/admin/my_app/my_model/change_list.html (or not.. you can use a global changelist override too if you'd like).
Step 3: Copy result list functionality
I think you can either copy result_list functionality (define a new template tag) or fake it by copying and pasting the result_list function and template include into your view.
# django.contrib.admin.templatetags.admin_list
def result_list(cl):
"""
Displays the headers and data list together
"""
return {'cl': cl,
'result_hidden_fields': list(result_hidden_fields(cl)),
'result_headers': list(result_headers(cl)),
'results': list(results(cl))}
result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
You can see the admin uses this admin/change_list_results.html template to render individual columns so you'll need to use one of the methods to replace this template tag.
Since it's looking for a global template, I wouldn't override it.
Either define a new tag w/ new template specifically for your view, or send result_list(cl) to your template directly and adopt the admin/change_list_results.html for use directly in your change_list.html template.
If you want a complete override of the admin_list template tag, here is my post in another thread. https://stackoverflow.com/a/55597294/11335685
I know it is not directly related to the change_list template, but the logic for it lays on the functions inside the admin_list template tag.
A disadvantage of that approach is i didn't find a way of overriding only specific function and get use of the other ones.
Still that was the only approach that was allowing me to not mess with the html templates and only alter the behavior of result_list function.
Hope that helps anyone.