Template snippet libraries for Django
Read the documentation at Read The Docs
Template snippet libraries for Django
License: MIT License
Template snippet libraries for Django
Read the documentation at Read The Docs
Given:
{% widget "form_alias:TextInput" id="lol" raw_value="lol2" %}
An exception is raised:
AttributeError: 'str' object has no attribute 'items'
because {{ widget.attrs|flatattrs }}
receives ''
(or TEMPLATE_STRING_IF_INVALID maybe? dunno) due to not having a Widget
instance in the context.
Probably the fix is to wrap the widget attrs output in an IfNode?
Doing a pip install django-sniplates ends up presenting me with 0.5.0, with a date of 2016-08-16, yet here we see releases showing Jan 25, 2016 with 0.4.1. Looks like 0.5.0 is the most current but isn't tagged here.
Submitting patched version of sniplates.py (0.7.0) for your consideration for future releases.
Intent: to overcome 2 issues with 'nested_widget':
Effects of modification:
How this is done, in NestedWidget.render(), is:
Example usage:
{% widblock 'myapp:foo' %}
{% block introText %} ...add styled intro text, with links... {% endblock %}
{% block bodyGraph %} ...include a graph template... {% endblock %}
... optional body of this widget block, to be injected as variable 'content' ...
{% endwidblock %}
Thank you for your effort in developing, sharing and maintaining sniplates. I look forward to making heavy use of it in my current and future projects.
Cheers
David
(aum108 on freenode)
I don't think we have tests covering a value of None
, or a list or tuple.
I have the following widgets (simplified for brevity):
{% block _label %}
<label for="{{ id_for_label }}">{{ label }}</label>
{% endblock %}
{% block CharField %}
{% reuse _label %}
<input id="{{ id_for_label }}" type="text">
{% endblock %}
For a particular use case, I want to generate the following:
<label for="id_my_field">Customized <a href="/somewhere">HTML</a> label</label>
<input id="id_my_field" type="text">
Where /somewhere
is a result of reverse('some_view')
.
The hacky solution is set the label of the form field to the HTML and mark it safe. However, due to circular imports and mark_safe
not playing along with reverse_lazy
this isn't feasible, and besides, HTML belongs in the templates.
Sniplates has nested_widget
, but it forces the variable name to be content
, not label
, and doesn't have the automatic attribute extraction of the form_field
.
{% form_field my_field nested_content_for='label' %}
Customized {% url 'some_view' %} label
{% end_form_field %}
Upon seeing nested_content_for
, form_field
parses the inside content and assigns the result to label
in the context. widget
works the same. nested_widget
is deprecated as it is equivalent to {% widget nested_content_for='content' %}
.
If nested_content_for
is not specified as an argument for form_field
or widget
, they behave like now.
{% form_field my_field nested %}
{% attribute 'label' %}
Customized {% url 'some_view' %} label
{% attribute 'something_else' %}
Multiple parameters can be specified!
{% end_form_field %}
Upon seeing nested
as an attribute, form_field
(and widget
) look for {% attribute ... %}
sections and add their result to the context. The advantage over the first option is the ability to specify multiple inline arguments.
I'm not particularly attached to the wording, etc. If you're okay with either option, I can write up a PR.
Just ran into this bug (and I probably know how to fix it as well):
Given a form:
class FreeformExportForm(forms.Form):
templates = forms.ModelMultipleChoiceField(
queryset=Template.objects.freeform(),
initial=Template.objects.freeform(),
widget=forms.CheckboxSelectMultiple
)
where the queryset is empty (there are no Template
objects), doing
{% form_field form.templates %}
in the template crashes.
The violating line is https://github.com/funkybob/django-sniplates/blob/master/sniplates/templatetags/sniplates.py#L445:
@register.simple_tag(takes_context=True)
def form_field(context, field, widget=None, **kwargs):
if not field:
raise template.TemplateSyntaxError('form_field requires a value field as first argument')
In this case, bool(field)
is equal to False
, while it is still a BoundField
instance.
@funkybob suggested change:
@register.simple_tag(takes_context=True)
def form_field(context, field, widget=None, **kwargs):
if not isinstance(field, BoundField):
raise template.TemplateSyntaxError('form_field requires a value field as first argument')
Template:
{% extends "mail/base.html" %}
{% load i18n reservation_tags sniplates %}
{% block content %}
{% load_widgets mail='sniplates/mail.html' %}
{% nested_widget 'mail:p' %}
{% trans "You can find the tickets and all visit information per group here. These links
are shareable with the companion of each group:" %}
{% endnested %}
{% endblock %}
sniplates/mail.html
:
Mail sections - to be used as nested_widgets
{% block introduction %}
<!-- introduction -->
<br><br>
<h3 style="color: {{ link_color }};font-family: "Arial", sans-serif;font-weight: normal;padding: 0;margin: 0;text-align: left;line-height: 1.3;word-break: normal;font-size: 20px;">{{ content }}</h3>
{% endblock introduction %}
Generic content
{% block p %}
<!-- content -->
<p style="margin: 0;margin-bottom: 10px;color: {{ color }};font-family: "Arial", sans-serif;font-weight: normal;padding: 0;text-align: left;line-height: 1.3;font-size: 17px;">{{ content }}</p>
{% endblock p %}
Expected output:
<!-- content -->
<p style="margin: 0;margin-bottom: 10px;color: #000;font-family: "Arial", sans-serif;font-weight: normal;padding: 0;text-align: left;line-height: 1.3;font-size: 17px;">
You can find the tickets and all visit information per group here. These links
are shareable with the companion of each group:
</p>
Actual output:
<!-- content -->
<p style="margin: 0;margin-bottom: 10px;color: #000;font-family: "Arial", sans-serif;font-weight: normal;padding: 0;text-align: left;line-height: 1.3;font-size: 17px;">
{% trans "You can find the tickets and all visit information per group here. These links
are shareable with the companion of each group:" %}
</p>
I saw my Django 3.0 compat PR got merged and some smaller tweaks were done (thanks!) including the version bump to 0.7.0, but this hasn't been published to PyPI yet. Could you find some time to do this?
Alternatively - since I'm already maintainer on this repo, I wouldn't mind taking care of PyPI releases either :-)
So, I've got a template tag, which I'm using to wrapover load_widgets
and reuse
... or at least that's the plan:
def my_data_nodes():
level3 = {'title': 'test3', 'children':[]}
level2 = {'title': 'test2', 'children': [level3]}
level1 = {'title': 'test', 'children': [level2]}
return [level1]
@register.simple_tag(takes_context=True)
def show_list(context, ...):
load_widgets(context, list_items="list_items.html", _soft=True)
with using(context, 'list_items'):
result = reuse(context, ['list'], nodes=my_data_nodes())
return result
So that should:
list_items
)list
)Given the template list_items.html
:
{% load sniplates %}
{% block list %}<ul>{% for node in nodes %}{% reuse "listitem" %}{% endfor %}</ul>{% endblock list %}
{% block listitem %}<li>
{{ node.title }}
{% if node.children %}{% reuse "list" nodes=node.children %}{% endif %}
</li>{% endblock listitem %}
basically I should end up with a standard <ul><li>...
list, I think.
But what I'm actually seeing is only 1 level of items -- the call to {% reuse 'list' ...%}
inside the listitem block never renders anything.
I think it's something to do with the push/pops on contexts, but I cannot figure out much beyond that.
block_context
contains:
{'listitem': [<Block Node: listitem ...>], 'list': [<Block Node: list. ...>]}
{% reuse 'listitem' %}
within {% block list %}
) the block context only contains:
{'listitem': [<Block Node: listitem ...>], 'list': []}
-- the BlockNode for list is gone.<ul>
(the call to {% reuse 'list' %}
within the listitem
block) both keys are now empty:
{'listitem': [], 'list': []}
I have no idea if that's intentional, but it seems like recursion through popping and rendering/reusing blocks ought to be faster than using includes recursively (which did work), so I thought I'd try it.
The output for using includes directly was:
The output from the attempt at {% reuse %}
was:
Subsequent to 1210d1c (first landing in 0.4.1), it looks like:
{% block PasswordInput %}{% reuse "input" input_type="password" value="" %}{% endblock %}
is wrong I think, value=""
ought to be raw_value=""
to avoid rendering anything into the input value=""
attribute?
bootstrap.html:
{% block label %}
<span class="label label-{{ label_type|default:'default' }}">
{{ text }}
</span>
{% endblock %}
template.html
{% load sniplates %}
{% load_widgets bootstrap="bootstrap.html" %}
{% widget "bootstrap:label" labell_type="error" text="Things go here" %}
In the case above, labell_type
is ignored altogether - it would be nice if the template author could be told there is a problem.
So, first bit of testing this feature branch, and I ran into the following issue:
A lot of the provided templates will have to change value
to raw_value
, including your test templates I think. Right now, the raw_value
None
will be output in TextInput
as <input type="text" value="None">
. Or, the value
property should maybe catch the values None
, 'False,
True`.
My personal preference would be to just use raw_value in the template, but that can lead to inconsistencies. For scalar values it makes sense, for fields with choices not so much, since the choices
property returns values that have been force_text
'ed, you have to do the comparisons against the force_text
'ed raw_value, which is value
in the template. So then you will be using raw_value
and value
in the template, for seemingly the same check - i.e.: is this choice the current/selected value?
Opening for discussion here, as I'm not on IRC right now.
If I recall correctly, @funkybob, you're somehow wizard enough to not be a heavy user of django-debug-toolbar. I'm not, alas, and rely on it for sanity-checking/debugging.
I'd like to explore/propose exposing enough hooks for the debug-toolbar template panel to pick up renderings that come out of sniplates.
Specifically, currently, {% load_widgets %}
calls out to resolve_blocks
, which loads a template (via get_template
) but never renders it, and because the API debug-toolbar expects a template to use (calling ._render
on a Template
instance) is never touched by sniplates, the template never ends up collected into the djdt panel.
A naive solution to that is:
...
# For Django 1.8 compatibility
template = getattr(template, 'template', template)
if settings.DEBUG:
template_rendered.send(sender=None, template=template, context=context)
This would at least mean that templates loaded turn up in the templates list, which is a useful thing in and of itself, if you know that DTL (and subsequently often 3rd party tag libraries) will often try and continue vs erroring loudly.
To get individual block renderings would be slightly more work (but ultimately would be my preference, so that answering the questions like "what's in this form_field's context right now?" is easier for me), but also probably require deciding on how to handle any explicit djdt support anyway, hence this discussion :)
Basically:
Mostly a self-reminder: with the past few releases the upgrade path from formulation has changed a bit and should be better documented - expecially with regard to value
/raw_value
.
There's an error in the value checking for form_field. Value is a callable and needs to be called. I'll provide a test and PR with fix later this week.
I'm on mobile now, sorry for the short report - merely a heads up and reminder for me.
I have this use case: an inline_formset with a ModelForm, these are the models involved:
class Build(models.Model):
title = models.CharField(max_length=50)
class BuildPhoto(models.Model):
build = models.ForeignKey(Build)
photo = models.ForeignKey('albums.Photo')
url = models.URLField(blank=True)
def __unicode__(self):
return self.build.title # simplified, but traverses the FK
class BuildPhotoForm(forms.ModelForm):
class Meta:
model = BuildPhoto
widgets = {'photo': forms.HiddenInput}
Now, when I make an inlineformset for BuildPhotoForm with BuildPhoto, Django adds the 'id' field in BuildPhotoForm (django.forms.models.BaseModelFormSet.add_fields). This field is a ModelChoiceField and the queryset is not configurable, except if you subclass the BaseFormSet and override add_fields to modify this.
This is a HiddenInput widget by default, just like the 'photo' field. However, for these widgets in sniplates, the 'choices' is evaluated, while for ModelChoiceFields field.choices
is a ModelChoiceIterator and thus lazy. This is very costly for the id
field in my case, as it traverses the FK, leading to a query bomb in my template. I have no interest in the choices
. It seems to me that choices
is only evaluated to get the display
of the field, so I vote for getting rid of display
. It doesn't make a lot of sense in multiple-value fields anyway. Or, if it's needed, make it lazy.
Concrete example: I was getting +3000 queries before subclassing, after 'fixing' it in my subclassed BaseFormSet, I'm only getting 27 queries.
Thoughts?
Running a coverage report indicates that the line:
https://github.com/funkybob/django-sniplates/blob/master/sniplates/templatetags/sniplates.py#L140
is not executed.
https://github.com/funkybob/django-sniplates/blob/master/sniplates/templatetags/sniplates.py#L350
This line here casts the value of a BooleanField to a string. This means if you try to do something like
<input type="checkbox" {% if value %}checked{% endif %}>
it won't work as value will always evaluate to True.
Am I missing something here, or is this a bug?
Similar I think to the fix in 53b4205 for form_field, given:
{% reuse '' %}
or (how I discovered it) not quoting the blockname {% reuse blockname %}
will silently fail because the block_list the function receives is u''
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.