Django campos de modelo dynamic

Estoy trabajando en una aplicación multiusuario en la que algunos usuarios pueden definir sus propios campos de datos (a través del administrador) para recostackr datos adicionales en formularios e informar sobre los datos. Este último bit hace que JSONField no sea una gran opción, así que en lugar de eso tengo la siguiente solución:

class CustomDataField(models.Model): """ Abstract specification for arbitrary data fields. Not used for holding data itself, but metadata about the fields. """ site = models.ForeignKey(Site, default=settings.SITE_ID) name = models.CharField(max_length=64) class Meta: abstract = True class CustomDataValue(models.Model): """ Abstract specification for arbitrary data. """ value = models.CharField(max_length=1024) class Meta: abstract = True 

Observe cómo CustomDataField tiene una ForeignKey to Site: cada sitio tendrá un conjunto diferente de campos de datos personalizados, pero usará la misma base de datos. Entonces los diversos campos de datos concretos se pueden definir como:

 class UserCustomDataField(CustomDataField): pass class UserCustomDataValue(CustomDataValue): custom_field = models.ForeignKey(UserCustomDataField) user = models.ForeignKey(User, related_name='custom_data') class Meta: unique_together=(('user','custom_field'),) 

Esto lleva al siguiente uso:

 custom_field = UserCustomDataField.objects.create(name='zodiac', site=my_site) #probably created in the admin user = User.objects.create(username='foo') user_sign = UserCustomDataValue(custom_field=custom_field, user=user, data='Libra') user.custom_data.add(user_sign) #actually, what does this even do? 

Pero esto se siente muy torpe, particularmente con la necesidad de crear manualmente los datos relacionados y asociarlos con el modelo concreto. ¿Hay un mejor enfoque?

Opciones que han sido descartadas de forma preventiva:

  • SQL personalizado para modificar las tablas sobre la marcha. En parte porque esto no va a escalar y en parte porque es demasiado de un hack.
  • Soluciones sin esquema como NoSQL. No tengo nada en contra de ellos, pero todavía no encajan bien. En última instancia, se escriben estos datos y existe la posibilidad de utilizar una aplicación de informes de terceros.
  • JSONField, como se indica anteriormente, ya que no funcionará bien con las consultas.

