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


183

我正在尝试创建一个基本模板以显示所选实例的字段值及其名称。可以将其视为表格式的该实例的值的标准输出,第一列具有字段名称(如果在字段上指定,则为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批准的简洁方法?这似乎是一项非常普通的任务,对于这个特定项目,我将需要经常执行。

Answers:


171

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
这仍然是非常手动的,我必须在视图中建立某种元对象,然后将其传递到模板中,这比我想要的更多。肯定有更整洁的方法吗?
韦恩·科特斯

2
您可以将所有这些封装在类中,就像ModelForm一样。
伊格纳西奥·巴斯克斯

18
我不相信您可以在模板中调用任何_方法。
Issac Kelly

2
这可以工作,但是您不应该依赖私有API(因为它以“ _”作为前缀)来实现它。依赖私有API的问题是不能保证私有方法在各个版本之间都可以使用。
Devy 2014年

1
我认为不应该使用这种方法,因为我们不应该从模板中使用下划线开头的属性
GP92,18年

72

您可以使用Django的to-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'))

太酷了-但是使用这种方法,如何将结果限制为仅某些字段?
Herman Schaaf

2
这应该是确定的答案,可以处理外键,并且不能进行私有api调用。好答案,谢谢。
云蒂2015年

3
不必使用序列化。您可以使用querysetvalues()方法,该方法返回一个字典。此外,此方法接受要作为子集的字段列表。见链接。查看我的完整答案。
user3062149 '16

我们可以将其更新为仅发送.fields而不用循环处理吗?我不想公开模型/表的名称
Loser Coder

此方法是否允许verbose_name传递字段的?
alias51

70

终于在开发邮件列表中找到了一个很好的解决方案:

在视图中添加:

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

1
好的解决方案,但不是很通用,因为它不会为ForeignKey字段返回model_to_dict,而是返回unicode结果,因此您无法轻松地将复杂对象序列化为dict
Vestel 2010年

22
有危险地覆盖对象,应该使用其他可变名称。
EmilStenström,2012年

谢谢!我替换了Django的model_to_dict()以便能够处理ForeignKey。请查看我的单独答案(由于注释不支持代码格式,我删除了之前的注释。抱歉,我不知道。)
Magnus Gustavsson

2
这里假设FooFormModelForm,是不是很容易,只需做:FooForm(instance=Foo.objects.get(pk=object_id)))
比利时

知道如何使用这种方法仅显示可编辑字段吗?
alias51

22

鉴于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 _metaAPI一直是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>
 

2
如果您要调整答案以显示将模型字段放入模板的“> 1.8”方式,那就太好了。目前,您的答案还没有直接回答问题。它显示了如何在外壳中获取模型的字段。
埃舍尔

@Escher-更新了答案!谢谢你的建议。让我知道我是否错过任何事情/弄错了什么!
Michael B

已投票。我进行了编辑,以包括打印值和字段名称。看看你的想法。
埃舍尔

您在哪里打印这些值?我只看到它打印名称和verbose_name吗?
Ernie博士

@MichaelB嗯。我无法使“ field.value”正常工作;这些字段似乎是数据库字段,而不是实际的列数据。我必须使用一个名为getattr(object,name)的过滤器。它适合您使用哪个Django版本?
Ernie博士

19

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

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

3
为什么需要except User.DoesNotExist:
七人制

我倾向于使用AttributeError代替User.DoesNotExist-我不明白为什么它会抛出User.DoesNotExist。
askvictor

另外,最好使用self._meta.get_fields(),因为这在django 1.8+中已正式公开。但是,您最终遇到了代码中的关系,您必须通过检查f.is_relation来过滤掉
askvictor

我已经将答案编辑为使用AttributeError而不是User.DoesNotExist(这是我原始实现的剩余内容)。谢谢。我一直_meta.get_fields()等到可以测试为止。
shacker's

13

好的,我知道这有点晚了,但是由于我在找到正确答案之前偶然发现了这个,其他人也可能会发现。

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

