webentwicklung-frage-antwort-db.com.de

Trennung von Geschäftslogik und Datenzugriff in django

Ich schreibe ein Projekt in Django und sehe, dass 80% des Codes in der Datei models.py. Dieser Code ist verwirrend und nach einer gewissen Zeit verstehe ich nicht mehr, was wirklich passiert.

Folgendes stört mich:

  1. Ich finde es hässlich, dass meine Modellebene (die eigentlich nur für die Arbeit mit Daten aus einer Datenbank verantwortlich sein sollte) auch E-Mails sendet, auf APIs zu anderen Diensten wechselt usw.
  2. Außerdem finde ich es inakzeptabel, Geschäftslogik in der Ansicht zu platzieren, da es auf diese Weise schwierig wird, sie zu kontrollieren. In meiner Anwendung gibt es beispielsweise mindestens drei Möglichkeiten, um neue Instanzen von User zu erstellen, technisch gesehen sollten sie jedoch einheitlich erstellt werden.
  3. Ich bemerke nicht immer, wann die Methoden und Eigenschaften meiner Modelle nicht deterministisch werden und wann sie Nebenwirkungen entwickeln.

Hier ist ein einfaches Beispiel. Das User Modell sah anfangs so aus:

class User(db.Models):

    def get_present_name(self):
        return self.name or 'Anonymous'

    def activate(self):
        self.status = 'activated'
        self.save()

Im Laufe der Zeit stellte sich Folgendes heraus:

class User(db.Models):

    def get_present_name(self): 
        # property became non-deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

    def activate(self):
        # method now has a side effect (send message to user)
        self.status = 'activated'
        self.save()
        send_mail('Your account is activated!', '…', [self.email])

Ich möchte, dass Entitäten in meinem Code getrennt werden:

  1. Entitäten meiner Datenbank, Datenbankebene: Was enthält meine Anwendung?
  2. Entitäten meiner Anwendung, Geschäftslogikebene: Was kann meine Anwendung machen?

Was sind die guten Praktiken, um einen solchen Ansatz zu implementieren, der in Django angewendet werden kann?

428
defuz

Anscheinend fragen Sie nach dem Unterschied zwischen dem Datenmodell und dem Domänenmodell - In letzterem finden Sie die Geschäftslogik und die wahrgenommenen Entitäten Bei Ihrem Endbenutzer ist Ersteres der Ort, an dem Sie Ihre Daten tatsächlich speichern.

Außerdem habe ich den dritten Teil Ihrer Frage so interpretiert, dass festgestellt wird, dass diese Modelle nicht getrennt sind.

Dies sind zwei sehr unterschiedliche Konzepte, und es ist immer schwierig, sie voneinander zu trennen. Es gibt jedoch einige gebräuchliche Muster und Werkzeuge, die für diesen Zweck verwendet werden können.

Über das Domain-Modell

Als Erstes müssen Sie erkennen, 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 "Was" ist der Name dieses Benutzers? " In klassischen Begriffen: Es geht um Abfragen und Befehle.

In Befehlen denken

Beginnen wir mit den Befehlen in Ihrem Beispiel: "Diesen Benutzer aktivieren" und "Diesen Benutzer deaktivieren". Das Schöne an Befehlen ist, dass sie leicht durch kleine Szenarien ausgedrückt werden können:

hat einen inaktiven Benutzer angegeben
wenn der Administrator diesen Benutzer aktiviert
dann wird der Benutzer aktiv
und wird eine Bestätigungs-E-Mail an den Benutzer gesendet
und wird dem Systemprotokoll ein Eintrag hinzugefügt
(etc. etc.)

