python - template - django views
Iteramos los nombres y valores de los campos de instancias del modelo en la plantilla (20)
A continuación está el mío, inspirado en get_all_fields de get_all_fields
. Obtiene un dict de una instancia de modelo, si el campo de relación de encuentro, luego asigna el valor de campo a dict recursivamente.
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
Esta función se usa principalmente para volcar una instancia modelo a datos json:
def to_json(self):
tree = to_dict(self, exclude=(''id'', ''User.password''))
return json.dumps(tree, ensure_ascii=False)
Intento crear una plantilla básica para mostrar los valores de campo de la instancia seleccionada, junto con sus nombres. Piense en ello como solo un resultado estándar de los valores de esa instancia en formato de tabla, con el nombre del campo (verbose_name específicamente si se especifica en el campo) en la primera columna y el valor de ese campo en la segunda columna.
Por ejemplo, supongamos que tenemos la siguiente definición de modelo:
class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
Me gustaría que se muestre en la plantilla como tal (suponga una instancia con los valores dados):
Field Name Field Value
---------- -----------
Name Wayne Koorts
E-mail [email protected]
Lo que estoy tratando de lograr es poder pasar una instancia del modelo a una plantilla y ser capaz de iterar dinámicamente en la plantilla, algo como esto:
<table>
{% for field in fields %}
<tr>
<td>{{ field.name }}</td>
<td>{{ field.value }}</td>
</tr>
{% endfor %}
</table>
¿Hay una manera clara y "aprobada por Django" de hacer esto? Parece una tarea muy común, y tendré que hacerlo a menudo para este proyecto en particular.
A la luz del lanzamiento de Django 1.8 (y la formalización de la API de Model _meta , pensé que actualizaría esto con una respuesta más reciente.
Asumiendo el mismo modelo:
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+ (API de modelo _meta formalizada)
Cambiado en Django 1.8:
La API Model
_meta
siempre existió como interna de Django, pero no fue formalmente documentada y admitida. Como parte del esfuerzo para hacer que esta API sea pública, algunos de los puntos de entrada API ya existentes han cambiado ligeramente. Se proporcionó una guía de migración para ayudarlo a convertir su código para usar la nueva API oficial.
En el siguiente ejemplo, utilizaremos el método formalizado para recuperar todas las instancias de campo de un modelo a través de 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'')]
En realidad, me ha llamado la atención que lo anterior está ligeramente por fuera de lo que se necesitaba (¡estoy de acuerdo!). Simple es mejor que complejo. Estoy dejando lo de arriba para referencia. Sin embargo, para mostrar en la plantilla, el mejor método sería usar un ModelForm y pasar en una instancia. Puede iterar sobre el formulario (equivalente a iterar sobre cada uno de los campos del formulario) y usar el atributo de etiqueta para recuperar el nombre detallado del campo modelo, y usar el método de valor para recuperar el valor:
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}
)
Ahora, renderizamos los campos en la plantilla:
<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>
Aquí hay otro enfoque usando un método modelo. Esta versión resuelve los campos de lista de selección / elección, salta los campos vacíos y le permite excluir campos específicos.
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
Luego en tu plantilla:
{% for f in app.get_all_fields %}
<dt>{{f.label|capfirst}}</dt>
<dd>
{{f.value|escape|urlize|linebreaks}}
</dd>
{% endfor %}
Eche un vistazo a la aplicación django-etc . Tiene la etiqueta de la plantilla model_field_verbose_name
para obtener el nombre detallado del campo de las plantillas: http://django-etc.rtfd.org/en/latest/models.html#model-field-template-tags
En lugar de editar cada modelo, recomendaría escribir una etiqueta de plantilla que devolverá todos los campos de cualquier modelo .
Cada objeto tiene una lista de campos ._meta.fields
.
Cada objeto de campo tiene un name
atributo que devolverá su nombre y el método value_to_string()
proporcionado con el object
modelo devolverá su valor.
El resto es tan simple como se dice en la documentación de Django .
Aquí está mi ejemplo de cómo se vería esta plantilla:
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>] %}")
Este enfoque muestra cómo usar una clase como ModelForm de django y una etiqueta de plantilla como {{form.as_table}}, pero hacer que toda la tabla parezca salida de datos, no un formulario.
El primer paso fue subclasificar el widget TextInput de django:
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))
Luego clasifiqué el ModelForm de django para cambiar los widgets predeterminados por versiones de solo lectura:
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"
Esos eran los únicos artilugios que necesitaba. Pero no debería ser difícil extender esta idea a otros widgets.
Esto puede considerarse un truco, pero lo he hecho antes de usar modelform_factory para convertir una instancia de modelo en un formulario.
La clase Form tiene mucha más información interna que es súper fácil de iterar y servirá para el mismo propósito a expensas de un poco más de sobrecarga. Si los tamaños de conjunto son relativamente pequeños, creo que el impacto en el rendimiento sería insignificante.
La única ventaja, además de la conveniencia, es que puede convertir fácilmente la tabla en una cuadrícula de datos editable en una fecha posterior.
Estoy usando esto, 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>
Finalmente encontré una buena solución para esto en la lista de correo de dev :
En la vista agregar:
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})
en la plantilla agrega:
{% for field in object %}
<li><b>{{ field.label }}:</b> {{ field.data }}</li>
{% endfor %}
He creado el siguiente método, que funciona para mí porque en todos los casos el modelo tendrá un ModelForm asociado.
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)
Aquí hay un extracto de la plantilla que estoy usando para esta vista en particular:
<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>
Lo bueno de este método es que puedo elegir en una plantilla, por plantilla, el orden en el que me gustaría mostrar las etiquetas de campo, usando la tupla pasada a GetModelData y especificando los nombres de los campos. Esto también me permite excluir ciertos campos (por ejemplo, una clave externa de usuario) ya que solo los nombres de campo pasados a través de la tupla están integrados en el diccionario final.
No voy a aceptar esto como la respuesta porque estoy seguro de que alguien puede proponer algo más "Djangonic" :-)
Actualización: estoy eligiendo esto como la respuesta final porque es el más simple de los que hace lo que necesito. Gracias a todos los que contribuyeron con las respuestas.
La solución Django 1.7 para mí:
Las variables son exactas para la pregunta, pero definitivamente debes ser capaz de diseccionar este ejemplo
La clave aquí es usar más o menos el .__dict__
del modelo
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)
plantilla :
{% for field in gen_house %}
{% if field != ''_state'' %}
{{ gen_house|getattribute:field }}
{% endif %}
{% endfor %}
en la plantilla utilicé un filtro para acceder al campo en el 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 ""
Ok, sé que esto es un poco tarde, pero desde que me encontré con esto antes de encontrar la respuesta correcta, podría ser otra persona.
De los documentos 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.''}]
Puede hacer que una forma haga el trabajo por usted.
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})
Luego en la plantilla:
<table>
{% for field in form %}
<tr>
<td>{{ field.name }}</td>
<td>{{ field.value }}</td>
</tr>
{% endfor %}
</table>
Puede usar el método values()
de un queryset
, que devuelve un diccionario. Además, este método acepta una lista de campos para subconjuntos. El método values()
no funcionará con get()
, por lo que debe usar filter()
(consulte la link ).
A la view
...
def show(request, object_id):
object = Foo.objects.filter(id=object_id).values()[0]
return render_to_response(''detail.html'', {''object'': object})
En detail.html
...
<ul>
{% for key, value in object.items %}
<li><b>{{ key }}:</b> {{ value }}</li>
{% endfor %}
</ul>
Para una colección de instancias devueltas por filtro:
object = Foo.objects.filter(id=object_id).values() # no [0]
En 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 %}
Puede usar el serializador de conjunto de consultas de python de Django.
Simplemente ponga el siguiente código en su vista:
from django.core import serializers
data = serializers.serialize( "python", SomeModel.objects.all() )
Y luego en la plantilla:
{% for instance in data %}
{% for field, value in instance.fields.items %}
{{ field }}: {{ value }}
{% endfor %}
{% endfor %}
Su gran ventaja es el hecho de que maneja campos de relación.
Para el subconjunto de campos pruebe:
data = serializers.serialize(''python'', SomeModel.objects.all(), fields=(''name'',''size''))
Realmente debería haber una forma integrada de hacer esto. Escribí esta utilidad build_pretty_data_view
que toma un objeto modelo y una instancia de formulario (un formulario basado en su modelo) y devuelve un SortedDict
.
Los beneficios de esta solución incluyen:
- Conserva el orden usando
SortedDict
incorporado deSortedDict
. - Cuando intenta obtener la etiqueta / verbose_name, pero vuelve al nombre del campo si no está definido.
- También tomará opcionalmente una lista
exclude()
de nombres de campo para excluir ciertos campos. - Si su clase de formulario incluye un
Meta: exclude()
, pero aún desea devolver los valores, agregue esos campos a la lista opcionalappend()
.
Para utilizar esta solución, primero agregue este archivo / función en algún lugar, luego impórtelo en su 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
Así que ahora en su views.py
podría hacer algo como esto
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,}))
Ahora en tu my-template.html
puedes repetir los datos como ...
{% for field,value in data.items %}
<p>{{ field }} : {{value.label}}: {{value.fieldvalue}}</p>
{% endfor %}
Buena suerte. ¡Espero que esto ayude a alguien!
Sí, no es bonito, tendrás que hacer tu propia envoltura. Eche un vistazo a la aplicación de databrowse , que tiene toda la funcionalidad que realmente necesita.
Solo una edición de @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
Deje que Django maneje todos los demás campos además de los campos relacionados. Siento que es más estable
Usé https://.com/a/3431104/2022534 pero sustituí el model_to_dict () de Django con esto para poder manejar 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
Tenga en cuenta que lo he simplificado bastante al quitar las partes del original que no necesitaba. Es posible que desee ponerlos de vuelta.
model._meta.get_all_field_names()
le dará todos los nombres de campo del modelo, luego puede usar model._meta.get_field()
para trabajar con el nombre detallado, y getattr(model_instance, ''field_name'')
para obtener el valor de el modelo.
NOTA: model._meta.get_all_field_names()
está en desuso en django 1.9. En su lugar, use model._meta.get_fields()
para obtener los campos del modelo y field.name
para obtener cada nombre de campo.