L’API _meta des modèles

class Options[source]

L’API _meta des modèles est au cœur de l’ORM de Django. Elle permet à d’autres parties du système telles que les recherches, les requêtes, les formulaires et le site d’administration de comprendre les capacités de chaque modèle. L’API est accessible au travers de l’attribut _meta de chaque classe de modèle, qui est une instance d’objet django.db.models.options.Options.

Les méthodes qu’elle fournit peuvent être utilisées pour :

  • Récupérer toutes les instances de champ d’un modèle

  • Récupérer une instance de champ unique d’un modèle par son nom

Changed in Django 1.8:

L’API _meta de la classe Model a toujours existé de manière interne à Django, mais n’était jusqu’ici pas documentée ni prise en charge formellement. Dans le cadre du travail de publication de cette API, certains points d’entrée de l’API existante ont été légèrement modifiés. Un guide de migration vous est proposé pour aider à convertir du code à la nouvelle API officielle.

API d’accès aux champs

Récupération d’une instance de champ unique d’un modèle par son nom

Options.get_field(field_name)[source]

Renvoie une instance de champ correspondant au nom indiqué.

field_name peut être le nom d’un champ du modèle, d’un champ d’un modèle abstrait ou hérité, ou d’un champ défini sur un autre modèle pointant vers ce modèle. Dans ce dernier cas, field_name est en fait le nom related_name défini par l’utilisateur ou généré automatiquement par Django lui-même.

Les champs cachés ne peuvent pas être récupérés par leur nom.

Si aucun champ du nom indiqué n’a été trouvé, une exception FieldDoesNotExist est générée.

>>> from django.contrib.auth.models import User

# A field on the model
>>> User._meta.get_field('username')
<django.db.models.fields.CharField: username>

# A field from another model that has a relation with the current model
>>> User._meta.get_field('logentry')
<ManyToOneRel: admin.logentry>

# A non existent field
>>> User._meta.get_field('does_not_exist')
Traceback (most recent call last):
    ...
FieldDoesNotExist: User has no field named 'does_not_exist'

Obsolète depuis la version 1.8: Options.get_field() acceptait précédemment un paramètre many_to_many qui pouvait être défini à False pour éviter de renvoyer des ManyToManyField. L’ancien comportement a été préservé par rétrocompatibilité ; cependant, le paramètre et son comportement sont obsolètes.

Si vous souhaitez exclure les champs ManyToManyField, vous pouvez inspecter l’attribut Field.many_to_many après avoir appelé get_field().

Récupération de toutes les instances de champ d’un modèle

Options.get_fields(include_parents=True, include_hidden=False)[source]
New in Django 1.8.

Renvoie un tuple de champs associés à un modèle. get_fields() accepte deux paramètres pouvant être utilisés pour contrôler quels champs sont renvoyés :

include_parents

True par défaut. Inclut récursivement les champs définis dans les classes parentes. Lorsque ce paramètre est défini à False, get_fields() ne renvoie que les champs déclarés directement sur le modèle concerné. Les champs de modèles qui héritent directement de modèles abstraits ou de classes mandataires sont considérés comme locaux, et non pas définis sur le parent.

include_hidden

False par défaut. Lorsque ce paramètre est défini à True, get_fields() inclut les champs utilisés pour fournir des fonctionnalités d’autres champs. Cela comprend également les champs possédant un nom related_name (tels que ManyToManyField et ForeignKey) qui commence par un « + ».

>>> from django.contrib.auth.models import User
>>> User._meta.get_fields()
(<ManyToOneRel: admin.logentry>,
 <django.db.models.fields.AutoField: id>,
 <django.db.models.fields.CharField: password>,
 <django.db.models.fields.DateTimeField: last_login>,
 <django.db.models.fields.BooleanField: is_superuser>,
 <django.db.models.fields.CharField: username>,
 <django.db.models.fields.CharField: first_name>,
 <django.db.models.fields.CharField: last_name>,
 <django.db.models.fields.EmailField: email>,
 <django.db.models.fields.BooleanField: is_staff>,
 <django.db.models.fields.BooleanField: is_active>,
 <django.db.models.fields.DateTimeField: date_joined>,
 <django.db.models.fields.related.ManyToManyField: groups>,
 <django.db.models.fields.related.ManyToManyField: user_permissions>)

