首页 文章

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

提问于
浏览
159

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

20 回答

  • 0

    是的's not pretty, you' ll必须制作你自己的包装 . 看看builtin databrowse app,它具有你真正需要的所有功能 .

  • 151

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

    Form类里面有很多更容易迭代的信息,它会以更多的开销为代价来实现相同的目的 . 如果您的设定尺寸相对较小,我认为性能影响可以忽略不计 .

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

  • 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
    

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

  • 8

    我建议写 one template tag 而不是编辑每个模型,这将返回 any model 给出的所有字段 .
    每个对象都有字段列表 ._meta.fields .
    每个字段对象都有属性 name ,它将返回它的名称,并且随模型 object 提供的方法 value_to_string() 将返回其值 .
    剩下的就像在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>] %}")
    
  • 61

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

  • 62

    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)
    

    template

    {% 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 ""
    
  • 21

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

  • 4

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

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

  • 12

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

    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

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

    终于在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 %}
    
  • 2

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

    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>
    
  • 2

    以下是我的,灵感来自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)
    
  • 7

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

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

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

    对于过滤器返回的 collection of instances

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

    详细地说......

    {% for instance in object %}
    <h1>{{ instance.id }}</h1>
    <ul>
        {% for key, value in instance.items %}
            <li><b>{{ key }}:</b>  {{ value }}</li>
        {% endfor %}
    </ul>
    {% endfor %}
    
  • 5

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

    Update: 我选择这个作为最后的答案,因为这是我所需要的最简单的 . 感谢所有贡献答案的人 .

  • 4

    这种方法显示了如何使用类似django的ModelForm和类似{}的模板标记,但是所有表看起来都像数据输出,而不是表单 .

    第一步是继承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"
    

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

  • 7

    根据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() 利用retrieving all field instances of a model的形式化方法:

    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>
    
  • 16

    我正在使用这个,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>
    
  • 8

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

    祝好运 . 希望这有助于某人!

相关问题