Django Template: Access list element by model attribute - python

I have a list of model instances and want to calculate a value for each of it in my view to show it on the page.
I got stuck when I tried to pass this value to the template.
My attempts:
1) Something like {{ list.(model.pk) }} - obviously a syntax error. I searched around the web and the documentation but couldn't find a valid syntax to evaluate a specific part of a "dot"-concatenation first.
2) Give the model a non-database attribute and access it simply with {{ instance.non_db_value }}. But it seems that the template gets its model values directly from the database, so this also failed.
A somewhat ugly solution might be to wrap all the model instances in the model list into a list of single lists, two values each: the first the instance itself, the second the calculated value I need to pass to the template.
With this, I could access the values with something like this:
{% for entry in wrapperlist %}
{{ wrapperlist.entry.0 }} <-> {{ wrapperlist.entry.1 }}
{% endfor %}
But I don't think this can't be it - am I thinking in the wrong direction? Any suggestions or ideas?

First of all, template doesn't get model values directly from DB. For template processor, your model instance it is yet another object. So, it is totally ok to:
#views.py
foo_list = models.Foo.objects.all()
for foo in foo_list:
foo.non_db_property = some_method()
render_to_response('template.html', {'foo_list': foo_list})
and in template:
{% for foo in foo_list %}
<li>{{ foo.non_db_property }}</li>
{% endfor %}
if it doesn't work for you, check out for errors.
Another approach is to create a model method if value is based on model properties only:
#models.py
class Foo(models.Model):
bar = models.CharField(max_length=3)
....
def non_db_value(self):
#calculate
return value
template will remain the same as above

Related

key and value of dictionary in html file Django [duplicate]

I would like to print out the number of votes that each choice got. I have this code in a template:
{% for choice in choices %}
{{choice.choice}} - {{votes[choice.id]}} <br />
{% endfor %}
votes is just a dictionary while choices is a model object.
It raises an exception with this message:
"Could not parse the remainder"
choices = {'key1':'val1', 'key2':'val2'}
Here's the template:
<ul>
{% for key, value in choices.items %}
<li>{{key}} - {{value}}</li>
{% endfor %}
</ul>
Basically, .items is a Django keyword that splits a dictionary into a list of (key, value) pairs, much like the Python method .items(). This enables iteration over a dictionary in a Django template.
you can use the dot notation:
Dot lookups can be summarized like
this: when the template system
encounters a dot in a variable name,
it tries the following lookups, in
this order:
Dictionary lookup (e.g., foo["bar"])
Attribute lookup (e.g., foo.bar)
Method call (e.g., foo.bar())
List-index lookup (e.g., foo[2])
The system uses the first lookup type
that works. It’s short-circuit logic.
To echo / extend upon Jeff's comment, what I think you should aim for is simply a property in your Choice class that calculates the number of votes associated with that object:
class Choice(models.Model):
text = models.CharField(max_length=200)
def calculateVotes(self):
return Vote.objects.filter(choice=self).count()
votes = property(calculateVotes)
And then in your template, you can do:
{% for choice in choices %}
{{choice.choice}} - {{choice.votes}} <br />
{% endfor %}
The template tag, is IMHO a bit overkill for this solution, but it's not a terrible solution either. The goal of templates in Django is to insulate you from code in your templates and vice-versa.
I'd try the above method and see what SQL the ORM generates as I'm not sure off the top of my head if it will pre-cache the properties and just create a subselect for the property or if it will iteratively / on-demand run the query to calculate vote count. But if it generates atrocious queries, you could always populate the property in your view with data you've collected yourself.
You need to find (or define) a 'get' template tag, for example, here.
The tag definition:
#register.filter
def hash(h, key):
return h[key]
And it’s used like:
{% for o in objects %}
<li>{{ dictionary|hash:o.id }}</li>
{% endfor %}
django_template_filter
filter name get_value_from_dict
{{ your_dict|get_value_from_dict:your_key }}
Similar to the answer by #russian_spy :
<ul>
{% for choice in choices.items %}
<li>{{choice.0}} - {{choice.1}}</li>
{% endfor %}
</ul>
This might be suitable for breaking down more complex dictionaries.
Ideally, you would create a method on the choice object that found itself in votes, or create a relationship between the models. A template tag that performed the dictionary lookup would work, too.
Could find nothing simpler and better than this solution. Also see the doc.
#register.filter
def dictitem(dictionary, key):
return dictionary.get(key)
But there's a problem (also discussed here) that the returned item is an object and I need to reference a field of this object. Expressions like {{ (schema_dict|dictitem:schema_code).name }} are not supported, so the only solution I found was:
{% with schema=schema_dict|dictitem:schema_code %}
<p>Selected schema: {{ schema.name }}</p>
{% endwith %}
UPDATE:
#register.filter
def member(obj, name):
return getattr(obj, name, None)
So no need for a with tag:
{{ schema_dict|dictitem:schema_code|member:'name' }}
You could use a namedtuple instead of a dict. This is a shorthand for using a data class. Instead of
person = {'name': 'John', 'age': 14}
...do:
from collections import namedtuple
Person = namedtuple('person', ['name', 'age'])
p = Person(name='John', age=14)
p.name # 'John'
This is the same as writing a class that just holds data. In general I would avoid using dicts in django templates because they are awkward.

