Display additional data while iterating over a Django formset - python

I have a list of soccer matches for which I'd like to display forms. The list comes from a remote source.
matches = ["A vs. B", "C vs. D", "E vs, F"]
matchFormset = formset_factory(MatchForm,extra=len(matches))
formset = MatchFormset()
On the template side, I would like to display the formset with the according title (i.e. "A vs. B").
{% for form in formset.forms %}
<fieldset>
<legend>{{TITLE}}</legend>
{{form.team1}} : {{form.team2}}
</fieldset>
{% endfor %}
Now how do I get TITLE to contain the right title for the current form? Or asked in a different way: how do I iterate over matches with the same index as the iteration over formset.forms?
Thanks for your input!

I believe that in the Django template language there is no built-in filter for indexing, but there is one for slicing (slice) -- and therefore I think that, in a pinch, you could use a 1-item slice (with forloop.counter0:forloop.counter) and .first on it to extract the value you want.
Of course it would be easier to do it with some cooperation from the Python side -- you could just have a context variable forms_and_matches set to zip(formset.forms, matches) in the Python code, and, in the template, {% for form, match in forms_and_matches %} to get at both items simply and readably (assuming Django 1.0 or better throughout this answer, of course).

This is an addition to Alex's answer.
I did some reading after my comment on Alex's answer and found that it is important to get the management form (basically a meta-form with information about how many forms are in the formset) into your template for your submitted data to be treated as a formset rather than just a pile of forms. Documentation here.
The only two ways I know to get that into your template are:
Send the formset in addition to whatever data structure you made,
and then render the management form with {{ my_formset.management_form }}
Render the management form in your view and send that as an item to your template
Of course if you use Alex's first method, the formset is already available so you can add the management form directly.

Related

passing dictionary to view ,with multiple key:value pairs

