J'essaie de créer un modèle de base pour afficher les valeurs de champ de l'instance sélectionnée, ainsi que leurs noms. Considérez-le simplement comme une sortie standard des valeurs de cette instance sous forme de tableau, avec le nom du champ (nom verbose spécifiquement si spécifié dans le champ) dans la première colonne et la valeur de ce champ dans la deuxième colonne.
Par exemple, supposons que nous ayons la définition de modèle suivante:
class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
Je voudrais qu'il soit affiché dans le modèle comme suit (supposons une instance avec les valeurs données):
Field Name Field Value
---------- -----------
Name Wayne Koorts
E-mail [email protected]
Ce que j'essaie de faire est de pouvoir passer une instance du modèle dans un modèle et de pouvoir la parcourir de manière dynamique dans le modèle, comme ceci:
<table>
{% for field in fields %}
<tr>
<td>{{ field.name }}</td>
<td>{{ field.value }}</td>
</tr>
{% endfor %}
</table>
Existe-t-il un moyen élégant, "approuvé par Django"? Cela semble être une tâche très commune, et je devrai le faire souvent pour ce projet particulier.
model._meta.get_all_field_names()
vous donnera tous les noms de champs du modèle. Vous pouvez ensuite utiliser model._meta.get_field()
pour trouver le nom détaillé et getattr(model_instance, 'field_name')
pour obtenir la valeur du modèle.
NOTE: model._meta.get_all_field_names()
est obsolète dans Django 1.9. Utilisez plutôt model._meta.get_fields()
pour obtenir les champs du modèle et field.name
pour obtenir chaque nom de champ.
Vous pouvez utiliser le sérialiseur d'interrogation to-python de Django.
Il suffit de mettre le code suivant dans votre vue:
from Django.core import serializers
data = serializers.serialize( "python", SomeModel.objects.all() )
Et puis dans le template:
{% for instance in data %}
{% for field, value in instance.fields.items %}
{{ field }}: {{ value }}
{% endfor %}
{% endfor %}
Son grand avantage est le fait qu'il gère les champs de relation.
Pour le sous-ensemble de champs, essayez:
data = serializers.serialize('python', SomeModel.objects.all(), fields=('name','size'))
Enfin trouvé une bonne solution à cela sur la liste de diffusion dev :
Dans la vue, ajoutez:
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})
dans le modèle, ajoutez:
{% for field in object %}
<li><b>{{ field.label }}:</b> {{ field.data }}</li>
{% endfor %}
À la lumière de la publication de Django 1.8 (et de la formalisation de la Model _meta API , je me suis dit que je mettrais cela à jour avec une réponse plus récente.
En supposant le même modèle:
_class Client(Model):
name = CharField(max_length=150)
email = EmailField(max_length=100, verbose_name="E-mail")
_
_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')]
_
modifié dans Django 1.8:
L’API Model
_meta
a toujours existé en tant que Django internal, mais n’a pas été officiellement documentée et prise en charge. Dans le cadre des efforts déployés pour rendre cette API publique, certains des points d'entrée d'API déjà existants ont légèrement changé. Un guide de migration a été fourni pour vous aider à convertir votre code afin qu'il utilise la nouvelle API officielle.
Dans l'exemple ci-dessous, nous allons utiliser la méthode formalisée pour récupérer toutes les instances de champ d'un modèle via 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 fait, il a été porté à mon attention que ce qui précède est légèrement excessif pour ce qui était nécessaire (je suis d’accord!). Simple c'est mieux que complexe. Je pars de ce qui précède pour référence. Cependant, pour afficher dans le modèle, la meilleure méthode consiste à utiliser un ModelForm et à le transmettre à une instance. Vous pouvez effectuer une itération sur le formulaire (équivalent à une itération sur chacun des champs du formulaire) et utiliser l'attribut label pour extraire le nom_vose du champ de modèle, et utiliser la méthode value pour extraire la valeur:
_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}
)
_
Maintenant, nous rendons les champs dans le modèle:
_<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>
_
Voici une autre approche utilisant une méthode de modèle. Cette version résout les champs de liste de choix/choix, ignore les champs vides et vous permet d’exclure des champs spécifiques.
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
Puis dans votre modèle:
{% for f in app.get_all_fields %}
<dt>{{f.label|capfirst}}</dt>
<dd>
{{f.value|escape|urlize|linebreaks}}
</dd>
{% endfor %}
Ok, je sais que c'est un peu tard, mais depuis que je suis tombé dessus avant de trouver la bonne réponse, quelqu'un d'autre pourrait le faire.
Depuis le 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.'}]
Vous pouvez avoir un formulaire faire le travail pour vous.
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})
Puis dans le template:
<table>
{% for field in form %}
<tr>
<td>{{ field.name }}</td>
<td>{{ field.value }}</td>
</tr>
{% endfor %}
</table>
Vous pouvez utiliser la méthode values()
d'un queryset
, qui renvoie un dictionnaire. En outre, cette méthode accepte une liste de champs à sous-définir. La méthode values()
ne fonctionnera pas avec get()
, vous devez donc utiliser filter()
(reportez-vous à API QuerySet ).
Dans view
...
def show(request, object_id):
object = Foo.objects.filter(id=object_id).values()[0]
return render_to_response('detail.html', {'object': object})
Dans detail.html
...
<ul>
{% for key, value in object.items %}
<li><b>{{ key }}:</b> {{ value }}</li>
{% endfor %}
</ul>
Pour une collection d'instances renvoyée par le filtre:
object = Foo.objects.filter(id=object_id).values() # no [0]
Dans 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 %}
Il devrait vraiment y avoir un moyen intégré de le faire. J'ai écrit cet utilitaire build_pretty_data_view
qui prend un objet de modèle et une instance de formulaire (un formulaire basé sur votre modèle) et renvoie un SortedDict
.
Les avantages de cette solution incluent:
SortedDict
intégré de Django.exclude()
de noms de champs pour exclure certains champs.Meta: exclude()
, mais que vous souhaitez toujours renvoyer les valeurs, ajoutez ces champs à la liste facultative append()
.Pour utiliser cette solution, commencez par ajouter ce fichier/cette fonction quelque part, puis importez-le dans votre 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
Alors maintenant, dans votre views.py
vous pourriez faire quelque chose comme ça
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,}))
Maintenant, dans votre modèle my-template.html
, vous pouvez parcourir les données comme suit ...
{% for field,value in data.items %}
<p>{{ field }} : {{value.label}}: {{value.fieldvalue}}</p>
{% endfor %}
Bonne chance. J'espère que ça aide quelqu'un!
J'ai utilisé https://stackoverflow.com/a/3431104/2022534 mais ai remplacé model_to_dict () de Django par ceci pour pouvoir gérer la clé étrangère:
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
Veuillez noter que je l'ai un peu simplifié en supprimant les parties de l'original dont je n'avais pas besoin. Vous voudrez peut-être les remettre.
Ci-dessous se trouve le mien, inspiré par shacker'sget_all_fields
. Il obtient un dict d'une instance de modèle, s'il rencontre un champ relation, puis attribue récursivement la valeur du champ à un dict.
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
Cette fonction est principalement utilisée pour vider une instance de modèle dans des données json:
def to_json(self):
tree = to_dict(self, exclude=('id', 'User.password'))
return json.dumps(tree, ensure_ascii=False)
Au lieu d’éditer chaque modèle, je recommanderais d’écrire une balise de modèle qui renverra tout le champ de n’importe quel modèle donné.
Chaque objet a une liste de champs ._meta.fields
.
Chaque objet de champ a l'attribut name
qui renverra son nom et la méthode value_to_string()
fourni avec votre modèle object
renverra sa valeur.
Le reste est aussi simple que ce qui est dit dans documentation Django .
Voici mon exemple à quoi ce templatetag pourrait ressembler:
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>] %}")
Cela peut être considéré comme un hack, mais je l’ai déjà fait avant d’utiliser modelform_factory pour transformer une instance de modèle en formulaire.
La classe Form contient beaucoup plus d'informations à l'intérieur, ce qui est très facile à parcourir et servira le même objectif au détriment d'un peu plus de temps système. Si les tailles de vos ensembles sont relativement petites, l'impact sur les performances serait négligeable.
Le seul avantage, outre la commodité, est que vous pouvez facilement transformer la table en une grille de données modifiable à une date ultérieure.
J'ai mis au point la méthode suivante, qui fonctionne pour moi car dans chaque cas, un objet ModelForm est associé au modèle.
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)
Voici un extrait du modèle que j'utilise pour cette vue particulière:
<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>
La bonne chose à propos de cette méthode est que je peux choisir, modèle par modèle, l’ordre dans lequel je souhaite afficher les étiquettes de champ, en utilisant le tuple transmis à GetModelData et en spécifiant les noms de champ. Cela me permet également d'exclure certains champs (par exemple, une clé étrangère d'utilisateur), car seuls les noms de champs transmis via le nuplet sont intégrés au dictionnaire final.
Je ne vais pas accepter cela comme réponse, car je suis sûr que quelqu'un peut proposer quelque chose de plus "Djangonic" :-)
Mise à jour: Je choisis ceci comme réponse finale car c’est la plus simple des réponses données, c’est ce qui est nécessaire. Merci à tous ceux qui ont fourni des réponses.
Oui, ce n'est pas joli, vous devrez faire votre propre emballage. Jetez un coup d’œil à l’application intégrée databrowse , qui dispose de toutes les fonctionnalités dont vous avez réellement besoin.
La solution Django 1.7 pour moi:
Ces variables sont exactes à la question, mais vous devriez pouvoir disséquer cet exemple
La clé ici est d'utiliser à peu près le .__dict__
du modèle
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)
modèle:
{% for field in gen_house %}
{% if field != '_state' %}
{{ gen_house|getattribute:field }}
{% endif %}
{% endfor %}
dans le modèle, j'ai utilisé un filtre pour accéder au champ dans le 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 ""
J'utilise ceci, 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>
Cette approche montre comment utiliser une classe telle que ModelForm de Django et une balise de modèle telle que {{form.as_table}}, mais que la table ressemble à une sortie de données et non à un formulaire.
La première étape consistait à sous-classer le 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))
Puis j'ai sous-classé ModelForm de Django pour échanger les widgets par défaut pour les versions en lecture seule:
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"
Ce sont les seuls widgets dont j'avais besoin. Mais il ne devrait pas être difficile d'étendre cette idée à d'autres widgets.
Juste une édition 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
Laissez Django gérer tous les champs autres que les champs associés. Je pense que c'est plus stable
Regardez l'application Django-etc . Il a la balise de modèle model_field_verbose_name
pour obtenir le nom de champ détaillé des modèles: http://Django-etc.rtfd.org/en/latest/models.html#model-field-template-tags =