Solche Szenarien sind nützlich, um zu sehen, wie verschiedene Teile Ihrer Infrastruktur von einem einzigen Befehl betroffen sein können - in diesem Fall Ihre Datenbank (eine Art "Aktiv" -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 beim Erstellen einer aufgabenorientierten Anwendung. Ihre Nutzer werden es 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 kombinieren.

Die Service-Schicht

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, ein Django Form für jeden Befehl zu verwenden. Ich bevorzuge diesen Ansatz, da er mehrere eng verwandte Aspekte kombiniert:

  • ausführung des Kommandos (was macht es?)
  • validierung der Befehlsparameter (kann es das tun?)
  • 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 Abfragen denken

Ihr Beispiel enthielt keine Abfragen, daher habe ich mir erlaubt, ein paar 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äsentation Abfrage nur für meine Vorlagen und/oder eine Geschäftslogik Abfrage gebunden an die Ausführung meiner Befehle und/oder eine Reporting Abfrage.

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 geringeren zeitlichen Einschränkungen. Diese Kategorien schließen sich nicht gegenseitig aus.

Die andere Frage lautet: "Habe ich die volle Kontrolle über die Antworten?" Beispielsweise haben wir bei der Abfrage des Benutzernamens (in diesem Kontext) keine Kontrolle über das Ergebnis, da wir auf eine externe API angewiesen sind.

Abfragen

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 lediglich präsentationsbezogen sind: 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 Abfragen zu Ihrer Datei services.py hinzufügen (falls Sie diese verwenden) oder eine Datei queries.py einfügen Modul:

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. Grundsätzlich definieren Sie 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, gibt es 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 von Ihnen gesteuert werden.

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 benutzerdefinierter Signale. Diese Signale werden natürlich von Ihren Befehlen ausgegeben. Signale haben den Vorteil, dass Sie mehrere Abfragemodelle mit Ihrem ursprünglichen Modell synchron halten können. Darüber hinaus kann die Signalverarbeitung mithilfe von Sellerie oder ähnlichen Frameworks in Hintergrundaufgaben verlagert werden.

signals.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()

Halte es sauber

Mit diesem Ansatz lässt sich auf lächerliche Weise leicht feststellen, 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 Datenbankfeldern nicht zugeordnet werden können? Sie sollten eine Abfrage extrahieren.
  • Entspricht meine Modellreferenzinfrastruktur nicht meiner Datenbank (z. B. E-Mail)? Sie sollten einen Befehl extrahieren.

Gleiches gilt für Views (da Views häufig unter dem gleichen 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

563
publysher

Normalerweise implementiere ich eine Serviceebene zwischen Ansichten und Modellen. Dies verhält sich wie die API Ihres Projekts und gibt Ihnen einen guten Überblick über das Geschehen im Hubschrauber. Diese Praxis habe ich von einem Kollegen geerbt, der diese Überlagerungstechnik häufig für Java projects (JSF) verwendet, z. B .:

models.py

class Book:
   author = models.ForeignKey(User)
   title = models.CharField(max_length=125)

   class Meta:
       app_label = "library"

services.py

from library.models import Book

def get_books(limit=None, **filters):
    """ simple service function for retrieving books can be widely extended """
    if limit:
        return Book.objects.filter(**filters)[:limit]
    return Book.objects.filter(**filters)

views.py

from library.services import get_books

class BookListView(ListView):
    """ simple view, e.g. implement a _build and _apply filters function """
    queryset = get_books()

Wohlgemerkt, ich nehme normalerweise Modelle, Ansichten und Dienste auf Modulebene und trenne sie je nach Projektgröße noch weiter

131

Zuallererst Wiederhole dich nicht .

Achten Sie dann bitte darauf, dass Sie nicht überplanen. Manchmal ist dies nur Zeitverschwendung und führt dazu, dass jemand den Fokus auf das verliert, was wichtig ist. Überprüfen Sie das Zen von Python von Zeit zu Zeit.

Sehen Sie sich aktive Projekte an

  • mehr Menschen = mehr müssen sich ordentlich organisieren
  • das Django-Repository Sie haben eine einfache Struktur.
  • das Pip-Repository Sie haben eine geradlinige Verzeichnisstruktur.
  • das Fabric Repository ist auch eine gute Wahl.

    • sie können alle Ihre Modelle unter yourapp/models/logicalgroup.py platzieren.
  • zum Beispiel können User, Group und verwandte Modelle unter yourapp/models/users.py abgelegt werden.
  • beispiel: Poll, Question, Answer ... könnte unter yourapp/models/polls.py stehen.
  • lade was du brauchst in __all__ innerhalb von yourapp/models/__init__.py

Weitere Informationen zu MVC

  • modell ist Ihre Daten
    • dies schließt Ihre tatsächlichen Daten ein
    • dies schließt auch Ihre Sitzungs-/Cookie-/Cache-/FS-/Indexdaten ein
  • benutzer interagiert mit dem Controller, um das Modell zu manipulieren
    • dies kann eine API oder eine Ansicht sein, die Ihre Daten speichert/aktualisiert
    • dies kann mit request.GET/request.POST ... usw. eingestellt werden
    • denken Sie auch an Paging oder Filtern .
  • die Daten aktualisieren die Ansicht
    • die Vorlagen nehmen die Daten und formatieren sie entsprechend
    • APIs, auch ohne Vorlagen, sind Teil der Ansicht. z.B. tastypie oder piston
    • dies sollte auch die Middleware berücksichtigen.

Nutzen Sie Middleware / Templatetags

  • Wenn Sie für jede Anfrage etwas Arbeit erledigen müssen, ist Middleware eine Möglichkeit.
    • z.B. Hinzufügen von Zeitstempeln
    • z.B. Aktualisieren von Metriken zu Seitenzugriffen
    • z.B. einen Cache füllen
  • Wenn Sie Code-Snippets haben, die immer wieder zum Formatieren von Objekten verwendet werden, sind Vorlagen-Tags gut.
    • z.B. aktive tab/url Paniermehl

Nutzen Sie Modellmanager

  • das Erstellen von User kann in eine UserManager(models.Manager) gehen.
  • einzelheiten zu Instanzen sollten auf dem models.Model vermerkt sein.
  • die wichtigsten Details für queryset könnten in einem models.Manager stehen.
  • möglicherweise möchten Sie nacheinander ein User erstellen, sodass Sie vielleicht der Meinung sind, dass es auf dem Modell selbst basieren sollte. Beim Erstellen des Objekts verfügen Sie jedoch möglicherweise nicht über alle Details:

Beispiel:

class UserManager(models.Manager):
   def create_user(self, username, ...):
      # plain create
   def create_superuser(self, username, ...):
      # may set is_superuser field.
   def activate(self, username):
      # may use save() and send_mail()
   def activate_in_bulk(self, queryset):
      # may use queryset.update() instead of save()
      # may use send_mass_mail() instead of send_mail()

Verwenden Sie nach Möglichkeit Formulare

Wenn Sie Formulare haben, die einem Modell zugeordnet sind, können Sie eine Menge Code aus dem Boilerplate entfernen. Das ModelForm documentation ist ziemlich gut. Das Trennen des Codes für Formulare vom Modellcode kann sinnvoll sein, wenn Sie viele Anpassungen vornehmen (oder manchmal zyklische Importfehler für fortgeschrittenere Verwendungszwecke vermeiden).

Verwenden Sie Verwaltungsbefehle , wenn möglich

  • z.B. yourapp/management/commands/createsuperuser.py
  • z.B. yourapp/management/commands/activateinbulk.py

Wenn Sie Geschäftslogik haben, können Sie diese trennen

  • Django.contrib.authverwendet Backends , genau wie db ein Backend hat ... etc.
  • fügen Sie ein setting für Ihre Geschäftslogik hinzu (z. B. AUTHENTICATION_BACKENDS).
  • du könntest Django.contrib.auth.backends.RemoteUserBackend benutzen
  • du könntest yourapp.backends.remote_api.RemoteUserBackend benutzen
  • du könntest yourapp.backends.memcached.RemoteUserBackend benutzen
  • delegieren Sie die schwierige Geschäftslogik an das Backend
  • stellen Sie sicher, dass die Erwartung direkt am Ein-/Ausgang eingestellt ist.
  • das Ändern der Geschäftslogik ist so einfach wie das Ändern einer Einstellung :)

backend-Beispiel:

class User(db.Models):
    def get_present_name(self): 
        # property became not deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

könnte werden:

class User(db.Models):
   def get_present_name(self):
      for backend in get_backends():
         try:
            return backend.get_present_name(self)
         except: # make pylint happy.
            pass
      return None

mehr über Designmuster

mehr über Schnittstellengrenzen

  • Ist der Code, den Sie verwenden möchten, wirklich Teil der Modelle? -> yourapp.models
  • Ist der Code Teil der Geschäftslogik? -> yourapp.vendor
  • Ist der Code Teil von generischen Tools/Bibliotheken? -> yourapp.libs
  • Ist der Code Teil der Geschäftslogik-Bibliotheken? -> yourapp.libs.vendor Oder yourapp.vendor.libs
  • Hier ist ein guter Tipp: Können Sie Ihren Code unabhängig testen?
    • ja gut :)
    • nein, möglicherweise liegt ein Schnittstellenproblem vor
    • wenn es eine klare Trennung gibt, sollte Unittest ein Kinderspiel sein mit die Verwendung von Spott
  • Ist die Trennung logisch?
    • ja gut :)
    • nein, möglicherweise haben Sie Probleme, diese logischen Konzepte separat zu testen.
  • Meinen Sie, Sie müssen umgestalten, wenn Sie 10x mehr Code erhalten?
    • ja, nein gut, nein bueno, refactor könnte eine menge arbeit sein
    • nein, das ist einfach großartig!

