迭代模板中的模型实例字段名称和值

时间:2010-01-31 01:48:11

标签: python django django-templates

我正在尝试创建一个基本模板来显示所选实例的字段值及其名称。可以将其视为表格格式中该实例值的标准输出,第一列中包含字段名称(如果在字段中指定,则具体为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批准”的方式来做到这一点?这似乎是一项非常常见的任务,我需要经常为这个特定的项目做这件事。

23 个答案:

答案 0 :(得分:161)

model._meta.get_all_field_names()会为您提供所有模型的字段名称,然后您可以使用model._meta.get_field()来查找详细名称,并使用getattr(model_instance, 'field_name')从模型中获取值。< / p>

注意:django 1.9中不推荐使用model._meta.get_all_field_names()。而是使用model._meta.get_fields()获取模型的字段,使用field.name获取每个字段名称。

答案 1 :(得分:65)

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

答案 2 :(得分:63)

最后在the dev mailing list上找到了一个很好的解决方案:

在视图中添加:

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

答案 3 :(得分:22)

鉴于Django 1.8的发布(以及Model _meta API的正式化,我想我会用更新的答案来更新它。

假设模型相同:

n=1;
d=50;
A=1.5;
im=imread('imagex.jpg');
h=size(im,1);
w=size(im,2);
[x y]=meshgrid(-floor(w/2):floor(w-1/2),-floor(h/2):floor(h-1/2));
hhp=(1./(d./(x.^2+y.^2).^0.5).^(2*n));
image_2Dfilter=fftshift(fft2(im));
Image_butterworth=image_2Dfilter;
imshow(Image_butterworth);
ifftshow(Image_butterworth);

Django&lt; = 1.7

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

Django 1.8+(形式化模型_meta API)

  

更改了Django 1.8:

     

模型 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')] API始终作为Django内部存在,但未正式记录和支持。作为公开此API的努力的一部分,一些现有的API入口点略有改变。我们提供了迁移指南,以帮助您转换代码以使用新的官方API。

在下面的示例中,我们将通过_meta使用retrieving all field instances of a model的形式化方法:

Client._meta.get_fields()

实际上,我已经注意到上面的内容略显不足以满足需要(我同意!)。简单比复杂更好。我将离开以上内容以供参考。但是,要在模板中显示,最好的方法是使用ModelForm并传入实例。您可以遍历表单(相当于迭代每个表单的字段)并使用label属性检索模型字段的verbose_name,并使用value方法检索值:

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

现在,我们在模板中渲染字段:

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

答案 4 :(得分:17)

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

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

答案 5 :(得分:13)

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

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

答案 6 :(得分:8)

您可以使用values()的{​​{1}}方法,该方法返回字典。此外,此方法接受要子集的字段列表。 queryset方法不适用于values(),因此您必须使用get()(请参阅QuerySet API)。

filter() ...

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>

detail.html ...

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

答案 7 :(得分: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>

答案 8 :(得分:7)

应该有一种内置的方法来做到这一点。我编写了这个实用程序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 %}
祝你好运。希望这有助于某人!

答案 9 :(得分:7)

以下是我的,受 shacker's 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)

答案 10 :(得分:7)

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

请注意,我已经通过删除我不需要的原件部分进行了简化。你可能想把它们放回去。

答案 11 :(得分:5)

我建议编写一个模板标记,而不是编辑每个模型,这将返回给定的任何模型的所有字段。
每个对象都有字段列表._meta.fields 每个字段对象都有属性name,它将返回其名称,并且随模型value_to_string()提供的方法object将返回其值。
剩下的就像Django documentation中所说的一样简单。

以下是此模板标签的示例:

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

答案 12 :(得分:4)

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

答案 13 :(得分:4)

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

Form类中有更多的信息,这些信息非常易于迭代,它将以相同的目的服务于更多的开销。如果您的设定尺寸相对较小,我认为性能影响可以忽略不计。

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

答案 14 :(得分: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”: - )

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

答案 15 :(得分: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中的字段 的 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 ""

答案 16 :(得分: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" 

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

答案 17 :(得分: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>

答案 18 :(得分: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处理除相关字段以外的所有其他字段。我觉得这更稳定了

答案 19 :(得分:0)

查看django-etc申请。它具有model_field_verbose_name模板标记,可从模板中获取字段详细名称:http://django-etc.rtfd.org/en/latest/models.html#model-field-template-tags

答案 20 :(得分:0)

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

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

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

答案 21 :(得分: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>

答案 22 :(得分:0)

<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