A partir de hoy, hay cuatro enfoques disponibles, dos de los cuales requieren un determinado backend de almacenamiento:

  1. Django-eav (el paquete original ya no se conserva, pero tiene algunos tenedores prósperos )

    Esta solución se basa en el modelo de datos Entity Attribute Value , básicamente, utiliza varias tablas para almacenar atributos dynamics de objetos. Gran parte de esta solución es que:

    • utiliza varios modelos de Django puros y simples para representar campos dynamics, lo que lo hace fácil de entender y de base de datos;
    • le permite adjuntar / separar efectivamente el almacenamiento dynamic de atributos al modelo Django con comandos simples como:

       eav.unregister(Encounter) eav.register(Patient) 
    • Muy bien se integra con Django admin ;

    • Al mismo tiempo siendo realmente poderoso.

    Desventajas:

    • No muy eficiente. Esto es más bien una crítica del propio patrón EAV, que requiere la combinación manual de los datos de un formato de columna a un conjunto de pares clave-valor en el modelo.
    • Más difícil de mantener. Mantener la integridad de los datos requiere una restricción de clave única de varias columnas, que puede ser ineficiente en algunas bases de datos.
    • Deberá seleccionar una de las bifurcaciones , ya que el paquete oficial ya no se mantiene y no hay un líder claro.

    El uso es bastante sencillo:

     import eav from app.models import Patient, Encounter eav.register(Encounter) eav.register(Patient) Attribute.objects.create(name='age', datatype=Attribute.TYPE_INT) Attribute.objects.create(name='height', datatype=Attribute.TYPE_FLOAT) Attribute.objects.create(name='weight', datatype=Attribute.TYPE_FLOAT) Attribute.objects.create(name='city', datatype=Attribute.TYPE_TEXT) Attribute.objects.create(name='country', datatype=Attribute.TYPE_TEXT) self.yes = EnumValue.objects.create(value='yes') self.no = EnumValue.objects.create(value='no') self.unkown = EnumValue.objects.create(value='unkown') ynu = EnumGroup.objects.create(name='Yes / No / Unknown') ynu.enums.add(self.yes) ynu.enums.add(self.no) ynu.enums.add(self.unkown) Attribute.objects.create(name='fever', datatype=Attribute.TYPE_ENUM,\ enum_group=ynu) # When you register a model within EAV, # you can access all of EAV attributes: Patient.objects.create(name='Bob', eav__age=12, eav__fever=no, eav__city='New York', eav__country='USA') # You can filter queries based on their EAV fields: query1 = Patient.objects.filter(Q(eav__city__contains='Y')) query2 = Q(eav__city__contains='Y') | Q(eav__fever=no) 
  2. Campos Hstore, JSON o JSONB en PostgreSQL

    PostgreSQL soporta varios tipos de datos más complejos. La mayoría se admiten a través de paquetes de terceros, pero en los últimos años Django los ha adoptado en django.contrib.postgres.fields.

    HStoreField :

    Django-hstore fue originalmente un paquete de terceros, pero Django 1.8 agregó HStoreField como un componente integrado, junto con varios otros tipos de campos compatibles con PostgreSQL.

    Este enfoque es bueno en el sentido de que le permite tener lo mejor de ambos mundos: campos dynamics y bases de datos relacionales. Sin embargo, hstore no es ideal en cuanto a rendimiento , especialmente si va a terminar almacenando miles de artículos en un campo. También solo soporta cadenas de valores.

     #app/models.py from django.contrib.postgres.fields import HStoreField class Something(models.Model): name = models.CharField(max_length=32) data = models.HStoreField(db_index=True) 

    En el shell de Django puedes usarlo así:

     >>> instance = Something.objects.create( name='something', data={'a': '1', 'b': '2'} ) >>> instance.data['a'] '1' >>> empty = Something.objects.create(name='empty') >>> empty.data {} >>> empty.data['a'] = '1' >>> empty.save() >>> Something.objects.get(name='something').data['a'] '1' 

    Puede emitir consultas indexadas en los campos hstore:

     # equivalence Something.objects.filter(data={'a': '1', 'b': '2'}) # subset by key/value mapping Something.objects.filter(data__a='1') # subset by list of keys Something.objects.filter(data__has_keys=['a', 'b']) # subset by single key Something.objects.filter(data__has_key='a') 

    JSONField :

    Los campos JSON / JSONB admiten cualquier tipo de datos codificable JSON, no solo pares clave / valor, sino que también tienden a ser más rápidos y (para JSONB) más compactos que Hstore. Varios paquetes implementan campos JSON / JSONB que incluyen django-pgfields , pero a partir de Django 1.9, JSONField es un componente integrado que utiliza JSONB para el almacenamiento. JSONField es similar a HStoreField y puede funcionar mejor con diccionarios grandes. También admite tipos distintos de cadenas, como enteros, booleanos y diccionarios nesteds.

     #app/models.py from django.contrib.postgres.fields import JSONField class Something(models.Model): name = models.CharField(max_length=32) data = JSONField(db_index=True) 

    Creando en el shell:

     >>> instance = Something.objects.create( name='something', data={'a': 1, 'b': 2, 'nested': {'c':3}} ) 

    Las consultas indexadas son casi idénticas a HStoreField, excepto que es posible el anidamiento. Los índices complejos pueden requerir la creación manual (o una migración con script).

     >>> Something.objects.filter(data__a=1) >>> Something.objects.filter(data__nested__c=3) >>> Something.objects.filter(data__has_key='a') 
  3. Django MongoDB

    O otras adaptaciones NoSQL Django: con ellas puede tener modelos completamente dynamics.

    Las bibliotecas NoSQL Django son geniales, pero tenga en cuenta que no son 100% compatibles con Django, por ejemplo, para migrar a Django-nonrel desde Django estándar, deberá reemplazar ManyToMany con ListField entre otras cosas.

    Echa un vistazo a este ejemplo Django MongoDB:

     from djangotoolbox.fields import DictField class Image(models.Model): exif = DictField() ... >>> image = Image.objects.create(exif=get_exif_data(...)) >>> image.exif {u'camera_model' : 'Spamcams 4242', 'exposure_time' : 0.3, ...} 

    Incluso puedes crear listas integradas de cualquier modelo de Django:

     class Container(models.Model): stuff = ListField(EmbeddedModelField()) class FooModel(models.Model): foo = models.IntegerField() class BarModel(models.Model): bar = models.CharField() ... >>> Container.objects.create( stuff=[FooModel(foo=42), BarModel(bar='spam')] ) 
  4. Django-mutant: modelos dynamics basados ​​en syncdb y South-hooks

    Django-mutant implementa campos de clave externa y m2m totalmente dynamics. Y está inspirado en soluciones increíbles pero un tanto triviales de Will Hardy y Michael Hall.

    Todos estos se basan en los ganchos Django South, que, según Will Hardy’s en DjangoCon 2011 Mírenlo !) Son robustos y están probados en producción ( código fuente relevante ).

    Primero en implementar esto fue Michael Hall .

    Sí, esto es mágico, con estos enfoques puede lograr aplicaciones, modelos y campos totalmente dynamics de Django con cualquier base de datos relacional. ¿Pero a qué precio? ¿La estabilidad de aplicación sufrirá con el uso pesado? Estas son las preguntas a tener en cuenta. Debe asegurarse de mantener un locking adecuado para permitir la modificación simultánea de las solicitudes de la base de datos.

    Si está utilizando la librería Michael Halls, su código se verá así:

     from dynamo import models test_app, created = models.DynamicApp.objects.get_or_create( name='dynamo' ) test, created = models.DynamicModel.objects.get_or_create( name='Test', verbose_name='Test Model', app=test_app ) foo, created = models.DynamicModelField.objects.get_or_create( name = 'foo', verbose_name = 'Foo Field', model = test, field_type = 'dynamiccharfield', null = True, blank = True, unique = False, help_text = 'Test field for Foo', ) bar, created = models.DynamicModelField.objects.get_or_create( name = 'bar', verbose_name = 'Bar Field', model = test, field_type = 'dynamicintegerfield', null = True, blank = True, unique = False, help_text = 'Test field for Bar', ) 