Kurz gesagt, Sie könnten

  • yourapp/core/backends.py
  • yourapp/core/models/__init__.py
  • yourapp/core/models/users.py
  • yourapp/core/models/questions.py
  • yourapp/core/backends.py
  • yourapp/core/forms.py
  • yourapp/core/handlers.py
  • yourapp/core/management/commands/__init__.py
  • yourapp/core/management/commands/closepolls.py
  • yourapp/core/management/commands/removeduplicates.py
  • yourapp/core/middleware.py
  • yourapp/core/signals.py
  • yourapp/core/templatetags/__init__.py
  • yourapp/core/templatetags/polls_extras.py
  • yourapp/core/views/__init__.py
  • yourapp/core/views/users.py
  • yourapp/core/views/questions.py
  • yourapp/core/signals.py
  • yourapp/lib/utils.py
  • yourapp/lib/textanalysis.py
  • yourapp/lib/ratings.py
  • yourapp/vendor/backends.py
  • yourapp/vendor/morebusinesslogic.py
  • yourapp/vendor/handlers.py
  • yourapp/vendor/middleware.py
  • yourapp/vendor/signals.py
  • yourapp/tests/test_polls.py
  • yourapp/tests/test_questions.py
  • yourapp/tests/test_duplicates.py
  • yourapp/tests/test_ratings.py

