Django: obtenir la liste des champs du modèle?

196

J'ai défini une Userclasse dont (finalement) hérite models.Model. Je veux obtenir une liste de tous les champs définis pour ce modèle. Par exemple phone_number = CharField(max_length=20),. Fondamentalement, je veux récupérer tout ce qui hérite de la Fieldclasse.

J'ai pensé que je pourrais les récupérer en profitant de inspect.getmembers(model), mais la liste qu'il renvoie ne contient aucun de ces champs. Il semble que Django ait déjà saisi la classe et ajouté tous ses attributs magiques et supprimé ce qui a été réellement défini. Alors ... comment puis-je obtenir ces champs? Ils ont probablement une fonction pour les récupérer à leurs propres fins internes?

mpen
la source
Cela pourrait aussi aider pypi.python.org/pypi/django-inspect-model/0.5
Paolo
duplication possible des champs du modèle Get dans Django
markpasc

Réponses:

46

Comme la plupart des réponses sont obsolètes, j'essaierai de vous mettre à jour sur Django 2.2 Voici les articles - votre application (articles, blog, boutique, etc.)

1) Depuis le lien du modèle : https://docs.djangoproject.com/en/2.2/ref/models/meta/

from posts.model import BlogPost

all_fields = BlogPost._meta.fields
#or
all_fields = BlogPost._meta.get_fields()

Notez que:

all_fields=BlogPost._meta.get_fields()

Obtiendra également quelques relations, qui, par exemple: vous ne pouvez pas afficher dans une vue.
Comme dans mon cas:

Organisation._meta.fields
(<django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...

et

Organisation._meta.get_fields()
(<ManyToOneRel: crm.activity>, <django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...

2) De l'instance

from posts.model import BlogPost

bp = BlogPost()
all_fields = bp._meta.fields

3) Du modèle parent

Supposons que nous ayons Post comme modèle parent et que vous souhaitiez voir tous les champs dans une liste et que les champs parents soient en lecture seule en mode Édition.

from django.contrib import admin
from posts.model import BlogPost 

@admin.register(BlogPost)
class BlogPost(admin.ModelAdmin):
    all_fields = [f.name for f in Organisation._meta.fields]
    parent_fields = BlogPost.get_deferred_fields(BlogPost)

    list_display = all_fields
    read_only = parent_fields
Maks
la source
1
Je suppose que c'est correct et je l'accepte comme nouvelle réponse. Merci pour la mise à jour!
mpen
@mpen ne le revendiquera pas de la meilleure façon ou de la manière la plus pythonique mais ci-dessous obtiendra / devrait obtenir les valeurs que vous voudriez afficher dans une vue, donc les en-têtes d'un tableau HTML si vous le souhaitez. Comme get_fields () renvoie un tuple, vous pouvez le parcourir et obtenir les valeurs qui ressemblent à appname.Model.field_name, ci-dessous nettoie les valeurs du deuxième point, inclut le cas dans lequel un trait de soulignement a été utilisé dans le nom du champ comme et en faire un titre, modifiez-le au besoin pour chaque situation unique. clean_field_names = [str(h).split('.')[2].replace("_", " ").title() for h in all_fields]
Alejandro Suarez
1
L'exemple d'instance doit être:all_fields = bp._meta.fields
George Kettleborough
@GeorgeKettleborough je vois le point. Cela ne fonctionne-t-il pas directement en classe également? Je n'ai pas d'exemple pour tester. Quoi qu'il en soit, l'exemple concerne l'instance, il devrait donc provenir de bp ou au moins de bp .__ class__
Maks
294

Django versions 1.8 et supérieures:

Vous devez utiliser get_fields():

[f.name for f in MyModel._meta.get_fields()]

La get_all_field_names()méthode est obsolète à partir de Django 1.8 et sera supprimée en 1.10 .

La page de documentation liée ci-dessus fournit une implémentation entièrement compatible en amont de get_all_field_names(), mais pour la plupart des cas, l'exemple précédent devrait très bien fonctionner.


Versions de Django antérieures à 1.8:

model._meta.get_all_field_names()

Cela devrait faire l'affaire.

Cela nécessite une instance de modèle réelle. Si tout ce que vous avez est une sous-classe de django.db.models.Model, alors vous devez appelermyproject.myapp.models.MyModel._meta.get_all_field_names()

rossipedia
la source
11
Je voulais aussi les objets, pas seulement leurs noms. Cela semble être disponible dans model._meta.fieldscependant, et leurs noms sont récupérables avec field.nameil semble. J'espère juste que c'est le moyen le plus stable pour récupérer ces informations :)
mpen
2
pas tout à fait sûr. Le soulignement semble indiquer qu'il s'agit d'une API interne, ce serait cool si les gars de Django en faisaient la promotion jusqu'à un appel de méthode réellement public django.db.models.Model. Je vais creuser et voir ce que je peux trouver
rossipedia
2
Je suppose que le faire via l' _metaattribut est le seul moyen ... En outre, recherchez les _meta.many_to_manychamps ManyToMany!
Bernhard Vallant
3
C'est une bonne solution mais cette méthode inclut les champs de relation inverse comme une ForeignKey inverse et ce ne sont pas exactement des "champs". Quelqu'un sait comment distinguer les champs réels?
viridis
2
@rossipedia: Juste en notant que l'API ._meta est publique (même si elle était privée, probablement lorsque cette réponse a été écrite pour la première fois): docs.djangoproject.com/en/1.8/ref/models/meta/…
Nick S
76

