迭代模板中的模型实例字段名称和值
我正在尝试创build一个基本的模板来显示所选实例的字段值及其名称。 可以把它看作表格格式的实例值的标准输出,第一列中的字段名称(特别是在字段中指定了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
我想要实现的是能够将模型的实例传递给模板,并能够在模板中dynamic地迭代它,如下所示:
<table> {% for field in fields %} <tr> <td>{{ field.name }}</td> <td>{{ field.value }}</td> </tr> {% endfor %} </table>
有没有一个干净的,“Django认可”的方式来做到这一点? 这似乎是一个非常普遍的任务,我将需要经常为这个特定的项目。
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
来获取每个字段的名称。
最后在开发邮件列表中find了一个很好的解决scheme:
在视图中添加:
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 %}
你可以使用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'))
根据Django 1.8的发布(和_meta API的forms化,我想我会更新一个更近的答案。
假设相同的模型:
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
_meta
API一直以Django的内部forms存在,但没有正式logging和支持。 作为公开这个API的努力的一部分,一些已经存在的API入口点已经稍微改变了。 已经提供了迁移指南来帮助您将代码转换为使用新的官方API。
在下面的例子中,我们将通过Client._meta.get_fields()
来使用forms化方法来检索模型的所有字段实例 :
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>
这是使用模型方法的另一种方法。 此版本parsing选取列表/select字段,跳过空字段,并让您排除特定的字段。
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 %}
好吧,我知道这有点晚了,但是因为在find正确的答案之前我偶然发现了,所以可能有其他人。
从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.'}]
应该有一个内置的方法来做到这一点。 我编写了这个实用程序build_pretty_data_view
,它接受一个模型对象和表单实例(基于您的模型的表单)并返回一个SortedDict
。
这个解决scheme的好处包括:
- 它使用Django内置的
SortedDict
来保存顺序。 - 尝试获取标签/ verbose_name时,如果没有定义,则会回退到字段名称。
- 它也可以select
exclude()
列表的字段名称来排除某些字段。 - 如果您的表单类包含一个
Meta: exclude()
,但您仍然希望返回这些值,请将这些字段添加到可选的append()
列表中。
要使用这个解决scheme,首先添加这个文件/function,然后将其导入到您的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 %}
祝你好运。 希望这可以帮助别人!
你可以有一个表格为你做的工作。
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
启发。 它得到一个模型实例的字典,如果遇到关系字段,则recursion地赋值字段值字典。
def to_dict(obj, exclude=[]): """生成一个 dict, recursion包含一个 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)
我用https://stackoverflow.com/a/3431104/2022534,但用这个replaceDjango的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
请注意,我通过删除原来不需要的部分来简化了它。 你可能想把这些放回去。
你可以使用queryset
的values()
方法,它返回一个字典。 此外,这个方法接受一个子集列表。 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>
对于filter返回的实例集合 :
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 %}
我想出了下面的方法,这对我来说很有用,因为在任何情况下,模型都会有一个与之相关的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, eg 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>
关于这个方法的好处是,我可以根据模板select我希望显示字段标签的顺序,使用传递给GetModelData的元组并指定字段名称。 这也允许我排除某些字段(例如用户外键),因为只有通过元组传入的字段名被构build到最终字典中。
我不会接受这个答案,因为我确定有人可以想出更多的“Djangonic”:-)
更新:我select这个作为最终的答案,因为这是最简单的那些给我所需要的。 感谢所有贡献答案的人。
我不build议编辑每个模型,而是build议编写一个模板标签 ,它将返回给定模型的所有字段。
每个对象都有字段列表._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>] %}")
是的,这不是很好,你必须做你自己的包装。 看看内置的databrowse应用程序,它具有你所需要的所有function。
这可能被认为是一个黑客,但我之前使用modelform_factory将模型实例转换为一个窗体。
Form类有更多的信息,这是非常容易迭代的,它将以相同的目的而牺牲稍微更多的开销。 如果你的规模相对较小,我认为性能影响可以忽略不计。
当然,除了方便之外,还有一个好处就是你可以在晚些时候把表格变成一个可编辑的数据网格。
Django 1.7解决scheme:
这个问题的variables是确切的,但你应该能够剖析这个例子
这里的关键几乎是使用模型的.__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 %}
在模板中,我使用了一个filter来访问字典中的字段
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 ""
我使用这个, 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的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来replace只读版本的默认小部件:
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"
那些是我需要的唯一部件。 但是把这个想法扩展到其他小部件应该不难。
只是@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处理除相关字段以外的所有其他字段。 我觉得更稳定
看看django-etc应用程序。 它具有model_field_verbose_name
模板标签以从模板中获取字段详细名称: http : model_field_verbose_name