oder irgendetwas anderes, das dir hilft; Finden Sie die Schnittstellen, die Sie benötigen und die Grenzen werden Ihnen helfen.

65
dnozay

Django verwendet eine leicht modifizierte Art von MVC. In Django gibt es kein Konzept eines "Controllers". Der nächstgelegene Proxy ist eine "Ansicht", die zu Verwechslungen mit MVC-Konvertierungen führt, da eine Ansicht in MVC eher der "Vorlage" von Django ähnelt.

In Django ist ein "Modell" nicht nur eine Datenbankabstraktion. In gewisser Hinsicht teilt es seine Aufgabe mit der "Sichtweise" des Django als Controller von MVC. Es enthält das gesamte einer Instanz zugeordnete Verhalten. Wenn diese Instanz im Rahmen ihres Verhaltens mit einer externen API interagieren muss, ist dies immer noch Modellcode. Tatsächlich müssen Modelle überhaupt nicht mit der Datenbank interagieren, sodass Sie sich Modelle vorstellen können, die vollständig als interaktive Ebene für eine externe API existieren. Es ist ein viel freieres Konzept eines "Modells".

24
Chris Pratt

In Django unterscheidet sich die MVC-Struktur, wie Chris Pratt sagte, von dem klassischen MVC-Modell, das in anderen Frameworks verwendet wird. Ich denke, der Hauptgrund dafür ist die Vermeidung einer zu strengen Anwendungsstruktur, wie dies in anderen MVC-Frameworks wie CakePHP der Fall ist.

In Django wurde MVC folgendermaßen implementiert:

