Accessing initial value of django forms when iterating over the fields - django

I'm trying to do something pretty simple; I'd like to apply a "hidden" style to a form field inside a django template when I've passed in some initial value like this:
form = form_class(initial={'field':data})
Normally, it would be like this:
<li class="{{form.somefield.name}} {% if form.somefield.initial %} hidden{% endif %}>
...
</li>
But I'm iterating over the forms, so what I want do do is something that looks like this:
{% for field in form %}
<li class="{{field.name}} {% if field.initial %} hidden{% endif %}">
...
</li>
{% endfor %}
but this doesn't work, because field.initial only has the value defined as initial to the field in the form, not the data that's passed in at the form's creation. Is there a good solution for this besides just breaking out the iterating into individual forms?
Some (bad) solutions I've thought of:
overriding init to stuff values form self.initial into self.fields;
writing a template tags called {% hideifhasinitial %}
adding a method to the form that uses zip on self and self.initial (doesn't work, since self.initial only had one element and self had 4, it only iterated over 1 element, and the keys (field names) didn't match up).

how about this?
{% for field in form %}
{% if field.name in field.form.initial.keys %}
...
{% endif %}
{% endfor %}

Initial data can be accessed on the value attribute, initial data represents the value of the field:
{{field.value}}

Turns out there's a way easier way to do this.
{% if field.name in form.initial.keys %}

The solution with the initial keys has not worked for me, because the field contains as a value an empty string. I had to write my own custom tag:
from django import template
register = template.Library()
#register.simple_tag
def field_empty(field):
if not field.form.initial.get(field.name):
return ' hidden'
return ''
In your example, I would use the tag this way:
<li class="{{ field.name }} {% field_empty field %}">

Related

django template - how to get a dictionary property without using a for loop

Doing this works
{% for comment in comments %}
{{ comment.user }}
{% endfor %}
However, I want to get all the comment.user values in the dictionary without using a for loop. Is this possible?
I ask because I need to do this check
{% if name in comment.user %} # check if name is in any one of the comments
# do something
{% endif %}
Basically, you need to get all the distinct users from comments. You have to do it in the view and pass users queryset back into the template:
users = User.objects.filter(comment__in=comments).distinct()

Django dynamic template variable inside if clause

Here's what I'm trying to achieve in "pseudo code":
{% for page in pages %}
{% if 'can_access_page_{{page.name}}' in perms %}
<li>
{{ page.name }}
</li>
{% endif %}
{% endfor %}
How to do this? Permission names I can customize — but still can't figure out this one.
Simplest way is to slightly abuse Django's existing add template filter (intended for numbers but works for strings), as in this answer:
https://stackoverflow.com/a/4524851/202168
You'll need a custom filter. Something like:
#register.filter
def check_page_perms(page, perms):
return 'can_access_page_%s' % page.name in perms
and use it:
{% if page|check_page_perms:perms %}

Iterate through model fields in jinja template

I have several models in my application, and as I will have some views doing the same thing (form + tables showing existing records in my model), but implemented dfferently because of the model, which will be different in each case, I was wondering if it was possible to make it generic.
I googled a bit and was not able to find anything relevant to my case.
What I would like to achieve:
In my view, I want to go through each object from the model that I passed to the template, for example:
return render_template('addstatus.html', form=form, statuses=Status.query.all(),
target_model="Status", fields=Status.__mapper__.c.keys())
But I want to have only one view, whatever the model will be, so I am not able to know in advance the fields of my model, the number of columns and so on.
I want to have something like that in my view:
{% for obj in objects %}
{% for field in obj.fields %} (not existing)
<h1> {{ field }} :: {{ obj.fields.field.value }}
{% endfor %}
{% endfor %}
Is it something possible? How can I achieve that?
You can add this method to your db.Model class (either by subclassing or by monkeypatching):
from sqlalchemy.orm import class_mapper, ColumnProperty
def columns(self):
"""Return the actual columns of a SQLAlchemy-mapped object"""
return [prop.key for prop in class_mapper(self.__class__).iterate_properties
if isinstance(prop, ColumnProperty)]
Then you can use {% for col in obj.columns() %} to iterate over the column names.
I had a similar issue, and wanted something purely Jinja2 based. This will return all of the keys/fields in your "obj" table, and worked well for me:
obj.__table__.columns._data.keys()
Here it is implemented in your sample code
{% for obj in objects %}
{% for field in obj.__table__.columns._data.keys() %}
<h1> {{ field }} :: {{ obj[field] }} </h1>
{% endfor %}
{% endfor %}

