Obtener los campos del modelo en Django

121

Dado un modelo de Django, estoy tratando de enumerar todos sus campos. He visto algunos ejemplos de cómo hacer esto usando el atributo del modelo _meta, pero ¿no indica el guión bajo delante del meta que el atributo _meta es un atributo privado y no se debe acceder directamente? ... ¿Porque, por ejemplo, el diseño de _meta podría cambiar en el futuro y no ser una API estable?

¿_Meta es una excepción a esta regla? ¿Es estable y está listo para usar o se considera una mala práctica acceder a él? ¿O hay una función o alguna otra forma de introspectar los campos de un modelo sin usar el atributo _meta? A continuación se muestra una lista de algunos enlaces que muestran cómo hacer esto usando el atributo _meta

Cualquier consejo es muy apreciado.

campo get / set del objeto django

http://www.djangofoo.com/80/get-list-model-fields

¿Cómo introspectar los campos del modelo de Django?

Joe J
fuente

Respuestas:

144

_metaes privado, pero relativamente estable. Hay esfuerzos para formalizarlo, documentarlo y eliminar el subrayado, lo que podría suceder antes de 1.3 o 1.4. Me imagino que se hará un esfuerzo para asegurar que las cosas sean compatibles con versiones anteriores, porque mucha gente lo ha estado usando de todos modos.

Si está particularmente preocupado por la compatibilidad, escriba una función que tome un modelo y devuelva los campos. Esto significa que si algo cambia en el futuro, solo tiene que cambiar una función.

def get_model_fields(model):
    return model._meta.fields

Creo que esto devolverá una lista de Fieldobjetos. Para obtener el valor de cada campo de la instancia, use getattr(instance, field.name).

Actualización: los colaboradores de Django están trabajando en una API para reemplazar el objeto _Meta como parte de Google Summer of Code. Ver:
- https://groups.google.com/forum/#!topic/django-developers/hD4roZq0wyk
- https://code.djangoproject.com/wiki/new_meta_api

Will Hardy
fuente
45
También debe tener en cuenta el hecho de que si también necesita los campos de muchos a muchos, debe acceder model._meta.many_to_many.
Bernhard Vallant
1
Gracias Will. Es bueno saber que otras personas también están usando _meta. Me gusta la idea de tener una función de envoltura. Lazerscience, gracias también. Es bueno saber que hay un buen método para obtener los campos many_to_many. Joe
Joe J
3
django/core/management/commands/loaddata.pyusa _meta para recorrer el árbol de aplicaciones, modelos y campos. Buen patrón a seguir ... y puedes apostar que es la "forma oficial".
placas de cocción
2
Si está utilizando claves externas genéricas, también debe verificar_meta.virtual_fields
andrei1089
7
_metaahora es una API formal y compatible (nuevo en Django 1.8)
mgalgs
97

Sé que esta publicación es bastante antigua, pero solo quería decirle a cualquiera que esté buscando lo mismo que hay una API pública y oficial para hacer esto: get_fields()yget_field()

Uso:

fields = model._meta.get_fields()
my_field = model._meta.get_field('my_field')

https://docs.djangoproject.com/en/1.8/ref/models/meta/

PirosB3
fuente
6
Esta es realmente la respuesta correcta para la versión más nueva de django.
chhantyal
3
Una pregunta: si estos son "públicos y oficiales", ¿por qué siguen estando bajo _meta?
krubo
9

Ahora hay un método especial: get_fields ()

    >>> from django.contrib.auth.models import User
    >>> User._meta.get_fields()

Acepta dos parámetros que se pueden usar para controlar qué campos se devuelven:

  • include_parents

    Verdadero por defecto. Incluye de forma recursiva campos definidos en clases principales. Si se establece en False, get_fields () solo buscará campos declarados directamente en el modelo actual. Los campos de modelos que heredan directamente de modelos abstractos o clases de proxy se consideran locales, no del padre.

  • include_hidden

    Falso por defecto. Si se establece en True, get_fields () incluirá campos que se utilizan para respaldar la funcionalidad de otros campos. Esto también incluirá cualquier campo que tenga un related_name (como ManyToManyField o ForeignKey) que comience con un "+"

