alias51
alias51

Reputation: 8608

How to access model field verbose name in a template?

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

Answers (1)

claypooj
claypooj

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

Related Questions