web-dev-qa-db-fra.com

Itérer sur les noms et les valeurs de champ d'instance de modèle dans le modèle

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.

172
Wayne Koorts

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.

165

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'))
67
Pawel Furmaniak

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

À 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")
_

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 modèle _meta formalisée)

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>
_
22
Michael B

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

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

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>
8
j-a

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

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:

  • Il préserve l'ordre en utilisant le SortedDict intégré de Django.
  • Quand tente d'obtenir le label/nom_vose, mais revient au nom du champ s'il n'en a pas été défini.
  • Il faudra aussi éventuellement une liste exclude() de noms de champs pour exclure certains champs.
  • Si votre classe de formulaire inclut une 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!

7
Alan Viars

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.

7
Magnus Gustavsson

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)
7
wonder

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>] %}")
5
seler

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.

4
Xealot

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.

4
Wayne Koorts

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.

4
Dmitry Shevchenko

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 ""
3
Jeremy

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

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.

2
Chuck

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

1
Shivam Goyal

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 =

0
idle sign