Перебирать имена и значения полей экземпляра модели в шаблоне
Я пытаюсь создать базовый шаблон для отображения значений полей выбранного экземпляра вместе с их именами. Думайте об этом как о простом стандартном выводе значений этого экземпляра в формате таблицы с именем поля (особенно 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>
Есть ли аккуратный "одобренный Джанго" способ сделать это? Кажется, это очень распространенная задача, и мне нужно будет делать ее часто для этого конкретного проекта.
24 ответа
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
чтобы получить имя каждого поля.
Вы можете использовать сериализатор наборов запросов 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'))
Наконец нашел хорошее решение для этого в списке рассылки dev:
В представлении добавить:
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})
в шаблон добавьте:
{% for field in object %}
<li><b>{{ field.label }}:</b> {{ field.data }}</li>
{% endfor %}
Вот еще один подход с использованием метода модели. Эта версия разрешает поля выбора / выбора, пропускает пустые поля и позволяет исключать определенные поля.
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 %}
В свете выпуска Django 1.8 (и формализации API Model _meta, я подумал, что обновлю это более поздним ответом.
Предполагая ту же модель:
class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
Джанго<= 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:
Модель
_meta
API всегда существовал как внутренний 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 и передача экземпляра. Вы можете выполнить итерацию по форме (эквивалент итерации по каждому из полей формы) и использовать атрибут label для получения 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>
Хорошо, я знаю, что уже немного поздно, но, поскольку я наткнулся на это, прежде чем найти правильный ответ, может кто-то другой.
# 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.'}]
Вы можете использовать values()
метод queryset
, который возвращает словарь. Кроме того, этот метод принимает список полей для поднабора. values()
метод не будет работать с get()
так что вы должны использовать filter()
(обратитесь к API QuerySet).
В 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]
Подробнее.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 %}
Я использовал /questions/31523222/perebirat-imena-i-znacheniya-polej-ekzemplyara-modeli-v-shablone/31523233#31523233 но заменил 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
Пожалуйста, обратите внимание, что я немного упростил это, удалив части оригинала, которые мне не нужны. Возможно, вы захотите вернуть их обратно.
Вы можете иметь форму сделать работу за вас.
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>
Ниже мой, вдохновленный Шакером get_all_fields
, Он получает диктат одного экземпляра модели, если встречается поле отношения, а затем рекурсивно присваивается значение поля дикту.
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)
Там действительно должен быть встроенный способ сделать это. Я написал эту утилиту 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 %}
Удачи. Надеюсь, это поможет кому-то!
Вместо того, чтобы редактировать каждую модель, я бы рекомендовал написать один тег шаблона, который будет возвращать все поля любой модели.
У каждого объекта есть список полей ._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>] %}")
Да, это не красиво, вам придется сделать свою собственную обертку. Взгляните на встроенное приложение для просмотра данных, которое обладает всеми необходимыми вам функциями.
Это может считаться хаком, но я сделал это перед использованием modelform_factory, чтобы превратить экземпляр модели в форму.
Класс Form содержит намного больше информации, которую очень легко перебирать, и он будет служить той же цели за счет немного больших накладных расходов. Если ваши размеры набора относительно малы, я думаю, что влияние на производительность будет незначительным.
Одно из преимуществ помимо удобства, конечно, заключается в том, что вы можете легко превратить таблицу в редактируемую сетку данных на более позднем этапе.
Я придумал следующий метод, который работает для меня, потому что в каждом случае с моделью будет связан 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, и указав имена полей. Это также позволяет мне исключить определенные поля (например, внешний ключ пользователя), так как в окончательный словарь встроены только имена полей, переданные через кортеж.
Я не собираюсь принимать это как ответ, потому что я уверен, что кто-то может придумать что-то более "Джангоническое":-)
Обновление: я выбираю это в качестве окончательного ответа, потому что это самый простой из тех, что дает то, что мне нужно. Спасибо всем, кто предоставил ответы.
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 ""
Я использую это, 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>
Этот подход показывает, как использовать класс, такой как 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"
Это были единственные виджеты, которые мне были нужны. Но не должно быть сложно распространить эту идею на другие виджеты.
Просто редактирование @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 обрабатывает все остальные поля, кроме связанных полей. Я чувствую что стабильнее
Django >= 2.0
добавлять get_fields()
на ваш models.py
:
class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
def get_fields(self):
return [(field.verbose_name, field.value_from_object(self)) for field in self.__class__._meta.fields]
Тогда назовите это как object.get_fields
на ваше template.html
:
<table>
{% for label, value in object.get_fields %}
<tr>
<td>{{ label }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</table>
Я только что протестировал что-то подобное в оболочке и, похоже, справился со своей задачей:
my_object_mapped = {attr.name: str(getattr(my_object, attr.name)) for attr in MyModel._meta.fields}
Обратите внимание: если вам нужно представление str() для посторонних объектов, вы должны определить его в их методе str. Из этого у вас есть dict значений для объекта. Затем вы можете визуализировать какой-нибудь шаблон или что-то еще.
Если имя вашей модели — «Клиент», и вы получаете клиентский объект по идентификатору, выполните следующие действия.
client = Client.objects.get(id=id)
fields = Client._meta.get_fields()
for field in fields:
value = getattr(client, field.name)
print(field.name)
print(value)
Взгляните на приложение django-etc. Она имеет model_field_verbose_name
тег шаблона для получения подробного имени поля из шаблонов: http://django-etc.rtfd.org/en/latest/models.html
<table border='1'>
<tr>
{% for mfild in fields%}
<td>{{mfild}}</td>
{% endfor%}
</tr>
{%for v in records%}
<tr>
<td>{{v.id}}</td>
<td>{{v.title}}</td>
<td class="">{{v.desc}}</td>
</tr>
{% endfor%}
</table>
enter code here