2009-04-30 5 views
29

Ich frage mich, ob es möglich war (und wenn ja, wie), mehrere Manager miteinander zu verketten, um einen Abfragesatz zu erstellen, der von den beiden einzelnen Managern beeinflusst wird. Ich werde das spezifische Beispiel erklären, an dem ich arbeite:Django Manager Verkettung

Ich habe mehrere abstrakte Modellklassen, die ich verwenden, um kleine, spezifische Funktionalität für andere Modelle bereitzustellen. Zwei dieser Modelle sind DeleteMixin und GlobalMixin.

Die DeleteMixin wird als solche definiert:

class DeleteMixin(models.Model): 
    deleted = models.BooleanField(default=False) 
    objects = DeleteManager() 

    class Meta: 
     abstract = True 

    def delete(self): 
     self.deleted = True 
     self.save() 

Grundsätzlich bietet es sich um eine pseudo-gelöscht (der gelöschte Flag), anstatt das Objekt tatsächlich gelöscht wird.

Die GlobalMixin wird als solche definiert:

class GlobalMixin(models.Model): 
    is_global = models.BooleanField(default=True) 

    objects = GlobalManager() 

    class Meta: 
     abstract = True 

Es ermöglicht jedes Objekt entweder als globales Objekt oder ein privates Objekt (wie eine öffentliche/private Blog-Post) definiert werden.

Beide haben ihre eigenen Manager, die sich auf das zurückgegebene Abfrage-Set auswirken. Mein DeleteManager filtert das Abfrage-Set, um nur Ergebnisse zurückzugeben, bei denen das gelöschte Flag auf False gesetzt ist, während der GlobalManager das Abfrage-Set so filtert, dass nur Ergebnisse zurückgegeben werden, die als global markiert sind. Hier ist die Erklärung für beide:

class DeleteManager(models.Manager): 
    def get_query_set(self): 
     return super(DeleteManager, self).get_query_set().filter(deleted=False) 

class GlobalManager(models.Manager): 
    def globals(self): 
     return self.get_query_set().filter(is_global=1) 

Die gewünschte Funktionalität wäre ein Modell haben beide dieser abstrakten Modelle erweitern und die Fähigkeit zu gewähren, um nur die Ergebnisse angezeigt, die sowohl nicht-gelöscht und global sind. Ich führte einen Testfall auf einem Modell mit 4 Instanzen durch: eine war global und nicht gelöscht, eine war global und gelöscht, eine war nicht-global und nicht-gelöscht, und eine war nicht-global und gelöscht. Wenn ich versuche, Ergebnismengen als solche zu erhalten: SomeModel.objects.all(), bekomme ich Instanz 1 und 3 (die zwei nicht gelöschten - großartig!). Wenn ich SomeModel.objects.globals() ausprobiere, erhalte ich den Fehler, dass DeleteManager keine Globals hat (vorausgesetzt, dass meine Model-Deklaration wie folgt lautet: SomeModel (DeleteMixin, GlobalMixin). Wenn ich die Order umspringe, t bekomme den Fehler, aber filtert die gelöschten nicht heraus). Wenn ich GlobalMixin ändere, um GlobalManager an Globals anstelle von Objekten anzuhängen (also wäre der neue Befehl SomeModel.globals.globals()), erhalte ich die Instanzen 1 und 2 (die zwei Globalen), während mein beabsichtigtes Ergebnis wäre, nur die Instanz zu bekommen 1 (der globale, nicht gelöschte).

Ich war mir nicht sicher, ob jemand in eine ähnliche Situation geraten war und zu einem Ergebnis gekommen war. Entweder ein Weg, um es in meinem aktuellen Denken funktionieren zu lassen oder eine Überarbeitung, die die Funktionalität bietet, nach der ich suche, wäre sehr willkommen. Ich weiß, dass dieser Beitrag ein wenig langatmig war. Wenn weitere Erklärungen benötigt werden, würde ich es gerne zur Verfügung stellen.

Edit:

ich, die Lösung gepostet habe ich unten auf dieses spezifische Problem verwendet. Es basiert auf dem Link zu Simons benutzerdefiniertem QuerySetManager.