Django SEO app, integrating external data like 'Best {{ product }} of the year'

I am trying to integrate external data in my metatags. What I want to achieve - by entering in Title field (in admin) "Details for {{ product.name }}" I would like to get to get automaticaly "Details for Hadron Collider" as a result in a browser. I have this in my template
{% load seo %}
{% get_metadata for product as metadata %}
{{ metadata.title }}
and I am passing Product object to the template, but what I get as a result is unchanged "Details for {{ product.name }}", so {{ value }} won't be populated / parsed? I have read the docs http://django-seo.readthedocs.org/en/latest/reference/administrators.html, it looks so simple
If you would like to reference the relevant model instance, you type the name of the model surrounded by two braces. For example Buy {{ product }} today!. You can go further and reference different fields from the relevant instance, for example By {{ product.name }} today!.
Though it doesn't work for me. Please, help. Probably I am doing something wrong.
Ok, after days of depression :) I solved it, at least for my project and currently only for Models, but for ModelInstance and Views it's almost I will make it later, it has the same problem in my case. I am quite new to Django, so a better solution may exist, I found this one.
I use Django 1.6.4, Python 2.7, multilingual project (Django Transmeta for model translation)
The problems:
1. variable substitution in models, model instances and views doesn't work. So, if we pass an object to DjangoSEO it won't substitute e.g. {{ product }}, {{ product.name }} variables. According to documentation it should. The problem, for Model is in backends.py, ModelBackend class. Function _resolve_value doesn't pass object to the function _resolve, which is supposed to populate meta tags with object properties. Here are the functions with slight changes that work for me.
def _resolve_value(self, name):
value = super(ModelMetadataBase, self)._resolve_value(name)
try:
return _resolve(value, self._content_object)
except AttributeError:
return value
def _resolve(value, model_instance=None, context=None):
""" Resolves any template references in the given value. """
if isinstance(value, basestring) and "{" in value:
if context is None:
context = Context()
if model_instance is not None:
context[model_instance.__class__.__name__.lower()] = model_instance
t = Template(value)
value = t.render(context)
return value
Also, in the file base.py, function get_linked_metadata we have to attach our object to Metadata instances like this:
if ModelMetadata is not None:
try:
model_md = ModelMetadata.objects.get(_content_type=content_type, _language=language)
except ModelMetadata.DoesNotExist:
model_md = ModelMetadata(_content_type=content_type, _language=language)
model_md._content_object = obj
instances.append(model_md)
And the problem 2 - DjangoSEO was using one Metadata instance for any language, even with option use_i18n = True. So, add _language=language as mentioned above.
Django SEO app is quite good, it has everything SEO needs (if it works :), so there is no reason to reinvent the wheel.
You need to wrap your variables in html tags.
{% load seo %}
{% get_metadata for product as metadata %}
<html>
<head>
<title>{{ metadata.title}}</title>
</head>
<body></body>
</html>