La get_all_related_fields()méthode mentionnée ici est déconseillée en 1.8 . À partir de maintenant, c'est get_fields().

>> from django.contrib.auth.models import User
>> User._meta.get_fields()
Wil
la source
1
Cela devrait maintenant être la réponse acceptée. Terse, et au point.
Arnaud P
Oui, c'est la réponse.
Eric
55

Je trouve cela utile pour les modèles django:

def __iter__(self):
    for field_name in self._meta.get_all_field_names():
        value = getattr(self, field_name, None)
        yield (field_name, value)

Cela vous permet de faire:

for field, val in object:
    print field, val
bjw
la source
2
Et ForeignKey? J'ai des erreurs comme çadjango.db.models.fields.related.RelatedObjectDoesNotExist: CustomModel has no custom_attribute.
SAKrisT
ForeignKeyfonctionne bien pour moi. Cependant, la capture silencieuse de toutes les exceptions est un anti-modèle. Il vaut mieux attraper AttributeError, ou du moins noter qu'une exception a été avalée en silence.
Sardathrion - contre SE abus
1
self._meta.get_all_field_names()a été amorti et supprimé. Vous pouvez utiliser quelque chose comme ça for field in self._meta.get_fields()et ensuiteyield (field.name, field.value_from_object(self))
MackM
13

Cela fait l'affaire. Je le teste uniquement dans Django 1.7.

your_fields = YourModel._meta.local_fields
your_field_names = [f.name for f in your_fields]

Model._meta.local_fieldsne contient pas de champs plusieurs-à-plusieurs. Vous devriez les faire utiliser Model._meta.local_many_to_many.

Rockallite
la source
10

Il n'est pas clair si vous avez une instance de la classe ou de la classe elle-même et que vous essayez de récupérer les champs, mais de toute façon, considérez le code suivant

Utiliser une instance

instance = User.objects.get(username="foo")
instance.__dict__ # returns a dictionary with all fields and their values
instance.__dict__.keys() # returns a dictionary with all fields
list(instance.__dict__.keys()) # returns list with all fields

Utiliser une classe

User._meta.__dict__.get("fields") # returns the fields

# to get the field names consider looping over the fields and calling __str__()
for field in User._meta.__dict__.get("fields"):
    field.__str__() # e.g. 'auth.User.id'
Nader Alexan
la source
10
def __iter__(self):
    field_names = [f.name for f in self._meta.fields]
    for field_name in field_names:
        value = getattr(self, field_name, None)
        yield (field_name, value)

Cela a fonctionné pour moi dans django==1.11.8

Vivek Anand
la source
8

MyModel._meta.get_all_field_names()a été déconseillé plusieurs versions en arrière et supprimé dans Django 1.10.

Voici la suggestion de compatibilité descendante des documents :

from itertools import chain

list(set(chain.from_iterable(
    (field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
    for field in MyModel._meta.get_fields()
    # For complete backwards compatibility, you may want to exclude
    # GenericForeignKey from the results.
    if not (field.many_to_one and field.related_model is None)
)))
aboutaaron
la source
6

Juste pour ajouter, j'utilise l'auto-objet, cela a fonctionné pour moi:

[f.name for f in self.model._meta.get_fields()]
arrt_
la source
5

Au moins avec Django 1.9.9 - la version que j'utilise actuellement -, notez qu'en .get_fields()fait "considère" également tout modèle étranger comme un champ, ce qui peut être problématique. Dites que vous avez:

class Parent(models.Model):
    id = UUIDField(primary_key=True)

class Child(models.Model):
    parent = models.ForeignKey(Parent)

Il s'ensuit que

>>> map(lambda field:field.name, Parent._model._meta.get_fields())
['id', 'child']

tandis que, comme le montre @Rockallite

>>> map(lambda field:field.name, Parent._model._meta.local_fields)
['id']
rester en vie
la source
4

Donc, avant de trouver ce post, j'ai réussi à le trouver.

Model._meta.fields

Cela fonctionne aussi bien que

Model._meta.get_fields()

Je ne sais pas quelle est la différence dans les résultats, s'il y en a un. J'ai exécuté cette boucle et obtenu la même sortie.

for field in Model._meta.fields:
    print(field.name)
Carl Brubaker
la source
-2

Pourquoi ne pas simplement utiliser cela:

manage.py inspectdb

Exemple de sortie:

class GuardianUserobjectpermission(models.Model):
    id = models.IntegerField(primary_key=True)  # AutoField?
    object_pk = models.CharField(max_length=255)
    content_type = models.ForeignKey(DjangoContentType, models.DO_NOTHING)
    permission = models.ForeignKey(AuthPermission, models.DO_NOTHING)
    user = models.ForeignKey(CustomUsers, models.DO_NOTHING)

    class Meta:
        managed = False
        db_table = 'guardian_userobjectpermission'
        unique_together = (('user', 'permission', 'object_pk'),)
Adam Pawluczuk
la source
1
Ce n'est pas une réponse à la question.
Shayne