Ich benutze einen etwas anderen Ansatz, der gut mit Süden spielt, indem er eine Perspektive erstellt. Eine Perspektive ist ein Proxy, der einige Felder im Modell umbenennt, aber den Namen der Spalte behält.
Für mich war es ein Beispiel, um die Flexibilität des django ORM zu zeigen. Ich bin mir nicht sicher, ob Sie dies im Produktionscode verwenden möchten. Daher ist es nicht genug getestet, aber es wird Ihnen eine Idee geben.
Die Idee
Eine Perspektive lassen den Benutzer verschiedene Modelle an einen Tisch zu schaffen, die ihre eigenen Methoden und haben unterschiedliche Feldnamen haben, aber das zugrunde liegende Modell und Tisch teilen.
Es kann verschiedene Typen in derselben Tabelle speichern, was für Protokollierungs- oder Ereignissysteme nützlich sein kann. Jede Perspektive kann nur ihre eigenen Einträge sehen, da sie nach einem Feldnamen action_type gefiltert wird.
Die Modelle sind nicht verwaltet, haben aber einen benutzerdefinierten Manager, so dass im Süden keine neuen Tabellen dafür erstellt werden.
Nutzungs
Die Implementierung ist eine Klasse Dekorateur, der die Metadaten des django Modell modifiziert. Es nimmt ein "Basis" -Modell und ein Wörterbuch von Alias-Feldern auf.
an einem Beispiel der ersten Blick lassen:
class UserLog(models.Model):
"""
A user action log system, user is not in this class, because it clutters import
"""
date_created = models.DateTimeField(_("Date created"), auto_now_add=True)
# Action type is obligatory
action_type = models.CharField(_("Action Type"), max_length=255)
integer_field1 = models.IntegerField()
integer_field2 = models.IntegerField()
char_field1 = models.CharField(max_length=255)
char_field2 = models.CharField(max_length=255)
@ModelPerspective({
'x': 'integer_field1',
'y': 'integer_field2',
'target': 'char_field1'
}, UserLog)
class UserClickLog(models.Model):
pass
ein Modell Dies schafft, die die Eigenschaft x abbildet integer_field1, y integer_field2 und Ziel zu char_field1 und wo die zugrunde liegende Tabelle ist das gleiche wie die Tabelle als Benutzerlog
Die Verwendung unterscheidet sich nicht von anderen Modellen, und im Süden wird nur die UserLog-Tabelle erstellt.
Jetzt schauen wir uns an, wie man das implementiert.
Implementierung
Wie funktioniert es?
Wenn die Klasse ausgewertet wird, erhält der Dekorator die Klasse. Dies wird Affen die Klasse patch, so dass Instanzen die Basistabelle wie angegeben widerspiegeln.
die Aliase
hinzufügen Wenn wir etwas tiefer in den Code gehen. Das aliasierte Wörterbuch wird gelesen und für jedes Feld wird das Basisfeld nachgeschlagen. Wenn wir das Feld in der Basistabelle gefunden haben, wird der Name geändert. Dies hat einen kleinen Nebeneffekt, dass es auch die Spalte ändert. Also müssen wir die Feldspalte aus dem Basisfeld abrufen. Dann wird das Feld mit der Methode contribute_to_class zur Klasse hinzugefügt, die sich um die gesamte Buchhaltung kümmert.
Dann werden alle nicht Alias-Eigenschaften zum Modell hinzugefügt. Dies ist nicht erforderlich, aber ich habe beschlossen, sie hinzuzufügen.
Einstellen der Eigenschaften
Jetzt haben wir alle Felder, müssen wir ein paar Eigenschaften festgelegt. Die Eigenschaft managed wird beim Ignorieren der Tabelle nach Süden tricksen, hat aber einen Nebeneffekt. Die Klasse wird keinen Manager haben. (Wir werden das später beheben). Wir kopieren auch den Tabellennamen (db_table) aus dem Basismodell und machen das Feld action_type standardmäßig zum Klassennamen.
Das letzte, was wir tun müssen, ist einen Manager zur Verfügung zu stellen. Etwas Vorsicht ist geboten, da Django angibt, dass es nur einen QuerySet-Manager gibt. Wir lösen dies, indem wir den Manager mit deepcopy kopieren und dann eine Filteranweisung hinzufügen, die nach dem Klassennamen filtert.
deep (QuerySet()). Filter (ACTION_TYPE = cls. Klasse. Name)
Diese nur relevante Datensätze unseren Tisch Rückkehr lassen. Jetzt wickle es in einen Dekorateur und es ist fertig.
Dies ist der Code:
from django.db import models
from django.db.models.query import QuerySet
def ModelPerspective(aliases, model):
"""
This class decorator creates a perspective from a model, which is
a proxy with aliased fields.
First it will loop over all provided aliases
these are pairs of new_field, old_field.
Then it will copy the old_fields found in the
class to the new fields and change their name,
but keep their columnnames.
After that it will copy all the fields, which are not aliased.
Then it will copy all the properties of the model to the new model.
Example:
@ModelPerspective({
'lusername': 'username',
'phonenumber': 'field1'
}, User)
class Luser(models.Model):
pass
"""
from copy import deepcopy
def copy_fields(cls):
all_fields = set(map(lambda x: x.name, model._meta.fields))
all_fields.remove('id')
# Copy alias fields
for alias_field in aliases:
real_field = aliases[alias_field]
# Get field from model
old_field = model._meta.get_field(real_field)
oldname, columnname = old_field.get_attname_column()
new_field = deepcopy(old_field)
# Setting field properties
new_field.name = alias_field
new_field.db_column = columnname
new_field.verbose_name = alias_field
new_field.contribute_to_class(cls, "_%s" % alias_field)
all_fields.remove(real_field)
for field in all_fields:
new_field = deepcopy(model._meta.get_field(field))
new_field.contribute_to_class(cls, "_%s" % new_field.name)
def copy_properties(cls):
# Copy db table
cls._meta.db_table = model._meta.db_table
def create_manager(cls):
from copy import deepcopy
field = cls._meta.get_field('action_type')
field.default = cls.__name__
# Only query on relevant records
qs = deepcopy(cls.objects)
cls.objects = qs.filter(action_type=cls.__name__)
def wrapper(cls):
# Set it unmanaged
cls._meta.managed = False
copy_properties(cls)
copy_fields(cls)
create_manager(cls)
return cls
return wrapper
Ist das für die Produktion bereit?
Ich würde es nicht in Produktionscode verwenden, für mich war es eine Übung, um die Flexibilität von Django zu zeigen, aber mit ausreichenden Tests könnte es in Code verwendet werden, wenn man will.
Ein weiteres Argument gegen die Verwendung in der Produktion wäre, dass der Code eine ordentliche Menge an internen Funktionen des django ORM verwendet. Ich bin mir nicht sicher, ob die API ausreichen wird, um stabil zu sein.
Und diese Lösung ist nicht die beste Lösung, die Sie sich vorstellen können. Es gibt mehr Möglichkeiten, dieses Problem des Speicherns dynamischer Felder in einer Datenbank zu lösen.
Möchten Sie das aktuelle Datenbankschema (das ältere) verwenden? – borges