Antwort

21

dieses Snippet auf Djangosnippets Siehe: http://djangosnippets.org/snippets/734/

Statt einen eigenen Methoden in einem Manager des Setzens, Unterklasse Sie die queryset selbst. Es ist sehr einfach und funktioniert perfekt. Das einzige Problem, das ich hatte, ist die Modellvererbung. Sie müssen den Manager immer in Modell-Unterklassen definieren (nur: "objects = QuerySetManager()" in der Unterklasse), obwohl sie das Abfrage-Set erben werden. Dies wird mehr Sinn machen, wenn Sie den QuerySetManager verwenden.

+0

, die den Trick getan zu haben scheint. Ich hätte wissen müssen, dass ich mich mehr auf den Abfragesatz und weniger auf den Manager konzentrieren sollte. Ich habe meine mögliche Lösung als Antwort veröffentlicht, obwohl ich Ihre als die beste ausgewählt habe. – Adam

2

Ich habe eine Weile damit verbracht, einen Weg zu finden, eine schöne Fabrik zu bauen, aber ich habe viele Probleme damit.

Das Beste, was ich Ihnen vorschlagen kann, ist Ihre Erbschaft zu ketten. Es ist nicht sehr allgemein gehalten, so dass ich bin mir nicht sicher, wie nützlich es ist, aber alles, was Sie tun müssten ist:

class GlobalMixin(DeleteMixin): 
    is_global = models.BooleanField(default=True) 

    objects = GlobalManager() 

    class Meta: 
     abstract = True 

class GlobalManager(DeleteManager): 
    def globals(self): 
     return self.get_query_set().filter(is_global=1) 

Wenn Sie etwas wollen, mehr generisch, das Beste, was ich mit oben kommen kann, ist eine definieren Basis Mixin und Manager, die get_query_set() neu definiert (ich nehme an, dass Sie das nur einmal tun wollen; die Dinge werden sonst ziemlich kompliziert) und dann eine Liste von Feldern übergeben, die über s hinzugefügt werden sollen.

Es wäre etwa so aussehen (nicht getestet):

class DeleteMixin(models.Model): 
    deleted = models.BooleanField(default=False) 

    class Meta: 
     abstract = True 

def create_mixin(base_mixin, **kwargs): 
    class wrapper(base_mixin): 
     class Meta: 
      abstract = True 
    for k in kwargs.keys(): 
     setattr(wrapper, k, kwargs[k]) 
    return wrapper 

class DeleteManager(models.Manager): 
    def get_query_set(self): 
     return super(DeleteManager, self).get_query_set().filter(deleted=False) 

def create_manager(base_manager, **kwargs): 
    class wrapper(base_manager): 
     pass 
    for k in kwargs.keys(): 
     setattr(wrapper, k, kwargs[k]) 
    return wrapper 

Ok, das ist also hässlich, aber was es Sie bekommt? Im Wesentlichen ist es die gleiche Lösung, aber viel dynamischer und ein wenig mehr DRY, obwohl komplexer zu lesen.

Zuerst erstellen Sie Ihren Manager dynamisch:

def globals(inst): 
    return inst.get_query_set().filter(is_global=1) 

GlobalDeleteManager = create_manager(DeleteManager, globals=globals) 

Dies schafft einen neuen Manager, die eine Unterklasse von DeleteManager und hat eine Methode namens globals.

Als Nächstes erstellen Sie Ihr mixin Modell:

GlobalDeleteMixin = create_mixin(DeleteMixin, 
           is_global=models.BooleanField(default=False), 
           objects = GlobalDeleteManager()) 

Wie ich schon sagte, es ist hässlich. Aber es bedeutet, dass Sie globals() nicht neu definieren müssen. Wenn Sie möchten, dass ein anderer Manager-Typ globals() hat, rufen Sie einfach create_manager erneut mit einer anderen Basis an. Und Sie können beliebig viele neue Methoden hinzufügen. Dasselbe gilt für den Manager, Sie fügen immer neue Funktionen hinzu, die verschiedene Abfragesätze zurückgeben.