# Also include hidden fields.
>>> User._meta.get_fields(include_hidden=True)
(<ManyToOneRel: auth.user_groups>,
 <ManyToOneRel: auth.user_user_permissions>,
 <ManyToOneRel: admin.logentry>,
 <django.db.models.fields.AutoField: id>,
 <django.db.models.fields.CharField: password>,
 <django.db.models.fields.DateTimeField: last_login>,
 <django.db.models.fields.BooleanField: is_superuser>,
 <django.db.models.fields.CharField: username>,
 <django.db.models.fields.CharField: first_name>,
 <django.db.models.fields.CharField: last_name>,
 <django.db.models.fields.EmailField: email>,
 <django.db.models.fields.BooleanField: is_staff>,
 <django.db.models.fields.BooleanField: is_active>,
 <django.db.models.fields.DateTimeField: date_joined>,
 <django.db.models.fields.related.ManyToManyField: groups>,
 <django.db.models.fields.related.ManyToManyField: user_permissions>)

Migration à partir de l’ancienne API

Dans le cadre de la formalisation de l’API Model._meta (de la classe django.db.models.options.Options), un certain nombre de méthodes et de propriétés ont été rendues obsolètes et seront supprimées dans Django 1.10.

Ces anciennes API peuvent être reproduites, soit en :

  • invoquant Options.get_field(), ou;

  • en invoquant Options.get_fields() pour récupérer une liste de tous les champs, puis en filtrant cette liste à l’aide des attributs de champ qui décrivent (ou récupèrent, dans le cas des variantes _with_model) les propriétés des champs souhaités.

Même s’il est possible de créer des remplacements strictement équivalents des anciennes méthodes, ce n’est pas forcément la meilleure approche. Prendre le temps de recréer les énumérations de champs afin de faire meilleur usage de la nouvelle API, en incluant potentiellement des champs qui étaient précédemment exclus, aboutira certainement à un code plus propre.

En supposant qu’un modèle est nommé MyModel, il est possible de procéder aux substitutions suivantes pour convertir du code à la nouvelle API :

  • MyModel._meta.get_field(name) devient :

    f = MyModel._meta.get_field(name)
    

    puis vérifier si :

    • f.auto_created == False, parce que la nouvelle API get_field() va renvoyer les relations « inverses », et :

    • f.is_relation and f.related_model is None, parce que la nouvelle API get_field() va renvoyer les relations GenericForeignKey.

  • MyModel._meta.get_field_by_name(name) renvoie un tuple de ces quatre valeurs avec les remplacements suivants :

    • field peut être trouvé par MyModel._meta.get_field(name)

    • model peut être trouvé par l’attribut model du champ.

    • direct peut être trouvé par : not field.auto_created or field.concrete

      Le contrôle auto_created exclut toute relation « vers l’avant » ou « inverse » créée par Django, mais cela comprend également les champs AutoField et OneToOneField sur les modèles mandataires. Nous évitons d’exclure ces champs à l’aide de l’attribut concrete.

    • m2m peut être trouvé au moyen de l’attribut many_to_many du champ.

  • MyModel._meta.get_fields_with_model() devient :

    [
        (f, f.model if f.model != MyModel else None)
        for f in MyModel._meta.get_fields()
        if not f.is_relation
            or f.one_to_one
            or (f.many_to_one and f.related_model)
    ]
    
  • MyModel._meta.get_concrete_fields_with_model() devient :

    [
        (f, f.model if f.model != MyModel else None)
        for f in MyModel._meta.get_fields()
        if f.concrete and (
            not f.is_relation
            or f.one_to_one
            or (f.many_to_one and f.related_model)
        )
    ]
    
  • MyModel._meta.get_m2m_with_model() devient :

    [
        (f, f.model if f.model != MyModel else None)
        for f in MyModel._meta.get_fields()
        if f.many_to_many and not f.auto_created
    ]
    
  • MyModel._meta.get_all_related_objects() devient :

    [
        f for f in MyModel._meta.get_fields()
        if (f.one_to_many or f.one_to_one)
        and f.auto_created and not f.concrete
    ]
    
  • MyModel._meta.get_all_related_objects_with_model() devient :

    [
        (f, f.model if f.model != MyModel else None)
        for f in MyModel._meta.get_fields()
        if (f.one_to_many or f.one_to_one)
        and f.auto_created and not f.concrete
    ]
    
  • MyModel._meta.get_all_related_many_to_many_objects() devient :

    [
        f for f in MyModel._meta.get_fields(include_hidden=True)
        if f.many_to_many and f.auto_created
    ]
    
  • MyModel._meta.get_all_related_m2m_objects_with_model() devient :

    [
        (f, f.model if f.model != MyModel else None)
        for f in MyModel._meta.get_fields(include_hidden=True)
        if f.many_to_many and f.auto_created
    ]
    
  • MyModel._meta.get_all_field_names() devient :

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

    Cela fournit un remplacement 100 % rétrocompatible, garantissant que les noms de champ et les noms d’attribut ForeignKey sont inclus, mais que les champs associés à GenericForeignKey ne le sont pas. Une version simplifiée pourrait être :

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

    Même si cette variante n’est pas rétrocompatible à 100 %, elle peut suffire dans bien des situations.

Back to Top