Estoy trabajando en una aplicación multicliente 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. El último bit hace que JSONField no sea una gran opción, por lo que 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. Luego, 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 se han descartado preventivamente:
- SQL personalizado para modificar tablas sobre la marcha. En parte porque esto no escalará y en parte porque es demasiado pirateo.
- Soluciones sin esquema como NoSQL. No tengo nada en contra de ellos, pero todavía no encajan bien. En última instancia, estos datos se escriben y existe la posibilidad de utilizar una aplicación de informes de terceros.
- JSONField, como se enumeró anteriormente, ya que no funcionará bien con las consultas.
Respuestas:
A partir de hoy, hay cuatro enfoques disponibles, dos de los cuales requieren un cierto backend de almacenamiento:
Django-eav (el paquete original ya no se mantiene pero tiene algunos tenedores prósperos )
Esta solución se basa en el modelo de datos de Valor de atributo de entidad , esencialmente, utiliza varias tablas para almacenar atributos dinámicos de objetos. Lo mejor de esta solución es que:
le permite adjuntar / desconectar eficazmente el almacenamiento dinámico de atributos al modelo Django con comandos simples como:
Muy bien se integra con Django admin ;
Al mismo tiempo ser realmente poderoso.
Desventajas:
El uso es bastante sencillo:
Campos Hstore, JSON o JSONB en PostgreSQL
PostgreSQL admite 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 agregó HStoreField como una función integrada, 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 dinámicos y bases de datos relacionales. Sin embargo, hstore no es ideal en cuanto al rendimiento , especialmente si va a terminar almacenando miles de elementos en un campo. También solo admite cadenas de valores.
En el shell de Django puedes usarlo así:
Puede emitir consultas indexadas en los campos de hstore:
JSONField :
Los campos JSON / JSONB admiten cualquier tipo de datos codificables 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, incluidos django-pgfields , pero a partir de Django 1.9, JSONField está integrado con 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 anidados.
Creando en el shell:
Las consultas indexadas son casi idénticas a HStoreField, excepto que es posible anidar. Los índices complejos pueden requerir la creación manual (o una migración programada).
Django MongoDB
U otras adaptaciones NoSQL Django: con ellas puede tener modelos totalmente dinámicos.
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.
Vea este ejemplo de Django MongoDB:
Incluso puede crear listas incrustadas de cualquier modelo de Django:
Django-mutante: modelos dinámicos basados en syncdb y South-hooks
Django-mutant implementa campos foráneos y m2m completamente dinámicos. Y está inspirado en soluciones increíbles pero algo hack de Will Hardy y Michael Hall.
Todos estos se basan en los ganchos Django South, que, según la charla de Will Hardy en DjangoCon 2011 (¡cuidado!) , Sin embargo, son robustos y probados en producción ( código fuente relevante ).
El primero en implementar esto fue Michael Hall .
Sí, esto es mágico, con estos enfoques puede lograr aplicaciones, modelos y campos de Django completamente dinámicos con cualquier base de datos relacional. ¿Pero a qué precio? ¿La estabilidad de la aplicación se verá afectada por el uso intensivo? Estas son las preguntas a considerar. Debe asegurarse de mantener un bloqueo adecuado para permitir solicitudes de alteración simultánea de la base de datos.
Si está utilizando Michael Halls lib, su código se verá así:
fuente
He estado trabajando para impulsar aún más la idea 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 pedidos más bases de modelo para que pueda subclasificar proxy de modelo, resumen o mixins.
De hecho, estoy trabajando en un mecanismo de bloqueo que no está en la memoria para asegurarme de que las definiciones de modelo se puedan compartir entre varias instancias de ejecución de django, evitando que usen definiciones obsoletas.
El proyecto todavía es muy alfa, pero es una tecnología fundamental para uno de mis proyectos, por lo que tendré que llevarlo a producción. El gran plan también es compatible con django-nonrel para que podamos aprovechar el controlador mongodb.
fuente
La investigación adicional revela que este es un caso algo especial del patrón de diseño de Valor de atributo de entidad , 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 modelos propios o modelos de django en aplicaciones de terceros.
fuente