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


9 Answers

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

我试图创建一个基本模板来显示所选实例的字段值及其名称。 可以将它看作表格格式的实例值的标准输出,第一列中的字段名称(特别是在字段中指定了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批准”的方式来做到这一点? 这似乎是一个非常普遍的任务,我需要经常为这个特定的项目做。




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

在模板中,我使用了一个过滤器来访问字典中的字段
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 ""






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

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文档

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



根据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>



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

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

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




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



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



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

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




Related