2011-10-28 20 views
130

Estoy trabajando en una aplicación multi-tented en la que algunos usuarios pueden definir sus propios campos de datos (a través del administrador) para recopilar datos adicionales en formularios e informar sobre los datos. Este último bit hace JSONField no es una gran opción, así que en vez tengo la siguiente solución:campos del modelo dinámico Django

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 

Nota cómo CustomDataField tiene una ForeignKey al sitio - cada sitio tendrá un conjunto diferente de los campos de datos personalizados, pero el uso de la misma base de datos. Entonces los diversos campos de datos concretos pueden definirse 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 a la siguiente aplicación:

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, en particular con la necesidad de crear manualmente los datos relacionados y asociarlo con el modelo concreto. ¿Hay un mejor enfoque?

opciones que han sido descartados de forma preventiva:

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

de forma preventiva, esto no es ninguna de estas preguntas: http://stackoverflow.com/questions/7801729/django-model-with-dynamic-attributes http://stackoverflow.com/questions/ 2854656/per-instance-dynamic-fields-django-model – GDorn

Respuesta

242

Al día de hoy, hay cuatro enfoques disponibles, dos de ellos que requieren un cierto backend de almacenamiento:

  1. Django-eav (el embalaje original ya no es mantenido, pero tiene algunos thriving forks)

    Esta solución se basa en Entity Attribute Value modelo de datos, esencialmente, usa varias tablas para almacenar atributos dinámicos de objetos. Grandes partes acerca de esta solución es que:

    • utiliza varios modelos de Django puras y simples para representar campos dinámicos, lo que hace que sea fácil de entender y de base de datos agnóstica;
    • le permite conectar con eficacia/desconectar el almacenamiento atributo dinámico al modelo de Django con comandos simples como:

      eav.unregister(Encounter) 
      eav.register(Patient) 
      
    • Nicely integrates with Django admin;

    • Al mismo tiempo, ser realmente poderoso.

    Desventajas:

      No
    • muy eficiente. Esto es más una crítica del propio patrón de EAV, que requiere fusionar manualmente los datos de un formato de columna a un conjunto de pares clave-valor en el modelo.
    • Más difícil de mantener. El mantenimiento de 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 one of the forks, 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 son compatibles con paquetes de terceros, pero en los últimos años Django los ha adoptado en django.contrib.postgres.fields.

    HStoreField:

    Django-hstore era originalmente un paquete de terceros, pero Django 1.8 añade HStoreField como incorporado, junto con varios otros tipos de campos de PostgreSQL-compatible.

    Este enfoque es bueno en el sentido de que le permite tener lo mejor de ambos mundos: campos dinámicos y base de datos relacional. Sin embargo, hstore es not ideal performance-wise, especialmente si va a terminar almacenando miles de elementos en un campo. También solo admite cadenas para 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) 
    

    Con cáscara de Django puede utilizar de esta manera:

    >>> 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 indexados contra 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:

    JSON/Los campos JSONB admiten cualquier tipo de datos codificables por JSON, no solo key/valu e pares, pero también tienden a ser más rápidos y (para JSONB) más compactos que Hstore. Varios paquetes implementar campos JSON/JSONB incluyendo django-pgfields, pero a partir de Django 1.9, JSONField es un sistema incorporado en el uso de JSONB para su 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 anidados.

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

    consultas indizadas son casi idénticos a HStoreField, excepto de anidación es posible. Los índices complejos pueden requerir la creación manual (o una migración guionizada).

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

    U otras adaptaciones NoSQL Django - con ellos se puede tener modelos totalmente dinámicos.

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

    Pedido 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, ...} 
    

    Puede incluso crear embedded lists de cualquiera de los modelos 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: Dynamic models based on syncdb and South-hooks

    Django-mutant implementos completamente dinámico clave externa y M2M campos. Y está inspirado en soluciones increíbles pero algo hackish por Will Hardy y Michael Hall.

    Todos ellos se basan en los ganchos de Django del Sur, que, de acuerdo con Will Hardy's talk at DjangoCon 2011(cuidado!) son, sin embargo, robusta y probada en producción (relevant source code).

    Primero en implement this fue Michael Hall.

    Sí, esto es mágico, con estos enfoques puede lograr aplicaciones totalmente dinámicas Django, modelos y campos con cualquier back-end de base de datos relacional. ¿Pero a qué precio? ¿La estabilidad de la aplicación sufrirá un uso intensivo? Estas son las preguntas a considerar. Debe asegurarse de mantener un lock adecuado para permitir solicitudes simultáneas de modificación de la base de datos.

    Si está utilizando Michael Salas lib, el código se vería 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', 
           ) 
    
+31

Esto es bastante completo. – Edwin

+0

@GDorn, gracias por una actualización! –

+2

este tema se habló recientemente en DjangoCon 2013 Europa: http://www.slideshare.net/schacki/django-dynamic-models20130502?from_search=2 y http://www.youtube.com/watch?v=67wcGdk4aCc –

3

más investigación revela que este es un caso especial del patrón de Entity Attribute Value diseño, que ha sido implementado para Django por un par de paquetes.

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

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

+0

Lo incluiré en la wiki. –

+1

Yo diría que al revés, que EAV es un caso especial de modelado dinámico. Se usa mucho en la comunidad de la "web semántica", donde se denomina "triple" o "cuádruple" si incluye una identificación única. Sin embargo, es poco probable que sea tan eficiente como un mecanismo que puede crear y modificar tablas SQL de forma dinámica. – Cerin

+0

@GDom es eav-django su primera opción? Me refiero a qué opción de arriba elegiste? – Moreno

13

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 (consulte contrib.related) también funcionan, e incluso es posible definir el contenedor para sus propios campos personalizados.

También hay soporte para opciones de modelo como unique_together y para pedidos más bases de modelos para que pueda subclasificar el modelo proxy, abstract o mixins.

Estoy trabajando en un mecanismo de bloqueo no en la memoria para asegurarme de que las definiciones de modelo se puedan compartir en múltiples instancias en ejecución django mientras se evita que usen definiciones obsoletas.

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 lista. El gran plan también es compatible con django-nonrel para que podamos aprovechar el controlador de mongodb.

+1

¡Hola, Simon! He incluido un enlace a su proyecto en [mi respuesta wiki] (http://stackoverflow.com/a/7934577/497056) justo después de haberlo creado en github. :))) ¡Encantado de verte en stackoverflow! –

Cuestiones relacionadas