plaga de pecos
fuente
9

get_fields()devuelve un tupley cada elemento es un Model fieldtipo, que no se puede usar directamente como una cadena. Entonces, field.namedevolverá el nombre del campo

my_model_fields = [field.name for field in MyModel._meta.get_fields()]
El código anterior devolverá una lista con todos los campos de nombre

Ejemplo

In [11]: from django.contrib.auth.models import User

In [12]: User._meta.get_fields()
Out[12]: 
(<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>)

In [13]: [field.name for field in User._meta.get_fields()]
Out[13]: 
['logentry',
 'id',
 'password',
 'last_login',
 'is_superuser',
 'username',
 'first_name',
 'last_name',
 'email',
 'is_staff',
 'is_active',
 'date_joined',
 'groups',
 'user_permissions']
JPG
fuente
¡Gran respuesta! ¡Gracias!
Tms91
8

Esto es algo que hace Django al construir un formulario a partir de un modelo. Está usando el atributo _meta, pero como señaló Bernhard, usa tanto _meta.fields como _meta.many_to_many. Mirando django.forms.models.fields_for_model, así es como podría hacerlo:

opts = model._meta
for f in sorted(opts.fields + opts.many_to_many):
    print '%s: %s' % (f.name, f)
extasiado
fuente
4

Los campos de modelo contenidos por _meta se enumeran en varias ubicaciones como listas de los respectivos objetos de campo. Puede ser más fácil trabajar con ellos como un diccionario donde las claves son los nombres de los campos.

En mi opinión, esta es la forma más irredundante y expresiva de recopilar y organizar los objetos de campo del modelo:

def get_model_fields(model):
  fields = {}
  options = model._meta
  for field in sorted(options.concrete_fields + options.many_to_many + options.virtual_fields):
    fields[field.name] = field
  return fields

(Consulte Este ejemplo de uso en django.forms.models.fields_for_model).

jxqz
fuente
2
Es posible que desee acompañar su código con una breve descripción de lo que está haciendo.
Bas van Dijk
2

Que tal este.

fields = Model._meta.fields
JDE876
fuente
1
Imagino que uno podría querer acceder a las propiedades de un campo. Además, como se señaló en respuestas anteriores, existen many_to_manyy virtual_fields.
Jocke
@Jocke tienes razón. Puede surgir la necesidad de acceder a otro atributo. Respuesta editada.
JDE876
2

Según la documentación de django 2.2, puede usar:

Para obtener todos los campos: Model._meta.get_fields()

Para obtener un campo individual: Model._meta.get_field('field name')

ex. Session._meta.get_field('expire_date')

Devang Padhiyar
fuente
1

Si necesita esto para su sitio de administración , también existe el ModelAdmin.get_fieldsmétodo ( docs ), que devuelve un listnombre de campo strings.

Por ejemplo:

class MyModelAdmin(admin.ModelAdmin):
    # extending change_view, just as an example
    def change_view(self, request, object_id=None, form_url='', extra_context=None):
        # get the model field names
        field_names = self.get_fields(request)
        # use the field names
        ...
djvg
fuente
0

Otra forma es agregar funciones al modelo y cuando desee anular la fecha, puede llamar a la función.

class MyModel(models.Model):
    name = models.CharField(max_length=256)
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    def set_created_date(self, created_date):
        field = self._meta.get_field('created')
        field.auto_now_add = False
        self.created = created_date

    def set_modified_date(self, modified_date):
        field = self._meta.get_field('modified')
        field.auto_now = False
        self.modified = modified_date

my_model = MyModel(name='test')
my_model.set_modified_date(new_date)
my_model.set_created_date(new_date)
my_model.save()
Thomas Turner
fuente