Итерировать имена и значения полей экземпляра модели в шаблоне

Я пытаюсь создать базовый шаблон для отображения значений полей выбранного экземпляра вместе с их именами. Подумайте об этом как о стандартном выводе значений этого экземпляра в формате таблицы с именем поля (verbose_name, особенно если указано в поле) в первом столбце и значением этого поля во втором столбце.

Например, допустим, мы имеем следующее определение модели:

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

Я хотел бы, чтобы он был выведен в шаблоне так же (предположим экземпляр с заданными значениями):

Field Name      Field Value
----------      -----------
Name            Wayne Koorts
E-mail          [email protected]

То, что я пытаюсь достичь, - это передать экземпляр модели шаблону и иметь возможность динамически перебирать его в шаблоне, примерно так:

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

Есть ли опрятный "одобренный Django" способ сделать это? Это похоже на очень общую задачу, и мне нужно будет делать это часто для этого конкретного проекта.

Ответ 1

model._meta.get_all_field_names() предоставит вам все имена полей модели, затем вы можете использовать model._meta.get_field() для работы с подробным именем и getattr(model_instance, 'field_name') для получения значения из модели.

ПРИМЕЧАНИЕ: model._meta.get_all_field_names() устарел в django 1.9. Вместо этого используйте model._meta.get_fields(), чтобы получить поля модели и field.name, чтобы получить каждое имя поля.

Ответ 2

Наконец, нашел хорошее решение для этого в списке рассылки dev:

В представлении 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})

в шаблоне add:

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

Ответ 3

Вы можете использовать Django to-python сериализатор запросов.

Просто введите следующий код:

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

И затем в шаблоне:

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

Его большое преимущество заключается в том, что он обрабатывает поля отношений.

Для подмножества полей попробуйте:

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

Ответ 4

В свете выпуска Django 1.8 (и формализации Model _meta API я решил, что обновляю это с помощью более позднего ответа.

Предполагая ту же модель:

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+ (формализованная модель _meta API)

Изменено в Django 1.8:

API Model _meta всегда существовал как внутренний Django, но формально не документирован и не поддерживался. В рамках усилий по открытию этого API некоторые из уже существующих точек входа API немного изменились. Руководство по миграции было предоставлено, чтобы помочь в преобразовании вашего кода для использования нового официального API.

В приведенном ниже примере мы будем использовать формализованный метод для получения всех экземпляров полей модели через 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')]

Собственно, мне было доведено до сведения, что приведенное выше немного за борт за то, что нужно (я согласен!). Простой лучше, чем сложный. Я оставляю выше для справки. Однако для отображения в шаблоне лучшим методом было бы использовать ModelForm и передать экземпляр. Вы можете перебирать форму (эквивалент итерации по каждому из полей формы) и использовать атрибут метки для извлечения verbose_name для поля модели и использовать метод 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}
    )

Теперь мы визуализируем поля в шаблоне:

<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>

Ответ 5

Здесь другой подход с использованием модельного метода. Эта версия разрешает поля выбора/выбора, пропускает пустые поля и позволяет исключать определенные поля.

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

Затем в вашем шаблоне:

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

Ответ 6

Хорошо, я знаю, что это немного поздно, но так как я наткнулся на это, прежде чем найти правильный ответ, так может кто-то еще.

Из 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.'}]

Ответ 7

Там действительно должен быть встроенный способ сделать это. Я написал эту утилиту build_pretty_data_view, которая принимает экземпляр модели и экземпляр формы (форму на основе вашей модели) и возвращает SortedDict.

Преимущества для этого решения включают:

  • Он сохраняет порядок, используя встроенный SortedDict Django.
  • При попытке получить метку /verbose _name, но возвращается к имени поля, если оно не определено.
  • Кроме того, он также может выбрать список имен полей exclude() для исключения определенных полей.
  • Если ваш класс формы содержит Meta: exclude(), но вы все равно хотите вернуть значения, добавьте эти поля в дополнительный append() список.

Чтобы использовать это решение, сначала добавьте этот файл/функцию, а затем импортируйте его в свой 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

Итак, теперь в вашем views.py вы можете сделать что-то вроде этого

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,}))

Теперь в вашем шаблоне my-template.html вы можете перебирать данные так:

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

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

{% endfor %}

Удачи. Надеюсь, это поможет кому-то!

Ответ 8

У вас может быть форма для вас.

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})

Затем в шаблоне:

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

Ответ 9

Ниже мой, вдохновленный трюком get_all_fields. Он получает диктовку одного экземпляра модели, если встречается поле отношения, затем присваивает значение поля a dict рекурсивно.

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

Эта функция в основном используется для вывода экземпляра модели в json-данные:

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

Ответ 10

Я использовал fooobar.com/questions/43044/..., но заменил Django model_to_dict() на это, чтобы иметь возможность обрабатывать 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

Обратите внимание, что я немного упростил его, удалив части оригинала, которые мне не нужны. Возможно, вы захотите вернуть их.

Ответ 11

Вы можете использовать метод values() для queryset, который возвращает словарь. Кроме того, этот метод принимает список полей для подмножества. Метод values() не будет работать с get(), поэтому вы должны использовать filter() (см. QuerySet API).

В view...

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

В detail.html...

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

Для коллекции экземпляров, возвращаемых фильтром:

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

В 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 %}

Ответ 12

Я придумал следующий метод, который работает для меня, потому что в каждом случае модель будет иметь связанный с ним ModelForm.

