Break long {% with %} assignment into multiple lines in a Django template [duplicate] - python

I am creating a custom django template tag by using such a code :
#register.simple_tag(takes_context=True)
def render_listing(context, *args, **kwargs):
... my code ...
This works well, but in my template, it seems that all parameters must be on a single line, for example :
this works:
{% render_listing param1=val1 param2=val2 ... paramN=valN %}
but on multiple lines, it does not work :
{% render_listing param1=val1
param2=val2
...
paramN=valN %}
I tried multiple escape sequences but I did not succeeded,
Is there a way to specify a template tag on multiple lines ?

No, the Django template language does not support multiple line tags. See ticket 8652, which was closed as WONTFIX, or this thread from the django-developers mailing list.
Sometimes, if there is a repeated prefix, you can make it more readable by using the with tag. For example if you have,
{% render_listing param1=long.common.prefix.val1 param2=long.common.prefix.val2 param2=long.common.prefix.val3 %}
you could rewrite as
{% with prefix=long.common.prefix %}
{% render_listing param1=prefix.val1 param2=prefix.val2 param2=prefix.val3 %}
{% endwith %}
Often (but not always), a really long tag is an indication that you're putting too much logic in the template. See if you can move some of it into the view, model method, template tag or template filter.

It's pretty straightforward to enable, though hackish:
import re
from django.template import base
base.tag_re = re.compile(base.tag_re.pattern, re.DOTALL)
"Using" it is simple; one place I find it especially useful is {% include %} tags:
{% include 'my/sweet/modal-template.template' with
message="Hey, do you really want to frob the widget?"
yes="Heck yes I do!"
no="No frickin' way!"
icon="error"
%}
I haven't tested this in more recent versions of Django but I imagine it could be adapted; that worked at least back around 1.8. I should point out that in theory some tags that do custom parsing of their arguments could break; in practice I haven't had any trouble in the last ~10 years of Django programming.

Related

Is this okay to add slashes in below code at template of Django?