我喜欢这个答案。如果查询返回的则是一条记录,而您只想要最新一条,请执行以下操作。1.确保你ordering = ['-id']class Meta:你的对象models.py。2.然后使用Blog.objects.filter(name__startswith='Beatles').values()[0]
Sevenearths,2012年

聪明的主意。但是,如果您已经有一个model对象,那么您将再次点击数据库以获取字段。可以解决吗?
frnhr 2014年

@ user1763732只需检查QuerySet的文档:docs.djangoproject.com/en/dev/ref/models/querysets
olofom 2015年

9

您可以使用的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 %}

太神奇了,谢谢!我有一个问题,如果你能帮助我的话;我将所有对象数据放入table,因此需要将每个key放入th。我该如何做到没有循环?只需获取任何对象实例并对其进行遍历就可以了key?目前我单独传递model_to_dict(Model())th,但我认为这是不必要的对象实例化。
Oxwivi '16

很棒的答案。就个人而言,我在列表视图和详细视图中都使用了它。列表视图在很大程度上很容易实现,但是在细节视图中,我get_object在细节视图上进行了覆盖(由于注释中的内联代码限制,因此可能会杂乱无章,考虑到该线程的饱和程度,它不足以满足自己的回答): def get_object(self, **kwargs): obj = super().get_object(**kwargs) obj = obj.__class__.objects.filter(pk=obj.pk).values()[0] return obj
sdconrox

您将如何添加obj.get_absolute_url到此列表而不重复行?
alias51

8

我使用了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

请注意,我已经删除了不需要的部分,从而大大简化了它。您可能要放回去。


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>

3
此方法(在中使用DetailView)对我来说效果很好。但是,您可能希望使用field.label而不是field.name
David Cain 2014年

7

确实应该有一种内置方法来执行此操作。我编写了此实用程序build_pretty_data_view,该实用程序接受模型对象和表单实例(基于您的模型的表单)并返回SortedDict

该解决方案的优势包括:

  • 它使用Django的内置命令保留命令 SortedDict
  • 尝试获取标签/详细名称时,但如果未定义则回退到字段名称。
  • 也可以选择 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 %}

祝好运。希望这对某人有帮助!


7

下面是我的,受到黑客的 启发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)

做得好!建议增加选择支持... elif hasattr(field,'choices'):tree [field.name] = dict(field.choices).get(value,value)
关东煮

5

我建议不要编写每个模型,而是建议编写一个模板标签,该标签将返回给定模型的所有字段。
每个对象都有字段列表._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>] %}")

4

是的,这并不漂亮,您必须自己制作包装纸。看一看内置的数据浏览应用程序,它具有您真正需要的所有功能。


我要说的是...。虽然我发现它完全是没用的应用程序,但是databrowse只是这样做。
mpen 2010年

4

这可能被认为是黑客,但是在使用modelform_factory将模型实例转换为表单之前,我已经做到了。

Form类内部有很多信息,这些信息非常容易迭代,并且可以达到相同的目的,但开销会稍有增加。如果您的设备尺寸相对较小,我认为对性能的影响可以忽略不计。

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


4

我想出了以下方法,该方法对我有用,因为在每种情况下,模型都会有一个与之关联的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”的概念:-)

更新:我选择此作为最终答案,因为这是我所需要的,是最简单的答案。感谢所有贡献答案的人。


3

我的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
filter.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

我正在使用它,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>

2

这种方法显示了如何使用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" 

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


1

只需编辑@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

我只是在shell中测试了类似的东西,似乎可以做到这一点:

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

请注意,如果要用str()表示异物,则应在其str方法中对其进行定义。由此,您可以确定对象的值。然后,您可以渲染某种模板或其他任何模板。


0

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>

-1

<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


1
您好,欢迎来到。请不要只发布代码答案。同样,这个问题已经有了一个可以接受的答案,您的代码格式不正确,正在使用不推荐使用的html属性,最重要的是:您没有在解释代码如何提供比被接受的更好的解决方案。
弗里德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.