He estado trabajando para impulsar la idea de django-dynamo. El proyecto aún no está documentado, pero puede leer el código en https://github.com/charettes/django-mutant .

En realidad, los campos FK y M2M (ver contrib.related) también funcionan e incluso es posible definir un contenedor para sus propios campos personalizados.

También hay soporte para opciones de modelo como unique_together y ordering plus Model bases para que pueda crear subclases de proxy de modelo, abstract o mixins.

En realidad, estoy trabajando en un mecanismo de locking que no está en la memoria para asegurar que las definiciones de los modelos se puedan compartir en varias instancias en ejecución de django mientras se evita usar una definición obsoleta.

El proyecto sigue siendo muy alfa, pero es una tecnología fundamental para uno de mis proyectos, así que tendré que llevarlo a producción listo. El gran plan también es compatible con django-nonrel para que podamos aprovechar el controlador mongodb.

Investigaciones adicionales revelan que este es un caso un tanto especial del patrón de diseño del Valor de Atributo de la Entidad , que se ha implementado para Django por un par de paquetes.

Primero, está el proyecto original eav-django , que está en PyPi.

Segundo, hay una bifurcación más reciente del primer proyecto, django-eav, que es principalmente un refactor para permitir el uso de EAV con modelos propios de django o modelos en aplicaciones de terceros.