python template教程 - 迭代模板中的模型实例字段名称和值




template大于 django表格 (17)

我试图创建一个基本模板来显示所选实例的字段值及其名称。 可以将它看作表格格式的实例值的标准输出,第一列中的字段名称(特别是在字段中指定了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          [email protected]

我试图实现的是能够将模型的实例传递给模板,并且能够在模板中动态地迭代它,如下所示:

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

有没有一个整洁的,“Django批准”的方式来做到这一点? 这似乎是一个非常普遍的任务,我需要经常为这个特定的项目做。


Answers

您可以使用querysetvalues()方法,该方法返回一个字典。 此外,此方法接受要在其上进行子集的字段列表。 values()方法不适用于get() ,因此您必须使用filter() (请参阅link )。

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

我使用了https://.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 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 %}

我使用这个, 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>

根据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+(正式版_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>

您可以让表单为您完成工作。

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>

下面是我的,受shacker的 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" 

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


model._meta.get_all_field_names()将为您提供模型的所有字段名称,然后您可以使用model._meta.get_field()model._meta.get_field()详细名称,并使用getattr(model_instance, 'field_name')来获取值该模型。

注意: model._meta.get_all_field_names()在django 1.9中已弃用。 而应使用model._meta.get_fields()来获取模型的字段和field.name以获取每个字段的名称。


实际上应该有一个内置的方法来做到这一点。 我编写了这个实用程序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 %}

祝你好运。 希望这可以帮助别人!


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


这可能被认为是黑客攻击,但在使用modelform_factory将模型实例转换为窗体之前,我已经完成了此操作。

Form类拥有更多的信息,这些信息非常容易迭代,并且它会以相同的目的服务,但费用会稍微增加一些。 如果您的设置尺寸相对较小,我认为性能影响可以忽略不计。

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


我不建议编辑每个模型,而是建议编写一个模板标记 ,它将返回给定模型的所有字段。
每个对象都有字段列表._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。

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

更新:我选择这个作为最终答案,因为它是那些给出的最简单的做我需要的东西。 感谢所有贡献答案的人。



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

在视图中添加:

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

关于什么

g = globals().copy()
for name, obj in g.iteritems():





python django django-templates