遍历模板中的模型实例字段名和值

我试图创建一个基本模板来显示所选实例的字段值及其名称。可以把它看作是该实例的表格式值的标准输出,字段名(如果在字段中指定了,则为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>

有没有一种简洁的、“django认可的”方式来做到这一点?这似乎是一个非常常见的任务,我将需要经常做这个特定的项目。

204082 次浏览

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来获取每个字段的名称。

是的,它不漂亮,你必须自己做包装。看看内置的databrowse应用程序,它拥有你真正需要的所有功能。

我提出了以下方法,这对我来说是有效的,因为在每种情况下,模型都有一个与之关联的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的元组并指定字段名,在一个模板的基础上选择我希望显示字段标签的顺序。这也允许我排除某些字段(例如User外键),因为只有通过元组传入的字段名才会构建到最终的字典中。

我不会接受这个答案,因为我相信有人会想出更“Djangonic”的东西:-)

更新:我选择这个作为最终答案,因为它是最简单的给出了我所需要的。感谢每一位提供答案的人。

这是另一种使用模型方法的方法。此版本解析选择列表/选择字段,跳过空字段,并允许您排除特定字段。

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

这可能被认为是一种hack,但我在使用modelform_factory将模型实例转换为表单之前已经这样做过。

Form类内部有更多的信息,非常容易迭代,它将达到同样的目的,代价是略多一些开销。如果您的设置尺寸相对较小,我认为性能影响可以忽略不计。

当然,除了方便之外,另一个优点是您可以在以后轻松地将表转换为可编辑的数据网格。

你可以使用Django的python queryset序列化器。

只需在视图中放入以下代码:

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

最后在开发人员邮件列表上找到了一个很好的解决方案:

在视图中添加:

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

我正在使用这个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>
与其编辑每个模型,我建议编写一个模板标签,它将返回给定的任何模型的所有字段。< br > 每个对象都有字段列表._meta.fields.
每个字段对象都有属性name,它将返回它的名称,方法value_to_string()将返回它的值,方法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>] %}")

应该有一个内置的方法来做到这一点。我写了这个实用程序build_pretty_data_view,它接受一个模型对象和一个表单实例(一个基于你的模型的表单),并返回一个SortedDict

该解决方案的好处包括:

  • 它使用Django内置的SortedDict来保持秩序。
  • When尝试获取标签/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 %}

祝你好运。希望这能帮助到一些人!

好吧,我知道这有点晚了,但因为我在找到正确答案之前偶然发现了这个问题,所以其他人可能也会这么做。

django文档:

# 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.'}]
下面是我的,灵感来自小屋的 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)

这个方法展示了如何使用像django的ModelForm这样的类和像\{\{form这样的模板标签。As_table}},但是让所有的表看起来像数据输出,而不是一个表单。

第一步是子类化django的TextInput小部件:

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"

这些是我唯一需要的小部件。但是将这个想法扩展到其他小部件应该不难。

我使用https://stackoverflow.com/a/3431104/2022534,但用这个替换了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>

看一下django-etc应用程序。它有model_field_verbose_name模板标记,从模板中获取字段详细名称:http://django-etc.rtfd.org/en/latest/models.html#model-field-template-tags

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

鉴于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+(形式化的Model _meta API)

在Django 1.8中更改:

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

你可以使用querysetvalues()方法,它返回一个字典。此外,该方法接受一个字段列表来进行子集。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 %}

只是@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处理除相关字段之外的所有其他字段。我觉得这样更稳定

我刚刚在shell中测试了这样的东西,似乎做了它的工作:

my_object_mapped = {attr.name: str(getattr(my_object, attr.name)) for attr in MyModel._meta.fields}

注意,如果你想要str()表示外部对象,你应该在它们的str方法中定义它。从那里你有对象的值字典。然后你可以渲染一些模板之类的东西。

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]

然后在你的template.html上调用它为object.get_fields:

<table>
{% for label, value in object.get_fields %}
<tr>
<td>\{\{ label }}</td>
<td>\{\{ value }}</td>
</tr>
{% endfor %}
</table>

<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

如果你的模型名称是客户端,你通过id获取客户端对象,然后像这样进行

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)