def status_(request,id):
stages=['Vendor Quotation','Estimate Generation','Purchase Requsition','Purchase Order','Provision To Invoice','Reimbursement Invoice','Request Receipt#(PTI+RI+PO)','Receipt# Received(Updated PTI)','Request Sales Tax Invoice','Uploaded to Jazz Portal']
ctx={'invoice':Invoices.objects.get(pk=id),'stages':stages}
return render(request,"Invoices/status.html",ctx)
Hi I am trying to pass multiple objects of data for displaying to the template for displaying , I am creating a ctx dictionary with multiple 2 key value pairs ,second Key-value pair is array ,when i access this array in by using
{% for idx in range(0,len(ctx.get("stages")) %}
I get following parsing error
**Could not parse the remainder: '(0,len(ctx.get("stages"))' from 'range(0,len(ctx.get("stages"))'**
You can not make function calls in Django templates with parameters, hence the above wiull not work, but likely you do not need this anyway, you can simply enumerate with:
{% for stage in stages %}
{{ stage }}
{% endfor %}
If you really need to enumerate, then you can pass a range object through the context, but then probably a next problem pops up: you can not subscript with a variable either. It will require using a lot of extra template filters, making it very complicated. Templates are normally not used to implement business logic, therefore Django has a template engine that does not support complicated function calls, etc. to give the developer an incentive to move the business logic to the view.

Is it possible to style a part of a label in Flask-WTForms?

I generate a form via Flask-WTF.
The relevant checkbox looks like this:
conditions = BooleanField(
"I agree to the above conditions.", validators=[DataRequired()])
I'd like to have the I agree bold or strong - NOT the rest of the sentence.
I cannot pass in HTML tags, as they get escaped and rendered as text.
This would look like this:
[] <strong>I agree</strong> to the above conditions.
I'd like to get this result:
I agree to the above conditions.
Is this possible?
Thanks for any hints.
Thanks to the other answer by #gaefan, I read more about Jinja templating and the safe filter and came up with a another working solution.
from flask import Markup
label_for_conditions = Markup('<span class="customClass">I agree</span> to the above conditions.')
conditions = BooleanField(label_for_conditions, validators=[DataRequired()])
This solution does not need even a safe filter in the template.
This answer is inspired by the following discussion:
Passing HTML to template using Flask/Jinja2
It is not a perfect solution, as now HTML and Python is mixed in the form definition, but it looks like you have to make a compromise.
A simple solution would be to just do it in the template. Instead of:
{{ user_form.conditions.errors }}{{ user_form.conditions.label_tag }}<br />{{ user_form.conditions }}
do:
{{ user_form.conditions.errors }}<strong>I agree</strong> to the above conditions.<br />{{ user_form.conditions }}
I haven't tested this, but you may be able to do:
conditions = BooleanField(
"<strong>I agree</strong> to the above conditions.", validators=[DataRequired()])
and then, in the template:
{{ user_form.conditions.errors }}{{ user_form.conditions.label_tag|safe }}<br />{{ user_form.conditions }}
There are two solutions:
When creating the Field using render_kw.
When rendering the Field in the template (when the field is being called [form.field()]).
Initialize Field with style:
All the Field objects in WTForms have a render_kw arg in __init__.
render_kw (dict) – If provided, a dictionary which provides default keywords that will be given to the widget at render time.
When your template is being rendered, Jinja2 is going to read this render_kw.
In your case, you can define:
conditions = BooleanField(
"I agree to the above conditions.", validators=[DataRequired()],
render_kw={"style": "font-weight: bold;")
Rendering while templating
When Jinja2 is rendering, you can specify other rendering options by calling the field.
form.field()
__call__(**kwargs): Render this field as HTML, using keyword args as additional attributes.
[...]
In all of the WTForms HTML widgets, keyword arguments are turned to
HTML attributes, though in theory a widget is free to do anything it
wants with the supplied keyword arguments, and widgets don’t have to
even do anything related to HTML.
So in your template file, you can do something like that:
{{ form.field(style="font-weight: bold;") }}
Note that there is an exception for the class keyword, probably reserved by something else, then the key should be class_.
Source: Fields - WTForms Documencation
I'm using WTForm 2.3.3 on Flask 1.1.2 and I ended up using the following since piping the label itself, the label_tag, etc. to safe were not working for me but this does:
{{ field.label.text|safe }}

How to add extra field data to a form field in Wtforms

I am trying to add extra data to a form field in wtforms.
I have to create a text field which has an associated unit with it (eg - meter/sec). How do I add the meter/sec string to the form field?
Is there any way to pass a dictionary or something to add data to the field that i can access in the template?
There is a not very well known parameter, description= to the field constructor. Though it purports to be for help text, the framework itself doesn't care what you put in there (and indeed doesn't use it anywhere at all, other than passing it along.)
So you could do, for example:
class PhysicsForm(Form):
speed = TextField('Speed', description={'unit': 'meters/sec'})
distance = TextField('Distance', description={'unit': 'kilometers'})
Then you could use it in a jinja-style template something like:
{{ form.speed }} <label>{{ form.speed.description.unit }}</label>
footnote There was no real reason for using a dictionary as the value of description - it was merely to illustrate that you can put nearly any value in there, including containers which can hold many values.

How can I distinguish lists from strings in django templates

I'm developing a project on Google AppEngine, using Django templates, so I have to use tags like {{ aitem.Author }} to print content within my HTML template.
Author, however, can either be a string or a list object, and I have no way to tell it in advance. When the Author is a list and I try to print it on my template, I get the ugly result of
Author: [u'J. K. Rowling', u'Mary GrandPr\xe9']
Is there any way to handle this kind of scenario (basically printing a field differently depending on its type) effectively? Do I have to rely on custom tags or any other means?
I think the cleanest solution would be to add a method to the model get_authors() which always returns a list either of one or more authors. Then you can use:
Author: {{ aitem.get_authors|join:", " }}
If you for some reason have only access to the templates and can't change the model, then you can use a hack like this:
{% if "[" == aitem.Author|pprint|slice:":1" %}
Author: {{ aitem.Author|join:", " }}
{% else %}
Author: {{ aitem.Author }}
{% endif %}
P.S. it's not a good convention to use capital letters for attribute names.
I think that Aidas's get_authors() solution is the best, but an alternative might be to create a template tag that does the test. You'll want to read up on custom template tags, but they aren't that hard to create if you look at the existing ones.
I followed Matthew's advice and eventually implemented a filter to handle lists.
I'm posting it here just in case someone else needs it.
#register.filter(name='fixlist')
def fixlist(author):
if type(author) == list:
return ', '.join(author)
else:
return author
I call it from the template pages like this {{ aitem.Author|fixlist }}
Thank you for the help!

Django Template Slice - Reversing Order

Thanks to a very helpful hint from another question I learned I can limit the amount of values in a list by slicing it in the template as such:
{% for comment in thread.comment_set.all|slice:":3" %}
Now I would like to get the last 3 results of my comments so I figured a simple ":-3" or "-3" would do the trick, alas:
Caught an exception while rendering: Negative indexing is not supported.
Also using:
{% for comment in thread.comment_set.all|slice:":3" reversed %}
Does not do the trick because if I have 5 comments, instead of 1,2,3 it displays the first three in 3,2,1 order.
Is there any way I can display the last 3 comments of a post without going into my database? I'd love to be able to do this purely using the templating system.
SOLUTION
{% for comment in thread.comment_set.all|dictsortreversed:"created"|slice:"3" %}
Displays the last three thanks to my table having the created timestamp.
Django's database queries are evaluated lazily, so the result of thread.comment_set.all is a QuerySet, not a list. A QuerySet supports many list-like functions, but not negative slicing, so the indexing error is not coming from the template filter itself. (If you're curious, slices on QuerySet objects get translated into a limit clause on the SQL statement, which is why you can't use a negative number).
In general, Django encourages a strict decoupling of templates and models; the views.py module is the glue where you do any work that requires knowledge of database models and queryset methods to translate your model data into simple variables and structures for the template.
Running a related query on a model from a template is not something you typically see in a Django template, and there's a good reason for this. Right now, it may seem very simple to slice the last three elements from the comment_set. Keep in mind, though, that the database will not return results in any guaranteed order. This means that, in addition to your slice, you now also need to add an order_by clause; there's simply no way to express this in a template, nor should there be. Better to think of the view as the translation between your model and the template, and let such database-facing work be done there, rather than embedded in HTML.
In this case, I would encourage you to pass an ordered slice to your template from the view:
# take first three sorted descending
comments = thread.comment_set.order_by('-something')[:3]
context = Context({'comments':comments})
return HttpResponse(tmplt.render(context))
If you must do the slicing in the template, and you really don't care about sorting the results, pass a list to the template. The slice filter will happily do negative slicing:
comments = list(thread.comment_set.all())
context = Context('comments':comments)
In the template:
{% for comment in comments|slice:"-3:" %}
I haven't seen the dictsortreversed filter used too often, and according to the docs it takes a key to sort by
{% for comment in thread.comment_set.all|dictsortreversed:"name"|slice:"3" %}
Use the "ordering" attribute of Meta class of the Comment class to set the required ordering of elements.
IMO templates is not the proper place to order your dataset. Ordering should be done in either Models or Views.
I needed just this and made a more complete solution, I think. Hope people find it useful. The view needs to find the id of the last inserted record (example here). You have to reverse order and get the id (PK) of the newest last record entered which will now be the first record, easy to find now at the top of the heap ;-). Calculate the lower limit id value you desire, then sort from the lower to the newest or last entry using this operator at the end ...[n:m] (I think that is just a list operator), dropping the unwanted records, and then present it in normal order. For a django tables example here is my code:
def showLatestRels(request):
#This view shows the latest 15 entries in the Release table
LastInserted = Release.objects.order_by('-pk')[0]
UpperLimit = LastInserted.id
LowerLimit = UpperLimit - 15
RelTable = Release.objects.all()
tablevalslat = ReleaseTable(RelTable[LowerLimit:UpperLimit])
RequestConfig(request).configure(tablevalslat)
return render(request, 'DCESrtap/latrels.html', {'tablevalslat': tablevalslat}
)
Lots of ways to do it, see Django docs Make/limit Queries
But couldn't get the dictsort idea to work....
Can't you just slice the list before passing it to your template?
You can reverse a queryset if it is already ordered by "created". So here is a faster solution.
{% for comment in thread.comment_set.all.reverse|slice:":3" %}
Also if you don't want in reversed order.
{% for comment in thread.comment_set.all.reverse|slice:":3"|dictsort:"created" %}
Although you should follow Jarrets Hardie advice above and do all that logic in the views, rather then in the templates.

Categories

Resources