Anscheinend fragen Sie nach dem Unterschied zwischen dem Datenmodell und dem Domänenmodell. Bei letzterem finden Sie die Geschäftslogik und Entitäten, die von Ihrem Endbenutzer wahrgenommen werden. Bei ersteren speichern Sie Ihre Daten tatsächlich.
Darüber hinaus habe ich den dritten Teil Ihrer Frage wie folgt interpretiert: Wie kann man feststellen, dass diese Modelle nicht getrennt bleiben?
Dies sind zwei sehr unterschiedliche Konzepte, und es ist immer schwierig, sie getrennt zu halten. Es gibt jedoch einige gängige Muster und Werkzeuge, die für diesen Zweck verwendet werden können.
Informationen zum Domänenmodell
Das erste, was Sie erkennen müssen, ist, dass es in Ihrem Domain-Modell nicht wirklich um Daten geht. Es geht um Aktionen und Fragen wie "Diesen Benutzer aktivieren", "Diesen Benutzer deaktivieren", "Welche Benutzer sind derzeit aktiviert?" und "Wie heißt dieser Benutzer?". Klassisch ausgedrückt: Es geht um Abfragen und Befehle .
In Befehlen denken
Schauen wir uns zunächst die Befehle in Ihrem Beispiel an: "Diesen Benutzer aktivieren" und "Diesen Benutzer deaktivieren". Das Schöne an Befehlen ist, dass sie leicht durch kleine gegebene Wann-Dann-Szenarien ausgedrückt werden können:
gegeben ein inaktiver Benutzer ,
wenn der Administrator dieser Benutzer aktiviert
dann der Benutzer aktiv wird ,
und eine Bestätigungs - E-Mail an den Benutzer gesendet wird ,
und ein Eintrag wird in das Systemprotokoll hinzugefügt
(etc. etc.)
Solche Szenarien sind nützlich, um zu sehen, wie verschiedene Teile Ihrer Infrastruktur von einem einzigen Befehl beeinflusst werden können - in diesem Fall Ihre Datenbank (eine Art 'aktives' Flag), Ihr Mailserver, Ihr Systemprotokoll usw.
Solche Szenarien helfen Ihnen auch beim Einrichten einer testgesteuerten Entwicklungsumgebung.
Und schließlich hilft Ihnen das Denken in Befehlen wirklich dabei, eine aufgabenorientierte Anwendung zu erstellen. Ihre Benutzer werden dies zu schätzen wissen :-)
Befehle ausdrücken
Django bietet zwei einfache Möglichkeiten, Befehle auszudrücken. Sie sind beide gültige Optionen und es ist nicht ungewöhnlich, die beiden Ansätze zu mischen.
Die Serviceschicht
Das Servicemodul wurde bereits von @Hedde beschrieben . Hier definieren Sie ein separates Modul und jeder Befehl wird als Funktion dargestellt.
services.py
def activate_user(user_id):
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
Formulare verwenden
Die andere Möglichkeit besteht darin, für jeden Befehl ein Django-Formular zu verwenden. Ich bevorzuge diesen Ansatz, weil er mehrere eng verwandte Aspekte kombiniert:
- Ausführung des Befehls (was macht er?)
- Validierung der Befehlsparameter (kann das?)
- Präsentation des Befehls (wie kann ich das machen?)
forms.py
class ActivateUserForm(forms.Form):
user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
# the username select widget is not a standard Django widget, I just made it up
def clean_user_id(self):
user_id = self.cleaned_data['user_id']
if User.objects.get(pk=user_id).active:
raise ValidationError("This user cannot be activated")
# you can also check authorizations etc.
return user_id
def execute(self):
"""
This is not a standard method in the forms API; it is intended to replace the
'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern.
"""
user_id = self.cleaned_data['user_id']
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
In Fragen denken
Ihr Beispiel enthielt keine Abfragen, daher habe ich mir erlaubt, einige nützliche Abfragen zu erstellen. Ich bevorzuge den Begriff "Frage", aber Abfragen sind die klassische Terminologie. Interessante Fragen sind: "Wie heißt dieser Benutzer?", "Kann sich dieser Benutzer anmelden?", "Liste der deaktivierten Benutzer anzeigen" und "Wie ist die geografische Verteilung der deaktivierten Benutzer?"
Bevor Sie mit der Beantwortung dieser Fragen beginnen, sollten Sie sich immer zwei Fragen stellen: Ist dies eine Präsentationsabfrage nur für meine Vorlagen und / oder eine Geschäftslogikabfrage, die an die Ausführung meiner Befehle gebunden ist, und / oder eine Berichtsabfrage .
Präsentationsabfragen dienen lediglich der Verbesserung der Benutzeroberfläche. Die Antworten auf Geschäftslogikabfragen wirken sich direkt auf die Ausführung Ihrer Befehle aus. Berichtsabfragen dienen lediglich analytischen Zwecken und unterliegen weniger zeitlichen Einschränkungen. Diese Kategorien schließen sich nicht gegenseitig aus.
Die andere Frage lautet: "Habe ich die vollständige Kontrolle über die Antworten?" Wenn wir beispielsweise den Benutzernamen abfragen (in diesem Zusammenhang), haben wir keine Kontrolle über das Ergebnis, da wir auf eine externe API angewiesen sind.
Abfragen stellen
Die grundlegendste Abfrage in Django ist die Verwendung des Manager-Objekts:
User.objects.filter(active=True)
Dies funktioniert natürlich nur, wenn die Daten tatsächlich in Ihrem Datenmodell dargestellt werden. Dies ist nicht immer der Fall. In diesen Fällen können Sie die folgenden Optionen in Betracht ziehen.
Benutzerdefinierte Tags und Filter
Die erste Alternative ist nützlich für Abfragen, die nur zur Präsentation dienen: benutzerdefinierte Tags und Vorlagenfilter.
template.html
<h1>Welcome, {{ user|friendly_name }}</h1>
template_tags.py
@register.filter
def friendly_name(user):
return remote_api.get_cached_name(user.id)
Abfragemethoden
Wenn Ihre Abfrage nicht nur präsentativ ist, können Sie Ihrer services.py Abfragen hinzufügen (falls Sie diese verwenden) oder ein queries.py- Modul einführen :
queries.py
def inactive_users():
return User.objects.filter(active=False)
def users_called_publysher():
for user in User.objects.all():
if remote_api.get_cached_name(user.id) == "publysher":
yield user
Proxy-Modelle
Proxy-Modelle sind im Kontext von Geschäftslogik und Berichterstellung sehr nützlich. Sie definieren grundsätzlich eine erweiterte Teilmenge Ihres Modells. Sie können das Basis-QuerySet eines Managers überschreiben, indem Sie die Manager.get_queryset()
Methode überschreiben .
models.py
class InactiveUserManager(models.Manager):
def get_queryset(self):
query_set = super(InactiveUserManager, self).get_queryset()
return query_set.filter(active=False)
class InactiveUser(User):
"""
>>> for user in InactiveUser.objects.all():
… assert user.active is False
"""
objects = InactiveUserManager()
class Meta:
proxy = True
Abfragemodelle
Für Abfragen, die von Natur aus komplex sind, aber häufig ausgeführt werden, besteht die Möglichkeit von Abfragemodellen. Ein Abfragemodell ist eine Form der Denormalisierung, bei der relevante Daten für eine einzelne Abfrage in einem separaten Modell gespeichert werden. Der Trick besteht natürlich darin, das denormalisierte Modell mit dem primären Modell synchron zu halten. Abfragemodelle können nur verwendet werden, wenn Änderungen vollständig unter Ihrer Kontrolle stehen.
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
Die erste Möglichkeit besteht darin, diese Modelle in Ihren Befehlen zu aktualisieren. Dies ist sehr nützlich, wenn diese Modelle nur durch einen oder zwei Befehle geändert werden.
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
Eine bessere Option wäre die Verwendung von benutzerdefinierten Signalen. Diese Signale werden natürlich von Ihren Befehlen ausgegeben. Signale haben den Vorteil, dass Sie mehrere Abfragemodelle mit Ihrem ursprünglichen Modell synchronisieren können. Darüber hinaus kann die Signalverarbeitung mithilfe von Sellerie oder ähnlichen Frameworks auf Hintergrundaufgaben verlagert werden.
signale.py
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
models.py
class InactiveUserDistribution(models.Model):
# see above
@receiver(user_activated)
def on_user_activated(sender, **kwargs):
user = kwargs['user']
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
Halten Sie es sauber
Wenn Sie diesen Ansatz verwenden, wird es lächerlich einfach festzustellen, ob Ihr Code sauber bleibt. Befolgen Sie einfach diese Richtlinien:
- Enthält mein Modell Methoden, die mehr als nur den Datenbankstatus verwalten? Sie sollten einen Befehl extrahieren.
- Enthält mein Modell Eigenschaften, die nicht Datenbankfeldern zugeordnet sind? Sie sollten eine Abfrage extrahieren.
- Verweist mein Modell auf eine Infrastruktur, die nicht meine Datenbank ist (z. B. E-Mail)? Sie sollten einen Befehl extrahieren.
Gleiches gilt für Ansichten (da Ansichten häufig unter demselben Problem leiden).
- Verwaltet meine Ansicht aktiv Datenbankmodelle? Sie sollten einen Befehl extrahieren.
Einige Referenzen
Django-Dokumentation: Proxy-Modelle
Django-Dokumentation: Signale
Architektur: Domain Driven Design