Ansichtsebene ist in zwei Teile geteilt. Die Ansichten sollten nur zum Verwalten von HTTP-Anforderungen verwendet werden. Sie werden aufgerufen und beantworten diese. Ansichten kommunizieren mit dem Rest Ihrer Anwendung (Formulare, Modellformulare, benutzerdefinierte Klassen, in einfachen Fällen direkt mit Modellen). Um die Schnittstelle zu erstellen, verwenden wir Vorlagen. Vorlagen sind für Django wie Strings, es ordnet ihnen einen Kontext zu, und dieser Kontext wurde der Ansicht von der Anwendung mitgeteilt (wenn die Ansicht danach fragt).

Die Modellschicht bietet Kapselung, Abstraktion, Validierung, Intelligenz und macht Ihre Daten objektorientiert (eines Tages wird DBMS dies auch tun). Dies bedeutet nicht, dass Sie riesige models.py-Dateien erstellen sollten (in der Tat ist es ein sehr guter Rat, Ihre Modelle in verschiedene Dateien aufzuteilen, sie in einen Ordner namens 'models' zu legen und eine '__init__.py'-Datei zu erstellen) Ordner, in den Sie alle Ihre Modelle importieren und schließlich das Attribut 'app_label' von models.Model (Klasse) verwenden. Das Modell sollte Sie von der Arbeit mit Daten abhalten, es wird Ihre Anwendung einfacher machen. Sie sollten bei Bedarf auch externe Klassen erstellen, z. B. "Werkzeuge" für Ihre Modelle. Sie können Erbe auch in Modellen verwenden, indem Sie das Attribut "abstract" der Meta-Klasse Ihres Modells auf "True" setzen.

Wo ist der Rest? Nun, kleine Webanwendungen sind im Allgemeinen eine Art Schnittstelle zu Daten. In einigen kleinen Programmfällen würde es ausreichen, Ansichten zum Abfragen oder Einfügen von Daten zu verwenden. In häufigeren Fällen werden Formulare oder Modellformulare verwendet, die eigentlich "Controller" sind. Dies ist keine andere als eine praktische und sehr schnelle Lösung für ein häufig auftretendes Problem. Es ist das, was eine Website zu tun hat.

Wenn Formulare für Sie nicht ausreichend sind, sollten Sie Ihre eigenen Klassen erstellen, um die Magie auszuführen. Ein sehr gutes Beispiel hierfür ist die Admin-Anwendung: Sie können ModelAmin-Code lesen, dies funktioniert tatsächlich als Controller. Es gibt keine Standardstruktur, ich empfehle Ihnen, vorhandene Django Apps zu untersuchen, dies hängt von jedem Fall ab. Das ist, was Django Entwickler beabsichtigt haben, können Sie hinzufügen XML-Parser-Klasse, eine API-Connector-Klasse, fügen Sie Sellerie für die Ausführung von Aufgaben hinzu, verwenden Sie nur den ORM, erstellen Sie einen Webdienst, ändern Sie die Administratoranwendung und vieles mehr ... Es liegt in Ihrer Verantwortung, qualitativ hochwertigen Code zu erstellen Respektieren Sie die MVC-Philosophie oder nicht, machen Sie es modular und erstellen Sie Ihre eigenen Abstraktionsebenen.

Mein Rat: Lies so viel Code wie möglich, es gibt viele Django= Anwendungen, aber nimm sie nicht so ernst. Jeder Fall ist anders, Muster und Theorie helfen, aber nicht immer Dies ist eine ungenaue Erkenntnis. Django=================================================================================== andere), aber gute Entwürfe kommen von erfahrenen Designern.

PS .: Verwenden Sie die Klasse 'User' aus der Auth-Anwendung (von Standard-Django), Sie können zum Beispiel Benutzerprofile erstellen oder zumindest deren Code lesen, dies wird für Ihren Fall nützlich sein.

6
Nate Gentile

