Перебирать имена и значения полей экземпляра модели в шаблоне - PullRequest
168 голосов
/ 31 января 2010

Я пытаюсь создать базовый шаблон для отображения значений полей выбранного экземпляра вместе с их именами. Думайте об этом как о стандартном выводе значений этого экземпляра в формате таблицы с именем поля (особенно 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          waynes@email.com

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

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

Есть ли аккуратный "одобренный Джанго" способ сделать это? Кажется, это очень распространенная задача, и мне придется часто ее выполнять для этого конкретного проекта.

Ответы [ 20 ]

7 голосов
/ 14 октября 2011

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

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

  • Сохраняет порядок, используя встроенную в Django SortedDict.
  • Когда пытается получить метку / 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 %}

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

5 голосов
/ 23 августа 2011

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

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

    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>] %}")
4 голосов
/ 01 февраля 2010

Я придумал следующий метод, который работает для меня, потому что в каждом случае с моделью будет связан 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": -)

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

4 голосов
/ 31 января 2010

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

4 голосов
/ 13 февраля 2010

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

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

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

3 голосов
/ 10 января 2015

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 ""
2 голосов
/ 25 октября 2012

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

Первый шагдолжен был создать подкласс виджета TextInput в django:

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

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

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" 

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

2 голосов
/ 14 апреля 2011

Я использую это, 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>
1 голос
/ 08 сентября 2016

Просто редактирование @ 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 обрабатывает все остальные поля, кроме связанных полей. Я чувствую что стабильнее

0 голосов
/ 22 октября 2014

Взгляните на приложение django-etc . У него есть model_field_verbose_name тег шаблона для получения подробного имени поля из шаблонов: http://django -etc.rtfd.org / en / latest / models.html # model-field-template-tags

...