"Last" tag not working

I have a queryset of "promotion" events which are rendered in a template. Each of these promotions also have 1 or more appointments. What I want to do is display the dates of the first and last appointments.
So far, using the "first" tag works. However, using the "last" tag causes:
TemplateSyntaxError Exception Value:
Caught an exception while rendering:
Negative indexing is not supported.
Here's the template script
{% for promotion in promotions%}
{% with promotion.appointment_set.all as appointments %}
{% with appointments|first as first_ap %}
{{ first_ap.date|date }}
{% endwith %}
{% with appointments|last as last_ap %}
{{ last_ap.date|date }}
{% endwith %}
{% endwith %}
{% endfor %}
What am I doing wrong here?
Converting the queryset into a list before giving it to the template also gets you where you want to go:
return render_to_response(template, {
appointments: list(Appointments.objects.all())
})
Since I'm using the whole list I do something like this (which might be open to improvement):
{% for ap in appointments %}
{% ifequal ap appointments|last %}
ap.date
{% endifequal %}
{% endfor %}
The object attributes still work. eg: ap.user.full_name
The last tag works by slicing a list to get the last item, using the negative index format: collection[-1]. But as the error message points out, negative indexing is not supported on querysets.
Probably the easiest way of solving this is to create a new method on your Promotion model to return the last appointment:
class Promotion(models.Model):
... fields, etc ...
def get_last_appointment(self):
try:
return self.appointment_set.all().order_by('-date')[0]
except IndexError:
pass
and call this from the template:
{{ promotion.get_last_appointment.date|date }}
The cause of your problem is what #Daniel pointed out: Querysets do not support negative indexing. His solution is worth exploring.
Another way of addressing this is to add a custom filter that will work with lists and querysets. Something like:
#register.filter
def custom_last(value):
last = None
try:
last = value[-1]
except AssertionError:
try:
last = value.reverse()[0]
except IndexError:
pass
return last
And in the template:
{% with appointments|custom_last as last_ap %}

Use variable as dictionary key in Django template

I'd like to use a variable as an key in a dictionary in a Django template. I can't for the life of me figure out how to do it. If I have a product with a name or ID field, and ratings dictionary with indices of the product IDs, I'd like to be able to say:
{% for product in product_list %}
<h1>{{ ratings.product.id }}</h1>
{% endfor %}
In python this would be accomplished with a simple
ratings[product.id]
But I can't make it work in the templates. I've tried using with... no dice. Ideas?
Create a template tag like this (in yourproject/templatetags):
#register.filter
def keyvalue(dict, key):
return dict[key]
Usage:
{{dictionary|keyvalue:key_variable}}
You need to prepare your data beforehand, in this case you should pass list of two-tuples to your template:
{% for product, rating in product_list %}
<h1>{{ product.name }}</h1><p>{{ rating }}</p>
{% endfor %}
Building on eviltnan's answer, his filter will raise an exception if key isn't a key of dict.
Filters should never raise exceptions, but should fail gracefully. This is a more robust/complete answer:
#register.filter
def keyvalue(dict, key):
try:
return dict[key]
except KeyError:
return ''
Basically, this would do the same as dict.get(key, '') in Python code, and could also be written that way if you don't want to include the try/except block, although it is more explicit.
There is a very dirty solution:
<div>We need d[{{ k }}]</div>
<div>So here it is:
{% for key, value in d.items %}
{% if k == key %}
{{ value }}
{% endif %}
{% endfor %}
</div>