Iterate over model instance field names and values in template

Iterate over model instance field names and values in template

I'm trying to create a basic template to display the selected instance's field values, along with their names.  Think of it as just a standard output of the values of that instance in table format, with the field name (verbose_name specifically if specified on the field) in the first column and the value of that field in the second column.
For example, let's say we have the following model definition:
class Client(Model):
    name = CharField(max_length=150)
    email = EmailField(max_length=100, verbose_name="E-mail")

I would want it to be output in the template like so (assume an instance with the given values):
Field Name      Field Value
----------      -----------
Name            Wayne Koorts
E-mail          waynes@email.com

What I'm trying to achieve is being able to pass an instance of the model to a template and be able to iterate over it dynamically in the template, something like this:

    {% for field in fields %}
        
    {% endfor %}
{{ field.name }} {{ field.value }}
Is there a neat, "Django-approved" way to do this? It seems like a very common task, and I will need to do it often for this particular project.

Solutions/Answers:

Answer 1:

model._meta.get_all_field_names() will give you all the model’s field names, then you can use model._meta.get_field() to work your way to the verbose name, and getattr(model_instance, 'field_name') to get the value from the model.

NOTE: model._meta.get_all_field_names() is deprecated in django 1.9. Instead use model._meta.get_fields() to get the model’s fields and field.name to get each field name.

Answer 2:

You can use Django’s to-python queryset serializer.

Just put the following code in your view:

from django.core import serializers
data = serializers.serialize( "python", SomeModel.objects.all() )

And then in the template:

{% for instance in data %}
    {% for field, value in instance.fields.items %}
        {{ field }}: {{ value }}
    {% endfor %}
{% endfor %}

Its great advantage is the fact that it handles relation fields.

For the subset of fields try:

data = serializers.serialize('python', SomeModel.objects.all(), fields=('name','size'))

Answer 3:

Finally found a good solution to this on the dev mailing list:

In the view add:

from django.forms.models import model_to_dict

def show(request, object_id):
    object = FooForm(data=model_to_dict(Foo.objects.get(pk=object_id)))
    return render_to_response('foo/foo_detail.html', {'object': object})

in the template add:

{% for field in object %}
    <li><b>{{ field.label }}:</b> {{ field.data }}</li>
{% endfor %}

Answer 4:

In light of Django 1.8’s release (and the formalization of the Model _meta API, I figured I would update this with a more recent answer.

Assuming the same model:

class Client(Model):
    name = CharField(max_length=150)
    email = EmailField(max_length=100, verbose_name="E-mail")

Django <= 1.7

fields = [(f.verbose_name, f.name) for f in Client._meta.fields]
>>> fields
[(u'ID', u'id'), (u'name', u'name'), (u'E-mail', u'email')]

Django 1.8+ (formalized Model _meta API)

Changed in Django 1.8:

The Model _meta API has always existed as a Django internal, but wasn’t formally documented and supported. As part of the effort to make this API public, some of the already existing API entry points have changed slightly. A migration guide has been provided to assist in converting your code to use the new, official API.

In the below example, we will utilize the formalized method for retrieving all field instances of a model via Client._meta.get_fields():

fields = [(f.verbose_name, f.name) for f in Client._meta.get_fields()]
>>> fields
[(u'ID', u'id'), (u'name', u'name'), (u'E-mail', u'email')]

Actually, it has been brought to my attention that the above is slightly overboard for what was needed (I agree!). Simple is better than complex. I am leaving the above for reference. However, to display in the template, the best method would be to use a ModelForm and pass in an instance. You can iterate over the form (equivalent of iterating over each of the form’s fields) and use the label attribute to retrieve the verbose_name of the model field, and use the value method to retrieve the value:

from django.forms import ModelForm
from django.shortcuts import get_object_or_404, render
from .models import Client

def my_view(request, pk):
    instance = get_object_or_404(Client, pk=pk)

    class ClientForm(ModelForm):
        class Meta:
            model = Client
            fields = ('name', 'email')

    form = ClientForm(instance=instance)

    return render(
        request, 
        template_name='template.html',
        {'form': form}
    )

Now, we render the fields in the template:

<table>
    <thead>
        {% for field in form %}
            <th>{{ field.label }}</th>
        {% endfor %}
    </thead>
    <tbody>
        <tr>
            {% for field in form %}
                <td>{{ field.value|default_if_none:'' }}</td>
            {% endfor %}
        </tr>
    </tbody>
</table>

Answer 5:

Here’s another approach using a model method. This version resolves picklist/choice fields, skips empty fields, and lets you exclude specific fields.

def get_all_fields(self):
    """Returns a list of all field names on the instance."""
    fields = []
    for f in self._meta.fields:

        fname = f.name        
        # resolve picklists/choices, with get_xyz_display() function
        get_choice = 'get_'+fname+'_display'
        if hasattr(self, get_choice):
            value = getattr(self, get_choice)()
        else:
            try:
                value = getattr(self, fname)
            except AttributeError:
                value = None

        # only display fields with values and skip some fields entirely
        if f.editable and value and f.name not in ('id', 'status', 'workshop', 'user', 'complete') :

            fields.append(
              {
               'label':f.verbose_name, 
               'name':f.name, 
               'value':value,
              }
            )
    return fields

Then in your template:

{% for f in app.get_all_fields %}
  <dt>{{f.label|capfirst}}</dt>
    <dd>
      {{f.value|escape|urlize|linebreaks}}
    </dd>
{% endfor %}

Answer 6:

Ok, I know this is a bit late, but since I stumbled upon this before finding the correct answer so might someone else.

From the django docs:

# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
[<Blog: Beatles Blog>]

# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]

Answer 7:

You can use the values() method of a queryset, which returns a dictionary. Further, this method accepts a list of fields to subset on. The values() method will not work with get(), so you must use filter() (refer to the QuerySet API).

In view

def show(request, object_id):
   object = Foo.objects.filter(id=object_id).values()[0]
   return render_to_response('detail.html', {'object': object})

In detail.html

<ul>
   {% for key, value in object.items %}
        <li><b>{{ key }}:</b> {{ value }}</li>
   {% endfor %}
</ul>

For a collection of instances returned by filter:

   object = Foo.objects.filter(id=object_id).values() # no [0]

In detail.html

{% for instance in object %}
<h1>{{ instance.id }}</h1>
<ul>
    {% for key, value in instance.items %}
        <li><b>{{ key }}:</b>  {{ value }}</li>
    {% endfor %}
</ul>
{% endfor %}

Answer 8:

I used https://stackoverflow.com/a/3431104/2022534 but replaced Django’s model_to_dict() with this to be able to handle ForeignKey:

def model_to_dict(instance):
    data = {}
    for field in instance._meta.fields:
        data[field.name] = field.value_from_object(instance)
        if isinstance(field, ForeignKey):
            data[field.name] = field.rel.to.objects.get(pk=data[field.name])
    return data

Please note that I have simplified it quite a bit by removing the parts of the original I didn’t need. You might want to put those back.

Answer 9:

You can have a form do the work for you.

def my_model_view(request, mymodel_id):
    class MyModelForm(forms.ModelForm):
        class Meta:
            model = MyModel

    model = get_object_or_404(MyModel, pk=mymodel_id)
    form = MyModelForm(instance=model)
    return render(request, 'model.html', { 'form': form})

Then in the template:

<table>
    {% for field in form %}
        <tr>
            <td>{{ field.name }}</td>
            <td>{{ field.value }}</td>
        </tr>
    {% endfor %}
</table>

Answer 10:

There should really be a built-in way to do this. I wrote this utility build_pretty_data_view that takes a model object and form instance (a form based on your model) and returns a SortedDict.

Benefits to this solution include:

  • It preserves order using Django’s built-in SortedDict.
  • When tries to get the label/verbose_name, but falls back to the field name if one is not defined.
  • It will also optionally take an exclude() list of field names to exclude certain fields.
  • If your form class includes a Meta: exclude(), but you still want to return the values, then add those fields to the optional append() list.

To use this solution, first add this file/function somewhere, then import it into your views.py.

utils.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
from django.utils.datastructures import SortedDict


def build_pretty_data_view(form_instance, model_object, exclude=(), append=()):
    i=0
    sd=SortedDict()

    for j in append:
        try:
            sdvalue={'label':j.capitalize(),
                     'fieldvalue':model_object.__getattribute__(j)}
            sd.insert(i, j, sdvalue)
            i+=1
        except(AttributeError):
            pass

    for k,v in form_instance.fields.items():
        sdvalue={'label':"", 'fieldvalue':""}
        if not exclude.__contains__(k):
            if v.label is not None:
                sdvalue = {'label':v.label,
                           'fieldvalue': model_object.__getattribute__(k)}
            else:
                sdvalue = {'label':k,
                           'fieldvalue': model_object.__getattribute__(k)}
            sd.insert(i, k, sdvalue)
            i+=1
    return sd

So now in your views.py you might do something like this

from django.shortcuts import render_to_response
from django.template import RequestContext
from utils import build_pretty_data_view
from models import Blog
from forms import BlogForm
.
.
def my_view(request):
   b=Blog.objects.get(pk=1)
   bf=BlogForm(instance=b)
   data=build_pretty_data_view(form_instance=bf, model_object=b,
                        exclude=('number_of_comments', 'number_of_likes'),
                        append=('user',))

   return render_to_response('my-template.html',
                          RequestContext(request,
                                         {'data':data,}))

Now in your my-template.html template you can iterate over the data like so…

{% for field,value in data.items %}

    <p>{{ field }} : {{value.label}}: {{value.fieldvalue}}</p>

{% endfor %}

Good Luck. Hope this helps someone!

Answer 11:

Below is mine, inspired by shacker’s get_all_fields.
It gets a dict of one model instance, if encounter relation field, then asign the field value a dict recursively.

def to_dict(obj, exclude=[]):
    """生成一个 dict, 递归包含一个 model instance 数据.
    """
    tree = {}
    for field in obj._meta.fields + obj._meta.many_to_many:
        if field.name in exclude or \
           '%s.%s' % (type(obj).__name__, field.name) in exclude:
            continue

        try :
            value = getattr(obj, field.name)
        except obj.DoesNotExist:
            value = None

        if type(field) in [ForeignKey, OneToOneField]:
            tree[field.name] = to_dict(value, exclude=exclude)
        elif isinstance(field, ManyToManyField):
            vs = []
            for v in value.all():
                vs.append(to_dict(v, exclude=exclude))
            tree[field.name] = vs
        elif isinstance(field, DateTimeField):
            tree[field.name] = str(value)
        elif isinstance(field, FileField):
            tree[field.name] = {'url': value.url}
        else:
            tree[field.name] = value

    return tree

This function is mainly used to dump a model instance to json data:

def to_json(self):
    tree = to_dict(self, exclude=('id', 'User.password'))
    return json.dumps(tree, ensure_ascii=False)

Answer 12:

Instead of editing every model I would recommend to write one template tag which will return all field of any model given.

Every object has list of fields ._meta.fields.
Every field object has attribute name that will return it’s name and method value_to_string() that supplied with your model object will return its value.
The rest is as simple as it’s said in Django documentation.

Here is my example how this templatetag might look like:

    from django.conf import settings
    from django import template

    if not getattr(settings, 'DEBUG', False):
        raise template.TemplateSyntaxError('get_fields is available only when DEBUG = True')


    register = template.Library()

    class GetFieldsNode(template.Node):
        def __init__(self, object, context_name=None):
            self.object = template.Variable(object)
            self.context_name = context_name

        def render(self, context):
            object = self.object.resolve(context)
            fields = [(field.name, field.value_to_string(object)) for field in object._meta.fields]

            if self.context_name:
                context[self.context_name] = fields
                return ''
            else:
                return fields


    @register.tag
    def get_fields(parser, token):
        bits = token.split_contents()

        if len(bits) == 4 and bits[2] == 'as':
            return GetFieldsNode(bits[1], context_name=bits[3])
        elif len(bits) == 2:
            return GetFieldsNode(bits[1])
        else:
            raise template.TemplateSyntaxError("get_fields expects a syntax of "
                           "{% get_fields <object> [as <context_name>] %}")

Answer 13:

Yeah it’s not pretty, you’ll have to make your own wrapper. Take a look at builtin databrowse app, which has all the functionality you need really.

Answer 14:

This may be considered a hack but I’ve done this before using modelform_factory to turn a model instance into a form.

The Form class has a lot more information inside that’s super easy to iterate over and it will serve the same purpose at the expense of slightly more overhead. If your set sizes are relatively small I think the performance impact would be negligible.

The one advantage besides convenience of course is that you can easily turn the table into an editable datagrid at a later date.

References