Also, ist das wirklich praktisch? Vielleicht nicht. Diese Antwort ist eher eine Übung in (ab) der Verwendung von Pythons Flexibilität. Ich habe nicht versucht, dies zu verwenden, obwohl ich einige der zugrunde liegenden Prinzipien dynamisch erweiterbarer Klassen verwende, um den Zugriff zu erleichtern.

Lassen Sie mich wissen, wenn etwas unklar ist, und ich werde die Antwort aktualisieren.

+0

Das Erbe zu ketten würde definitiv funktionieren, aber leider würde es den Zweck meiner Mixins vereiteln. Es gibt Fälle (ich weiß nicht wann), wo ich etwas haben möchte, um nur GlobalMixin zu haben oder einfach DeleteMixin (oder beides) zu haben. Ich werde auf jeden Fall deinen anderen Vorschlag prüfen müssen. Ich stimme zu, dass es ein wenig hässlich ist, aber vielleicht kann es gereinigt werden, um die ähnliche Funktionalität in einem saubereren Paket bereitzustellen. – Adam

+0

Sie können sie mit dieser Methode machen, indem Sie einfach models.Model als Basis-Mixin und models.Manager als Basis-Manager übergeben, aber es lohnt sich nur, wenn Sie eine Menge Permutationen von Mixins und Managern haben. – tghw

8

Hier ist die spezifische Lösung für mein Problem mit dem benutzerdefinierten QuerySetManager von Simon, dass Scott verknüpft.

from django.db import models 
from django.contrib import admin 
from django.db.models.query import QuerySet 
from django.core.exceptions import FieldError 

class MixinManager(models.Manager):  
    def get_query_set(self): 
     try: 
      return self.model.MixinQuerySet(self.model).filter(deleted=False) 
     except FieldError: 
      return self.model.MixinQuerySet(self.model) 

class BaseMixin(models.Model): 
    admin = models.Manager() 
    objects = MixinManager() 

    class MixinQuerySet(QuerySet): 

     def globals(self): 
      try: 
       return self.filter(is_global=True) 
      except FieldError: 
       return self.all() 

    class Meta: 
     abstract = True 

class DeleteMixin(BaseMixin): 
    deleted = models.BooleanField(default=False) 

    class Meta: 
     abstract = True 

    def delete(self): 
     self.deleted = True 
     self.save() 

class GlobalMixin(BaseMixin): 
    is_global = models.BooleanField(default=True) 

    class Meta: 
     abstract = True 

Jede mixin in die Zukunft, die zusätzliche Funktionalität für die Abfrage muss eingestellt hinzufügen möchte einfach BaseMixin verlängern (oder es irgendwo in seiner Hierarchie haben). Jedes Mal, wenn ich versuche, die gestellte Anfrage zu filtern, habe ich sie in einen try-catch geschrieben, für den Fall, dass das Feld nicht existiert (dh es wird das Mixin nicht erweitert). Der globale Filter wird mit globals() aufgerufen, während der Löschfilter automatisch aufgerufen wird (wenn etwas gelöscht wird, möchte ich nie, dass es angezeigt wird). Die Verwendung dieses Systems ermöglicht die folgenden Arten von Befehlen:

TemporaryModel.objects.all() # If extending DeleteMixin, no deleted instances are returned 
TemporaryModel.objects.all().globals() # Filter out the private instances (non-global) 
TemporaryModel.objects.filter(...) # Ditto about excluding deleteds 

Einer Sache zu beachten ist, dass die Löschfilter werden Admin-Interfaces nicht beeinflussen, da die Standard-Manager zuerst erklärt (es den Standard machen). Ich erinnere mich nicht, als sie den Admin änderten, um Model._default_manager anstelle von Model zu verwenden.Objekte, aber alle gelöschten Instanzen werden weiterhin im Admin angezeigt (für den Fall, dass Sie sie löschen müssen).

+1

Wenn Sie möchten, dass der Administrator den gewünschten Manager verwendet, schauen Sie hier: http://stackoverflow.com/questions/1545067/django-specify-which-model-manager-django-admin-should-use –

Verwandte Themen