Reputation: 8608
This question has an answer here, but it is outdated and incorrect as it does not work in a template.
I am using a standard ListView
to access objects in a template. I want to display my objects in a table with field names in the header and field values in the table body.
To do this, is fairly straightforward, but I am not sure how to access the object field name. You can't use {{ object._meta.get_field[field].verbose_name }}
as _meta
is not available in templates.
For example: First I add a list of the fields I want to render to the context:
def get_context_data(self, **kwargs):
context['fields'] = ['id', 'email', 'name']
Then I loop them in the template:
<table>
<thead>
{% for object in object_list %}
{% if loop.first %}
<tr>
{% for field in fields %}
// MISSING CODE //
{% endfor %}
</tr>
{% endif %}
{% endfor %}
</thead>
<tbody>
{% for object in object_list %}
<tr>
{% for field in fields %}
<td>{{ object[field] }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
This works well in that it displays just the fields I want in the body, but somehow I need to access the field verbose name on the object. I guess I could pass a dict
in my context instead of a list (with field:verbose_name
key:value pairs) , but this doesn't feel very DRY.
Any ideas?
Upvotes: 4
Views: 1583
Reputation: 413
I had the same problem, so I created a mixin that exposes model._meta to the template and creates a list of fields from the model. The mixin overrides the default get_context()
method.
It was cleaner to doctor up the list of fields in the mixin than it was to try and do it in the template, but I still wanted access to things like model._meta.verbose_name for headings, so I passed model._meta as additional context too.
Here's the mixin:
# views.py
#=================================================================
class ExtraContextMixin:
"""Add the model meta and model fields as objects to the context.
Ignores the primary key and fields that are not editable.
"""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['model_meta'] = self.model._meta
context['fields'] = []
for fld in self.model._meta.get_fields():
if fld.editable and not fld.primary_key:
context['fields'].append(fld)
return context
So for your list view (or any other class-based view), you just add in the mixin. Make sure ExtraContextMixin
is first so it will override get_context_data()
properly. Also, make sure you define ExtraContextMixin
before any class you're using it with in your module.
# views.py
#=================================================================
class FooListView(ExtraContextMixin, ListView):
...
In order to be certain the data for each object matched up correctly with each field heading, I made a custom template tag. The tag has the added benefit of displaying the human-readable choices if you're using any fields that have choices defined.
# custom_tags.py
#=================================================================
from django import template
register = template.Library()
@register.simple_tag
def get_field_data(object, field):
"""Get human-readable data from the field to be used in an html template."""
# Get the data stored in the given field
data = getattr(object,field.name)
# If the field has choices defined, return the human-readable
# version of the data.
if not getattr(field,'choices') is None:
choice_dict = dict(field.choices)
data = choice_dict[data]
return data
You would then implement it in the template like this:
# template.html
#=================================================================
{% load custom_tags %}
<table>
<thead>
{% for field in fields %}
<th>{{field.verbose_name|capfirst}}</th>
{% endfor %}
</thead>
<tbody>
{% for object in object_list %}
<tr>
{% for field in fields %}
<td>
{% get_field_data object field %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Upvotes: 1