Ich arbeite an einer mandantenfähigen Anwendung, in der einige Benutzer ihre eigenen Datenfelder definieren können (über den Administrator), um zusätzliche Daten in Formularen zu erfassen und über die Daten zu berichten. Das letzte Bit macht JSONField nicht zu einer großartigen Option. Stattdessen habe ich die folgende Lösung:
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
Beachten Sie, wie CustomDataField einen ForeignKey für Site hat. Jede Site verfügt über einen anderen Satz benutzerdefinierter Datenfelder, verwendet jedoch dieselbe Datenbank. Dann können die verschiedenen konkreten Datenfelder definiert werden als:
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'),)
Dies führt zu folgendem Nutzen:
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?
Dies ist jedoch sehr umständlich, insbesondere wenn die zugehörigen Daten manuell erstellt und mit dem konkreten Modell verknüpft werden müssen. Gibt es einen besseren Ansatz?
Optionen, die vorläufig verworfen wurden:
Ab heute sind vier Ansätze verfügbar, von denen zwei ein bestimmtes Speicher-Backend erfordern:
Django-eav (Das Originalpaket wird nicht mehr gepflegt, hat aber einige blühende Gabeln)
Diese Lösung basiert auf dem Datenmodell Entitätsattributwert und verwendet im Wesentlichen mehrere Tabellen, um dynamische Attribute von Objekten zu speichern. Das Tolle an dieser Lösung ist, dass:
ermöglicht das effektive Anhängen/Entfernen des dynamischen Attributspeichers an das Django - Modell mit einfachen Befehlen wie:
eav.unregister(Encounter)
eav.register(Patient)
Zur gleichen Zeit wirklich mächtig zu sein.
Nachteile:
Die Verwendung ist ziemlich einfach:
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)
Hstore-, JSON- oder JSONB-Felder in PostgreSQL
PostgreSQL unterstützt mehrere komplexere Datentypen. Die meisten werden über Pakete von Drittanbietern unterstützt, aber in den letzten Jahren hat Django sie in Django.contrib.postgres.fields übernommen.
HStoreField :
Django-hstore war ursprünglich ein Paket eines Drittanbieters, aber Django 1.8 fügte HStoreField als eingebautes in, zusammen mit mehreren anderen von PostgreSQL unterstützten Feldtypen.
Dieser Ansatz ist in einem Sinne gut, dass Sie das Beste aus beiden Welten erhalten: dynamische Felder und relationale Datenbanken. Hstore ist jedoch in Bezug auf die Leistung nicht ideal , insbesondere wenn Sie am Ende Tausende von Artikeln in einem Feld speichern werden. Es werden auch nur Zeichenfolgen für Werte unterstützt.
#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)
In Django's Shell kannst du es so benutzen:
>>> 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'
Sie können indizierte Abfragen für Hstore-Felder ausgeben:
# 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/JSONB-Felder unterstützen jeden JSON-codierbaren Datentyp, nicht nur Schlüssel/Wert-Paare, sondern sind in der Regel auch schneller und (für JSONB) kompakter als Hstore. Einige Pakete implementieren JSON/JSONB-Felder, einschließlich Django-pgfields, aber ab Django 1.9 JSONField ist eine integrierte Verwendung von JSONB für die Speicherung. JSONField ähnelt HStoreField und kann mit großen Wörterbüchern eine bessere Leistung erzielen. Es werden auch andere Typen als Zeichenfolgen unterstützt, z. B. Ganzzahlen, Boolesche Werte und verschachtelte Wörterbücher.
#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)
Erstellen in der Shell:
>>> instance = Something.objects.create(
name='something',
data={'a': 1, 'b': 2, 'nested': {'c':3}}
)
Indizierte Abfragen sind nahezu identisch mit HStoreField, außer dass eine Verschachtelung möglich ist. Für komplexe Indizes ist möglicherweise eine manuelle Erstellung (oder eine Skriptmigration) erforderlich.
>>> Something.objects.filter(data__a=1)
>>> Something.objects.filter(data__nested__c=3)
>>> Something.objects.filter(data__has_key='a')
Oder andere NoSQL Django -Anpassungen - mit ihnen können Sie voll dynamische Modelle haben.
NoSQL Django -Bibliotheken sind großartig, aber denken Sie daran, dass sie nicht zu 100% Django-kompatibel sind, um beispielsweise von Standard Django nach Django-nonrel zu migrieren müssen unter anderem ManyToMany durch ListField ersetzen.
Kasse dieses Django MongoDB Beispiel:
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, ...}
Sie können sogar eingebettete Listen aller Django Modelle erstellen:
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')]
)
Django-Mutant: Dynamische Modelle basierend auf Syncdb und South-Hooks
Django-mutant implementiert vollständig dynamische Fremdschlüssel- und m2m-Felder. Und ist inspiriert von unglaublichen, aber etwas hackigen Lösungen von Will Hardy und Michael Hall.
All dies basiert auf Django South Hooks, die laut Will Hardys Vortrag auf der DjangoCon 2011 (aufgepasst!) dennoch sind robust und in der Produktion getestet ( relevanter Quellcode ).
Zuerst dies implementieren war Michael Hall .
Ja, das ist magisch. Mit diesen Ansätzen können Sie voll dynamische Django Apps, Modelle und Felder mit jedem relationalen Datenbank-Backend erreichen. Aber zu welchen Kosten? Leidet die Stabilität der Anwendung bei starker Beanspruchung? Dies sind die zu berücksichtigenden Fragen. Sie müssen sicherstellen, dass eine ordnungsgemäße Sperre beibehalten wird, um gleichzeitige Datenbankänderungsanforderungen zuzulassen.
Wenn Sie Michael Halls lib verwenden, sieht Ihr Code folgendermaßen aus:
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',
)
Ich habe daran gearbeitet, die Django-Dynamo-Idee weiter voranzutreiben. Das Projekt ist noch nicht dokumentiert, aber Sie können den Code unter https://github.com/charettes/Django-mutant lesen.
Tatsächlich funktionieren auch FK- und M2M-Felder (siehe Contrib.Related), und es ist sogar möglich, Wrapper für Ihre eigenen benutzerdefinierten Felder zu definieren.
Es werden auch Modelloptionen wie unique_together und ordering sowie Modellbasen unterstützt, sodass Sie Modellproxys, Abstracts oder Mixins in Unterklassen unterteilen können.
Ich arbeite gerade an einem nicht im Speicher befindlichen Sperrmechanismus, um sicherzustellen, dass Modelldefinitionen für mehrere Django ausgeführte Instanzen freigegeben werden können, ohne dass sie veraltete Definitionen verwenden.
Das Projekt ist immer noch sehr Alpha, aber es ist eine Eckpfeilertechnologie für eines meiner Projekte, sodass ich es zur Serienreife bringen muss. Der große Plan sieht auch die Unterstützung von Django-Nonrel vor, damit wir den Mongodb-Treiber nutzen können.
Weitere Untersuchungen haben ergeben, dass dies ein etwas spezieller Fall von Entity Attribute Value Design Pattern ist, der für Django von einigen Paketen implementiert wurde.
Erstens gibt es das ursprüngliche Projekt eav-Django , das sich auf PyPi befindet.
Zweitens gibt es eine neuere Abzweigung des ersten Projekts, Django-eav , das in erster Linie ein Refactor ist, um die Verwendung von EAV mit Djangos eigenen Modellen oder Modellen in Apps von Drittanbietern zu ermöglichen.