I am trying to solve a problem. During the process I got this question.
{% if request.path == '/{{sub_cat.sub_cat_parent.cat_slug}}/{{sub_cat.sub_cat_slug}}/' %}
<div class="alert-secondary">Active</div>
{% else %}
How do I properly include slashes in here '/{{sub_cat.sub_cat_parent.cat_slug}}/{{sub_cat.sub_cat_slug}}/'? Is it okay to use {{ within {% template tags? what am I doing wrong in here?
"variable substitution" ({{ somevar }}) syntax is indeed not supported in templatetags arguments - which makes sense if you consider that in the context of template rendering, this syntax would be the equivalent of a "print" statement.
This doesn't mean you can't pass context variables to templatetags - else templatetags would be of very limited use - but you do so by just mentionning them ie assuming you have a context variable named "path", you could use it here as
{% if request.path==path %}
...
You can also use filter expressions here, ie this is valid too:
{% if request.path==path|lower %}
(assuming the templatetag's author correctly handled the tags arguments - I let you refer to the documentation for more on this).
Now wrt/ your problem here, you could of course use a custom filter to join the different parts but that would actually be a very bad idea. Django's url handing is based on the idea that you should actually never hardcode an url anywhere - you define and name urls in your urls.py files, and then use the builtin functions (django.core.urlresolvers.reverse()) / templatetags ({% url %}) to generate them.
Here, the proper solution would be to define a get_absolute_url() method on your sub_cat object that returns the correct url (actually the path portion of it) for this object, and test against it in the template.
As far as I'm concerned, I'd even use a second indirection level as a template filter so 1/ your model (or whatever sub_cat is) knows it's own url, and 2/ the template filter can tell if a given sub_cat is "active" for the current request.path so if the spec defining when a sub_cat is "active" is to change you'll only have the template filter function to change instead of having to browse thru all your templates (possibly missing something on the way).
use with clause
{% with "/"|add:sub_cat.sub_cat_parent.cat_slug|add:"/"|add:sub_cat.sub_cat_slug|add:"/" as url_path %}
{% if request.path == url_path %}
<div class="alert-secondary">Active</div>
{% else %}
{% endwith %}
this will work

Variable context between two blocks in Django templates?

I have two blocks that call the same method with same variables. I want to call the method only once, but the result is then outsite the scope of the block tags. I've tried calling this method in the parent template header.html and with a with tag, but nothing seems to work.
This is the layout:
{% extends "header.html" %}
{% load navigation_tags %}
{% block header %}
{% get_section site=site as section %}
{% include "foobar.html" with section=section %}
{% endblock header %}
{% block navigation %}
<nav>
<div class="container">
{% get_section site=site as section %}
{% navigation section.slug %}
</div>
</nav>
{% endblock navigation %}
navigation_tags.py
#register.assignment_tag
def get_parent_section(site):
if site.id == settings.FOOBAR_SITE_ID:
section = Section.objects.get(id=settings.FOOBAR_SECTION_ID)
else:
# This is also a section instance.
return site.default_section
As mentioned by 2pacho in another answer and Fernando Cezar in a comment, the easiest way to share values between different sections is to set it in the template context. If you are using the render shortcut function, you can pass a dict as the context parameter to add a value to the rendering context of the template. That would be a good place to add it and this would be the easiest place to put it.
return render(request, 'template.html', {'section': get_parent_section(site)})
However, if for some reason, you can't include it in the context, you can use a decorator to add memoization to your function, so that it will cache the computation results and return it immediately when called with the same parameters. You can use functools.lru_cache to do so, or it's Django backport at django.utils.lru_cache.lru_cache if you are using Python 2.x.
#register.assignment_tag
#functools.lru_cache()
def get_parent_section(site):
if site.id == settings.FOOBAR_SITE_ID:
section = Section.objects.get(id=settings.FOOBAR_SECTION_ID)
else:
# This is also a section instance.
return site.default_section
I wouldn't call a method outside .py . Think that this is using Jinja2 templates,
it's powerful but not in the way that the backend can be.
What I recommend you doing in this case is to generate a context for the template and use this variables there.
Would be as simple as adding it to your context where it's being generated.
context['site_parent'] = get_parent_section(site)
Think that Jinja2 (html) has to be as simple as possible and that can help you with basic coding and time saving (like loops to print the exact same information or show and hide code depending on the context) but I would keep it as simple you can when rendering.
If you would like you can read official django website about templates https://docs.djangoproject.com/en/2.0/topics/templates/
But from my expirience I would keep the method calls in the views.py

Including Blocks from Jinja templates

I would like to include blocks from templates instead of macros, as many templates will be including content from many other templates, so extends isn't an option.
I've read many answers about this, including blocks in included files, but the use case always seems to be different. I suspect this cannot be done.
template1.html
{% block description %}
<p> Here is a description </p>
{% endblock %}
And in template2.html
{% from 'template1.html' import description %} <- doesnt work
You have two options here.
Use a macro, which sounds like something you don't want to do.
Use a template_filter.
Assuming you're using Flask, this is as easy as:
#app.template_filter('get_block')
def template_filter(block, file):
html = render_template(file)
# Then use regex or something to parse
# or even simple splits (better with named blocks), like...
content = html.split('{%% block %s %%}'%(block))[-1]
content = content.split('{%% endblock %%}')[0]
return content
And to use it:
{% 'description'|get_block('template1.html') %}

Infinite recursion while extending the admin's app change_form template

I have the following template in template/admin/change_form.html:
{% extends "admin/change_form.html" %}
{% block extrahead %}
{% include "dojango/base.html" %}
{% block dojango_content %}
{% endblock %}
{% endblock %}
However for some reason it throws a
TemplatesyntaxError: TemplateSyntaxError at /admin/cms/post/add/
Caught RuntimeError while rendering: maximum recursion depth exceeded while calling a Python object
I know it's late, but...
If extending - which is a far better option than duplicating - the key is to have it named anything except /admin/change_form.html.
(Although the OP referred to template/admin/change_form.html, this is simply because a path in his TEMPLATE_DIRS tuple ends in '/template' - mine generally end in '/templates' - but, these directories can be named anything and located anywhere.)
It will be used automatically on a per-app basis if named /admin/<MyAppName>/change_form.html
It will be used automatically on a per-model basis if named /admin/<MyAppName>/<MyModelName>/change_form.html
It can be named anything if specified explicitly in the ModelAdmin
class MyModelAdmin(admin.ModelAdmin):
change_form_template = 'subdir/my_change_form.html'
Finally, if insistent on naming it /admin/change_form.html, you can - provided that the extends tag contains the full path to your django installation instead of a relative one.
You are in admin/change_form.html and you extend admin/change_form.html. You cannot extend the same template which you are in.
You probably expected that if you override template from admin application, you can extend the one you override. But this is not how it works. When you override a template you cannot access it.
Solution to your problem is to copy original template and change things you don't like.
Also, you can point your AdminOptions class to another template using the change_form_template property.
Something like:
class MyOptions(AdminOptions):
change_form_template = 'myapp/my_change_form.html'
And myapp/my_change_form.html:
{% extends "admin/change_form.html" %}
I had the same problem. Solved by placing the overridden template under myapp/templates/admin/myapp instead of myapp/templates/admin.
The best way to do this that I have found is to use '..' to go up a couple of directories, then go back down into directories that should only be found in the Django code base.
As the Django templates are in something like "django/contrib/admin/templates/admin", I found that this worked me:
{% extends "../../admin/templates/admin/change_form.html" %}
If that still causes a clash with some other structure you have, you could go further:
{% extends "../../../contrib/admin/templates/admin/change_form.html" %}
or even:
{% extends "../../../../django/contrib/admin/templates/admin/change_form.html" %}
Although it is a little hacky, at least by doing the above you don't have to use some other technique that involves copying the django source or setting up a symlink.
With Django core it's impossible. But it is not impossible.
Copy and paste "the original template and change things you don't like" it's very ugly.
Don't make in the templates, whatever you don't make in python
This solution is to any template:
http://pypi.python.org/pypi/django-smart-extends

Render Externally Defined Block In Django Template

I'm writing a simple blog-like application for Django and am trying to get the effect of having a front page with posts limited to 5, with a comprehensive archive that lists something like 100 posts at a time. (100 is not realistic, just throwing a number out there)
Since the blog post blocks will look exactly the same between the two pages minus the number being shown, I'd like to put the corresponding HTML in a separate template that I can include or link to from the actual templates being rendered. I've looked over the documentation, and the include tag looked promising, but it apparently renders outside of the current context, which is not helpful to my cause, since it wouldn't get the objects to loop through. Outside of that, I can't see any other way to do what I want. Is this possible or am I just out of luck and going to have to violate DRY? Code is below to give you an idea of what I want.
Thanks
#######################
# news/frontpage.html #
#######################
{% extends "news/base.html" %}
{% block site_title %} - Front Page{% endblock %}
{% block center_col %}
{{ block.super }}
View Older Blog Posts
{% endblock %}
{% block blog_rows %}
{% for object in object_list %}
# Blog post content would go here, however it is to be included.
{% endfor %}
{% endblock %}
You're looking for an inclusion tag.
Why don't you filter for the blog posts you want to show in your view? That way you can keep the template the same:
{% for object in blogposts %}
# ...
{% endfor %}
You define blogposts in your view, which either includes 5 or 100 posts.
Ignacio is right that you want an inclusion tag, but you should know that the include tag does not render outside the current context - it very definitely uses the same context as the block it's in.
Your problem is probably that you're trying to call blogpost_set on the object_list - but the relationship is not with the list of objects, it's with each individual object in the list. You'd need to iterate through object_list and then through blogpost_set.all on each one.

Categories