Making query from template

I am using jinja2 as the templating engine for django app. I wanted to make query in the template. I tried doing this:
{% for f in fs %}
{% Following.objects.filter(follows=f).count() %}
{% endfor %}
I'm passing 'fs' in variables while rendering the templates which a list.
But, its wrong. I can't do the 'query_set' call because of the way my models are defined. Here is a snippet:
class Following(models.Model):
user = models.ForeignKey(User)
follows = models.ForeignKey(F)
class F(models.Model):
name = models.CharField(max_length=50)
So, is there a possible way to do this?
If you are using jinja2 rather than the normal Django template language, what you have should work: you don't say why it doesn't.
But nevertheless, there's no need to define a separate method. You can use the automatic reverse relationship accessor:
{{ f.following_set.count() }}
You can't call methods that take parameter from django template. To overcome this, you can define method in your F model to get the required count.
For example:
class F(models.Model):
name = models.CharField(max_length=50)
def get_follow_count(self):
Following.objects.filter(follows=self).count()
Then in template you can do
{% for f in fs %}
{{ f.get_follow_count }}
{% endfor %}

Linking errors to the right field in WTForms FieldLists

I am using WTForm to validate a form that I submit directly from a javascript model (filled through knockout). In my form I have a list of bank accounts which can be added/removed dynamically. On the python side I have something like this :
class Account(Form):
acc_name = TextField('Account', [validators.Length(min=2, max=35)])
class InformationForm(Form):
account_list = FieldList(FormField(Account))
The json data that is received for validation is the following :
'account_list': [{'acc_name': 'aaaaa'}, {'acc_name': 'b'}]}
The problem is then when I validate I receive something like this where there is no way to know which account in the list is the source of the error:
'account_list': [{'acc_name': [u'Field must be between 2 and 35 characters long.']}
How can I do to link the error to the right account ?
EDIT : the way I did it in the end is that I added a getErrors method to my InformationForm class, which builds a dictionnary indexed by a unique Id for each Account where the values are each Account error. I then return that as json to my application. I keep the question open in case there is a "natural" solution...
Each individual element in a FieldList is a field in and of itself. If the FieldList contains a TextField, then each entry is a TextField. If it contains a FormField then it's a FormField (which then contains a form with its own fields) The FieldList entries can be accessed either by iterating a FieldList or by accessing the .entries attribute of the FieldList.
So instead of looking at form.account_list.errors look at the enclosed field's errors.
So for your use, something like this:
{% for subfield in form.account_list %}
<!-- subfield in this case is an instance of FormField -->
{{ subfield.form.acc_name() %}
{% if subfield.errors %}
{% for error in subfield.form.acc_name.errors %}
<p class="error">{{ error }}</p>
{% endfor %}
{% endif %}
{% endfor %}
You probably want to abstract this out to a macro instead of doing this for every specialty field setup, if this is something you need to do consistently and often.

Possible to limit filters ManyToMany/Foreign Key in Django admin for a model where the relationship is defined on the other model?

So the title is a bit obtuse, I know, but I couldn't think of a more succinct way to state it. Here's the issue:
I've created two proxy models for "user types", both inheriting from django.contrib.auth.User. Each has a custom manager limiting the queryset to items belonging to a particular Group. Specifically, there's a PressUser which is any user belonging to the "Press" group and StaffUser which is any user in any other group than "Press".
The issue is that when I add 'groups' to list_filters on my StaffUsers modeladmin, the resulting filter options are every group available, including "Press", and not just groups available to StaffUsers.
I've research a bit online and came up with a custom filterspec that should produce the behavior I want, but the problem is that the User model's 'groups' attribute is actually a related_name applied from the Group model. As a result, I can't attach my filterspec to 'groups' in my proxy model.
Is there any other way to apply the filterspec? Alternatively, is there a better approach to filtering the items returned by the default filterspec?
So, I was able to solve my own problem. For those that might run into a similar situation, here are the steps:
The approach I took is to modify the change_list.html template and manually filter out the items I didn't want to be included. There's quite a number of changes to make, though.
First, add a changelist_view method to your ModelAdmin:
# myproject/account/admin.py
class StaffUserAdmin(models.ModelAdmin):
...
def changelist_view(self, request, extra_context=None):
groups = Group.objects.exclude(name__in=['Press',]).values_list('name')
extra_context = {
'groups': [x[0] for x in groups],
}
return super(StaffUserAdmin, self).changelist_view(request,
extra_context=extra_context)
Basically, all we're doing here is passing in the filtered list of Groups we want to use into the context for the template.
Second, create a change_list.html template for your app.
# myproject/templates/admin/auth/staffuser/change_list.html
{% extends "admin/change_list.html" %}
{% load admin_list %}
{% load i18n %}
{% load account_admin %}
{% block filters %}
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% trans 'Filter' %}</h2>
{% for spec in cl.filter_specs %}
{% ifequal spec.title 'group' %}
{% admin_list_group_filter cl spec groups %}
{% else %}
{% admin_list_filter cl spec %}
{% endifequal %}
{% endfor %}
</div>
{% endif %}
{% endblock filters %}
This one deserves a little explanation. First, the template tag loads: admin_list is used for the default Django template tag responsible for rendering the filters, admin_list_filter, i18n is used for trans, and account_admin is for my custom template tag (discussed in a sec), admin_list_group_filter.
The variable spec.title holds the title of the field that's being filtered on. Since I'm trying to alter how the Groups filter is displayed, I'm checking if it equals 'groups'. If it does, then I use my custom template tag, otherwise, it falls back to the default Django template tag.
Third, we create the template tag. I basically just copied the default Django template tag and made the necessary modifications.
# myproject/account/templatetags/account_admin.py
from django.template import Library
register = Library()
def admin_list_group_filter(cl, spec, groups):
return {'title': spec.title, 'choices' : list(spec.choices(cl)), 'groups': groups }
admin_list_group_filter = register.inclusion_tag('admin/auth/group_filter.html')(admin_list_group_filter)
The only things that I've changed here are adding a new argument to the method called 'groups' so I can pass in my filtered list of groups from before, as well as adding a new key to the dictionary to pass that list into the context for the template tag. I've also changed the template the tag uses to a new one that we're about to create now.
Fourth, create the template for the template tag.
# myproject/templates/admin/auth/group_filter.html
{% load i18n %}
<h3>{% blocktrans with title as filter_title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul>
{% for choice in choices %}
{% if choice.display in groups %}
<li{% if choice.selected %} class="selected"{% endif %}>
{{ choice.display }}</li>
{% endif %}
{% endfor %}
</ul>
No big surprises here. All we're doing is putting all the pieces together. Each choice is a dictionary with all the values needed to construct the filter link. Specifically, choice.display holds the actual name of the instance that will be filtered by. Obviously enough, I've set up a check to see if this value is in my filtered list of groups I want to show, and only render the link if it is.
So, it's a bit involved but works remarkably well. Just like that, you have a list of filters that is exactly what you want instead of the default ones generated by Django.
I'm going to tell you off the bat that I've never done this before myself, so take it with a grain of salt.
What I'd suggest would be to override get_changelist on your ModelAdmin, to return a custom ChangeList class, which you can define somewhere in your admin module.
Your custom ChangeList class would simply override get_filters, so you can map your custom FilterSpec for the group field.
Another thing that might interest you are patches from the feature request ticket for specifying custom filter specs. The latest patch doesn't work for Django 1.3rc1 yet, although #bendavis78 recently posted that he's working on a new one, but depending on your version of Django it may apply cleanly.
It looks like it barely missed the cut to get included into the 1.3 milestone, so I figure it's going to make it into the trunk as soon as work beings on Django 1.4.

Categories