Ich bin größtenteils mit der gewählten Antwort einverstanden ( https://stackoverflow.com/a/12857584/871392 ), möchte aber die Option im Abschnitt "Abfragen erstellen" hinzufügen.

Man kann QuerySet-Klassen für Modelle definieren, um Filterabfragen durchzuführen und zu aktivieren. Danach können Sie diese Queryset-Klasse für den Manager des Modells als Proxy verwenden, wie dies bei den Klassen build-in Manager und QuerySet der Fall ist.

Wenn Sie jedoch mehrere Datenmodelle abfragen mussten, um ein Domänenmodell zu erhalten, erscheint es mir vernünftiger, dieses wie zuvor vorgeschlagen in ein separates Modul zu integrieren.

0
l0ki

Ich müsste dir zustimmen. In Django) gibt es viele Möglichkeiten, aber am besten beginnen Sie mit einem Review Djangos Design-Philosophie .

  1. Das Aufrufen einer API von einer Modelleigenschaft aus wäre nicht ideal. Es scheint sinnvoller, in der Ansicht so etwas zu tun und möglicherweise eine Serviceebene zu erstellen, um die Dinge trocken zu halten. Wenn der Aufruf der API nicht blockiert und der Aufruf teuer ist, ist es möglicherweise sinnvoll, die Anforderung an einen Service-Worker (einen Worker, der aus einer Warteschlange verwendet) zu senden.

  2. Gemäß Djangos Designphilosophie fassen Modelle jeden Aspekt eines "Objekts" zusammen. Daher sollte die gesamte Geschäftslogik, die sich auf dieses Objekt bezieht, dort leben:

Alle relevanten Domänenlogiken einschließen

Modelle sollten jeden Aspekt eines „Objekts“ nach Martin Fowlers Active Record-Entwurfsmuster einschließen.

  1. Die von Ihnen beschriebenen Nebenwirkungen sind offensichtlich. Die Logik hier könnte besser in Querysets und Manager unterteilt werden. Hier ist ein Beispiel:

    models.py

    import datetime
    
    from djongo import models
    from Django.db.models.query import QuerySet
    from Django.contrib import admin
    from Django.db import transaction
    
    
    class MyUser(models.Model):
    
        present_name = models.TextField(null=False, blank=True)
        status = models.TextField(null=False, blank=True)
        last_active = models.DateTimeField(auto_now=True, editable=False)
    
        # As mentioned you could put this in a template tag to pull it
        # from cache there. Depending on how it is used, it could be
        # retrieved from within the admin view or from a custom view
        # if that is the only place you will use it.
        #def get_present_name(self):
        #    # property became non-deterministic in terms of database
        #    # data is taken from another service by api
        #    return remote_api.request_user_name(self.uid) or 'Anonymous'
    
        # Moved to admin as an action
        # def activate(self):
        #     # method now has a side effect (send message to user)
        #     self.status = 'activated'
        #     self.save()
        #     # send email via email service
        #     #send_mail('Your account is activated!', '…', [self.email])
    
        class Meta:
            ordering = ['-id']  # Needed for DRF pagination
    
        def __unicode__(self):
            return '{}'.format(self.pk)
    
    
    class MyUserRegistrationQuerySet(QuerySet):
    
        def for_inactive_users(self):
            new_date = datetime.datetime.now() - datetime.timedelta(days=3*365)  # 3 Years ago
            return self.filter(last_active__lte=new_date.year)
    
        def by_user_id(self, user_ids):
            return self.filter(id__in=user_ids)
    
    
    class MyUserRegistrationManager(models.Manager):
    
        def get_query_set(self):
            return MyUserRegistrationQuerySet(self.model, using=self._db)
    
        def with_no_activity(self):
            return self.get_query_set().for_inactive_users()
    

    admin.py

    # Then in model admin
    
    class MyUserRegistrationAdmin(admin.ModelAdmin):
        actions = (
            'send_welcome_emails',
        )
    
        def send_activate_emails(self, request, queryset):
            rows_affected = 0
            for obj in queryset:
                with transaction.commit_on_success():
                    # send_email('welcome_email', request, obj) # send email via email service
                    obj.status = 'activated'
                    obj.save()
                    rows_affected += 1
    
            self.message_user(request, 'sent %d' % rows_affected)
    
    admin.site.register(MyUser, MyUserRegistrationAdmin)
    
0
radtek