def GetModelData(form, fields):
    """
    Extract data from the bound form model instance and return a
    dictionary that is easily usable in templates with the actual
    field verbose name as the label, e.g.

    model_data{"Address line 1": "32 Memory lane",
               "Address line 2": "Brainville",
               "Phone": "0212378492"}

    This way, the template has an ordered list that can be easily
    presented in tabular form.
    """
    model_data = {}
    for field in fields:
        model_data[form[field].label] = eval("form.data.%s" % form[field].name)
    return model_data

@login_required
def clients_view(request, client_id):
    client = Client.objects.get(id=client_id)
    form = AddClientForm(client)

    fields = ("address1", "address2", "address3", "address4",
              "phone", "fax", "mobile", "email")
    model_data = GetModelData(form, fields)

    template_vars = RequestContext(request,
        {
            "client": client,
            "model_data": model_data
        }
    )
    return render_to_response("clients-view.html", template_vars)

Вот выдержка из шаблона, который я использую для этого конкретного вида:

<table class="client-view">
    <tbody>
    {% for field, value in model_data.items %}
        <tr>
            <td class="field-name">{{ field }}</td><td>{{ value }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>

Хорошая вещь об этом методе заключается в том, что я могу выбирать по шаблону по шаблону порядок, в котором я хотел бы отображать метки полей, используя кортеж, переданный в GetModelData и определяющий имена полей. Это также позволяет мне исключать определенные поля (например, внешний ключ пользователя), так как только имена полей, переданные через кортеж, встроены в окончательный словарь.

Я не собираюсь принимать это как ответ, потому что я уверен, что кто-то может придумать что-то более "Djangonic": -)

Обновление: Я выбираю это как окончательный ответ, потому что это самый простой из приведенных, который делает то, что мне нужно. Спасибо всем, кто внес ответы.

Ответ 13

Вместо редактирования каждой модели я бы рекомендовал написать один тег шаблона, который вернет все поле любой модели.
Каждый объект имеет список полей ._meta.fields.
Каждый объект поля имеет атрибут name, который вернет его имя и метод value_to_string(), который поставляется с вашей моделью object, вернет его значение.
Остальное так же просто, как сказано в документации Django.

Вот мой пример, как выглядит это templatetag:

    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>] %}")

Ответ 14

Да, это не очень, вам придется сделать свою собственную обертку. Взгляните на встроенное databrowse приложение, в котором есть все необходимые функциональные возможности.

Ответ 15

Это можно считать взломом, но я сделал это до использования modelform_factory, чтобы превратить экземпляр модели в форму.

Класс Form имеет намного больше информации внутри этого супер простого итерации, и он будет служить той же цели за счет немного большего количества накладных расходов. Если размеры вашего набора относительно малы, я думаю, что влияние производительности будет незначительным.

Единственное преимущество, помимо удобства, состоит в том, что вы можете легко превратить таблицу в редактируемый файл данных позже.

Ответ 16

Решение Django 1.7 для меня:

Здесь переменные являются точными для вопроса, но вы определенно сможете проанализировать этот пример

Ключевым моментом здесь является использование .__dict__ модели
views.py

def display_specific(request, key):
  context = {
    'question_id':question_id,
    'client':Client.objects.get(pk=key).__dict__,
  }
  return render(request, "general_household/view_specific.html", context)

шаблон

{% for field in gen_house %}
    {% if field != '_state' %}
        {{ gen_house|getattribute:field }}
    {% endif %}
{% endfor %}

в шаблоне я использовал фильтр для доступа к полю в dict filters.py

@register.filter(name='getattribute')
def getattribute(value, arg):
  if value is None or arg is None:
    return ""
  try:
    return value[arg]
  except KeyError:
    return ""
  except TypeError:
    return ""

Ответ 17

Я использую это, https://github.com/miracle2k/django-tables.

<table>
<tr>
    {% for column in table.columns %}
    <th><a href="?sort={{ column.name_toggled }}">{{ column }}</a></th>
    {% endfor %}
</tr>
{% for row in table.rows %}
    <tr>
    {% for value in row %}
        <td>{{ value }}</td>
    {% endfor %}
    </tr>
{% endfor %}
</table>

Ответ 18

Этот подход показывает, как использовать класс типа django ModelForm и тег шаблона, например {{form.as_table}}, но все таблицы выглядят как вывод данных, а не форма.

Первым шагом был подкласс django TextInput widget:

from django import forms
from django.utils.safestring import mark_safe
from django.forms.util import flatatt

class PlainText(forms.TextInput):
    def render(self, name, value, attrs=None):
        if value is None:
            value = ''
        final_attrs = self.build_attrs(attrs)
        return mark_safe(u'<p %s>%s</p>' % (flatatt(final_attrs),value))

Затем я подклассифицировал django ModelForm для замены виджетов по умолчанию для версий с версией:

from django.forms import ModelForm

class ReadOnlyModelForm(ModelForm):
    def __init__(self,*args,**kwrds):
        super(ReadOnlyModelForm,self).__init__(*args,**kwrds)
        for field in self.fields:
            if isinstance(self.fields[field].widget,forms.TextInput) or \
               isinstance(self.fields[field].widget,forms.Textarea):
                self.fields[field].widget=PlainText()
            elif isinstance(self.fields[field].widget,forms.CheckboxInput):
                self.fields[field].widget.attrs['disabled']="disabled" 

Это были единственные виджеты, которые мне нужны. Но не обязательно будет распространять эту идею на другие виджеты.

Ответ 19

Просто редактирование @wonder

def to_dict(obj, exclude=[]):
    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 as e:
            value = None
        except ObjectDoesNotExist as e:
            value = None
            continue
        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
        else:
            tree[field.name] = obj.serializable_value(field.name)
    return tree

Пусть Django обрабатывает все другие поля, отличные от связанных полей. Я чувствую, что более стабильный