I have a one template say:
default_form.html
which receives in its context a variable, say variant.
I can of course render the contents of the variable with {{ variant }}
And here:
https://stackoverflow.com/a/28076380/4002633
I see a wonderful way to load a template if it exists. It works nicely if I have a template called variant as follows:
{% try_include variant %}
But I am hoping to go one step further. I would like to use a constructed template name, in the example above and include if it exists a template named default_form_variant.html.
I imagine something like this:
{% try_include "default_form_{{variant}}.html" %}
but even more general rather than encoding the current template's name, getting that, if possible, from the context itself or in a custom tag from the parser.
I am struggling to see how and where in the custom tag definition I'd have access to three things at once:
The name of the current template: default_form.html in this case.
The argument to the tag: variant in this case
The ability to construct a template name from the first two (easy enough with os.path) and inject it into the do_include handler.
Well, as no-one was rushing to answer this I experimented with the wonderful solution to another problem posted here:
https://stackoverflow.com/a/28076380/4002633
at some length and have implemented the following with success:
class IncludeVariant(template.Node):
'''
A Template Node that tries to include a template file named as a variant
of the file it's included in. That is if it's in a template named:
form_data.html
as:
{% include_variant context_var %}
it will try and include:
form_data_context_var.html
where context_var is a context variable.
For help on custom template tags:
https://docs.djangoproject.com/en/3.1/howto/custom-template-tags/#writing-the-compilation-function
'''
def __init__(self, parser, token):
self.parser = parser
self.token = token
def render(self, context):
try:
words = self.token.split_contents()
variant = context.get(self.token.contents.split()[1], self.token.contents.split()[1])
path = context.template_name
parts = os.path.splitext(path)
words[1] = f"'{parts[0]}_{variant}{parts[1]}'"
self.token.contents = " ".join(words)
include = do_include(self.parser, self.token)
return include.render(context)
except template.TemplateDoesNotExist:
return ''
#register.tag('include_variant')
def include_variant(parser, token):
'''
Include the specified variant on this template but only if it exists.
:param parser:
:param token:
'''
return IncludeVariant(parser, token)
It is a mildly risky approach in that it rests on premises that stood up to empirical testing but I can't find clear documentation for. Namely that we can rebuild the passed-in token as I have done (a re-join of a token.split_contents()) and that this is robust. It certainly seems to be and inspecting do_include() I am satisfied it's functional.
This permits me to include variants of the current template based on the contents of a context variable.
Related
I'm working on configurable email template and need to validate if only available_variables are used in the content. So if user put {{ apple }}, the form should return ValidationError('This variable is unavailable').
models.py:
email_content = models.TextField()
forms.py:
def clean_email_content(self):
email_content = self.cleaned_data['email_content']
available_variables = ['{{ company_name }}', '{{ user_name }}']
required_variables = ['{{ company_name }}']
for required_variable in required_variables:
if not required_variable in email_content:
raise forms.ValidationError("{} is required".format(required_variable))
# how to check if there are only `available_variables` in the content?
TL;DR
email_content cannot contain other variables (strings that starts with {{ and ends with }}) than specified in available_variables
Should I use regex or can I validate this using some method from Django Template Engine?
You may want to use the template engine's lexer instead (nb: django 1.11 code, might need to be adapted if you use django 2.x):
from django.template import base
lexer = base.Lexer("{{ var_one }} is one {% if var_two %}{{ var_two|upper }} is two {% endif %}")
tokens = lexer.tokenize()
for t in tokens:
if t.token_type == base.TOKEN_VAR:
print("{}: {}".format(t, t.contents)
I leave it up to you to read the template.base code to find out other useful features...
Also in your validation method you definitly want to catch ALL errors at once (instead of raising as soon as you found an error) so the user can fix all errors at once (unless you really want to drive your users mad, that is).
And finally, as Gasanov suggests in his answer, you also want to use sets to find both missing required vars and illegal ones - this is much more efficient than doing sequential lookups.
We can use regex to find all tags from email_content. After that we convert them to set and substract all available_variables from it to find all incorrect ones. If any exists - we throw ValidationError.
Note that available_variables is set itself and contains just tags, without curly brackets.
Our regex checks for any numbers of spaces between brackets and tag, so your users shouldn't be able to cheat.
import re
def clean_email_content(self):
email_content = self.cleaned_data['email_content']
available_variables = {'company_name', 'user_name'}
finds = re.findall(r"{{[ ]*(.*?)[ ]*}}", email_content)
invalid_tags = set(finds) - available_variables
if invalid_tags:
raise forms.ValidationError(
"Should not contain invalid tags: " + str(invalid_tags)
)
return email_content
I currently have a function called "copyright" (a dynamic copyright message) that I am trying to include into my base Django template, like below:
def copyright():
some code
some more code
print(finaloutput)
I have it sitting in my modules/utils.py which is in my assets directory which I have registered in my static directories.
I want to be able to call that function like {{ copyright }} straight in my top level base.html inside my main templates folder.
I have tried everything to ensure I am loading the staticfiles with no luck. Am I approaching this the wrong way?
Unfortunately almost everything you're doing here is wrong.
This has nothing to do with static files: as you said yourself, this is a dynamic function so isn't static by definition. Anyway, you can't put Python code in your assets directory. And finally, any function like this will always need to return the result, not print it.
What you need here is a template tag, which you put in your app's templatetags directory and register via the decorator:
#register.simple_tag
def copyright():
some code
some more code
return finaloutput
Then, load the tags in your template and call it as a tag, not a variable:
{% load utils %} # or whatever you called the file
...
{% copyright %}
See the template tags docs.
There are several ways to achieve your end goal, but nothing you are doing will get you there.
You can,
Use template tags.
Use context processors, in several different ways.
Use {{ view.function_name }} as-is in your templates if you are using class based generic views from Django.
Judging from how I think you have things set up, the fastest way could be to just pass in some context data in your views.
If you are using functional views, your code can look something like this:
def my_view(request):
def copyright():
return "copyright 2018"
return render('my_template.html', {'copyright': copyright})
If you are using class based generic views, you can simply modify your get_context_data.
class Home(TemplateView):
def get_context_data(self, *args, **kwargs):
ctx = super(TemplateView, self).get_context_data(self, *args, **kwargs)
ctx['copyright'] = self.copyright()
return ctx
def copyright(self):
return "copyright 2018"
I have yet to see anybody implement this pattern and am eager to learn if it's even technically viable. Let me provide an example of what the pattern would look like using a custom filter:
In this example, the "get_widget" filter will look for MyWidget objects with the name or key passed in as the first argument.
Template Logic
{% get_widget "whizbang" as item %}
<h1>{{item.name}}</h1>
{% get_widget "1234" as item %}
<h1>{{item.name}}</h1>
Custom Filter
#register.assignment_tag(takes_context=True)
def get_widget(context, widget_name):
try:
return MyWidget.objects.get(name=widget_name)
except MyWidget.DoesNotExist:
return None
But that seems rather hideous.
What I'd like to see is something a little more dyanmic:
Example:
Retrieve an instance of MyWidget based on its name being "whizbang" or, alternatively, using it's key.
In the template:
<h1>{{MyWidget.whizbang}}</h1>
<h1>{{MyWidget.1234}}</h1>
The question is twofold:
Would it be possible to pass in a singleton/factory to the
request_context
Is there a Python mechanism to "intercept" a method
call and interpret it before it's executed?
After sifting through the Python docs it looks like a combination of __getattrr__() and passing in a class name is all that was required. My apologies for answering my own question. Hopefully this will be useful for someone else.
Template
{{ Menus.HeyDude.DoSomething }}
Class
from mycode import Menu
class MenuFactory():
def __getattr__(self, name):
try:
return Menu.object.get(name=name)
except Menu.DoesNotExist:
raise AttributeError
Middlewear context processor
from mycode import MenuFactory
def context_processor(request):
return_val = {}
# Add all of the factories
return_val['Menus'] = MenuFactory
return return_val
I have a requirement for consecutive custom template tags to be combined before rendering the template in django 1.6+. The tags may be identical or different tags from a selected list of "compatible" tags.
I am trying to loop through the templates nodelist prior to render(context) being called. This loop would combine consecutive nodes that are compatible, however I can't work out how to automatically call a function on the template before it is rendered with a context.
I have looked at
1) middleware - none seem to have access to the compiled template node list
2) custom template class - breaks shortcuts like render and render_to_response
3) function called directly on template in the view - same issue as above
Any thoughts on how this could be achieved?
A bit of background
The basic idea is to have different template tags "render together" e.g. in a simplified example:
{% tag_one "1A" "1B" %}{% tag_two "2A" %}
Instead of rendering as two separate blocks:
[ 1A 1B ][ 2A ]
This would render as a single tag block:
[ 1A 1B 2A ]
There is a bit more logic than this behind the tag's render function but not relevant here...
you may use this snippet: Capture template output as a variable and define a new filter that removes the "][" or whatever inside of it.
example (in a file in your templatetags directory):
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
#register.tag(name='captureas')
def do_captureas(parser, token):
try:
tag_name, args = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError("'captureas' node requires a variable name.")
nodelist = parser.parse(('endcaptureas',))
parser.delete_first_token()
return CaptureasNode(nodelist, args)
class CaptureasNode(template.Node):
def __init__(self, nodelist, varname):
self.nodelist = nodelist
self.varname = varname
def render(self, context):
output = self.nodelist.render(context)
context[self.varname] = output
return ''
#register.filter(name='join_my_tags')
#stringfilter
def join_my_tags(text):
return text.replace(' ][ ', '')
usage (in your view):
{% captureas my_tags %}{% tag_one "1A" "1B" %}{% tag_two "2A" %}{% endcaptureas %}
{{my_tags|join_my_tags}}
untested, alternative approach: it does not requires any templatetag but you have to be very selective in the replacement if you want to avoid collateral effects (e.g. messing up your javascript code). It is similar to your attempts but act after rendering, not before.
modify your view in this way:
from django.shortcuts import render
def index(request):
myresponse = render(request, 'index.html') # add your context object if you need it
return HttpResponse(myresponse.content.replace(' ][ ', '')) # you may eventually use regex or eventually even a full html parser (!) but please take performances in account :)
You can "peek ahead" in the parser provided to the template tag. For instance the following uses parser.next_token() to inspect the next few TemplateNodes to see if they are compatible.
def my_tag(parser, token):
parse_me = [token]
# the following lines check if there are any additional nodes
# that we should combine and adds them to a list for processing
if parser.tokens:
while parser.tokens[0].token_type == 2 and parser.tokens[0].content[0:3] == 'my_':
parse_me.append(parser.next_token())
contents = ""
for tok in parse_me:
# apply some logic to combine the nodes
contents += tok.contents
return MyCustomNode(contents)
I'm trying to create a custom template tag that only renders a block of code once, regardless of how many times the tag/partial that contains it is executed.
This is how I've implemented it, but as you can see, it's a bit hackish:
my_partial.html:
{% once mycontent %}
this will only show once
{% endonce%}
my_template.html:
{% load my_tags %}
{% for i in list %}
{% my_partial %}
{% endfor %}
my_tags.py:
#register.inclusion_tag('my_partial.html',takes_context=True)
def my_partial(context):
return dict(arbitrary extra data)
#register.tag(name="once")
def do_once(parser, token):
try:
# Splitting by None == splitting by spaces.
tag_name, var_name = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError("%r tag requires arguments" % token.contents.split()[0])
nodelist = parser.parse(('endonce',))
parser.delete_first_token()
return DoOnceNode(nodelist, var_name)
class DoOnceNode(template.Node):
def __init__(self, nodelist, var_name):
self.nodelist = nodelist
self.var_name = '_do_once_'+var_name
def render(self, context):
request = context['request']
# Make request.GET mutable.
request.GET = dict(request.GET)
if self.var_name in request.GET:
return ''
else:
request.GET[self.var_name] = 1
return self.nodelist.render(context)
Specifically, I'm using the request.GET dictionary as a mutable global scope. It's hackish and obviously not what the request object is designed to do, but it works.
Ideally, I'd like to use something like the context, but I found that it isn't shared between calls to this tag. i.e. self.var_name in context is always False, rendering it useless as a global scope.
Why isn't context shared the same way request is shared? Is there someway to make it truly shared, or is there some other object I can use to store globally accessible variables within a request?
I'm not exactly sure what you need to accomplish or if your approach is indeed the best approach, but I'd suggest your look into the forloop.first variable before you go too far down this road. Your approach seems awkward at best at a glance, but I could be wrong since I don't know the specifics of the situation
django for template tag
Most likely you should be able to make this work to your needs, however if it falls short I'd suggest that the source for the for template tag (and it's forloop variable) would likely be very illustrative on how you might implement what you're looking to do.
What I ended up doing is saving a variable within the context and checking for that in the Node's render method:
class CustomNode(template.Node):
def render(self, context: dict) -> str:
context['already_rendered'] = context.get('already_rendered', set())
if self.__class__ in context['already_rendered']:
return ''
context['already_rendered'].add(self.__class__)
...