2010-01-29 4 views
43

Ich versuche, einen Weg zu finden, um eine benutzerdefinierte QuerySet und eine benutzerdefinierte Manager zu implementieren, ohne DRY zu brechen. Dies ist, was habe ich bisher:Benutzerdefinierte QuerySet und Manager ohne DRY zu brechen?

class MyInquiryManager(models.Manager): 
    def for_user(self, user): 
     return self.get_query_set().filter(
        Q(assigned_to_user=user) | 
        Q(assigned_to_group__in=user.groups.all()) 
       ) 

class Inquiry(models.Model): 
    ts = models.DateTimeField(auto_now_add=True) 
    status = models.ForeignKey(InquiryStatus) 
    assigned_to_user = models.ForeignKey(User, blank=True, null=True) 
    assigned_to_group = models.ForeignKey(Group, blank=True, null=True) 
    objects = MyInquiryManager() 

Das funktioniert gut, bis ich so etwas tun:

inquiries = Inquiry.objects.filter(status=some_status) 
my_inquiry_count = inquiries.for_user(request.user).count() 

Diese prompt bricht alles, weil die QuerySet nicht die gleichen Methoden wie die Manager . Ich habe versucht, eine benutzerdefinierte QuerySet Klasse zu erstellen und es in MyInquiryManager zu implementieren, aber am Ende repliziere ich alle meine Methodendefinitionen.

Ich fand auch this snippet, das funktioniert, aber ich brauche, um for_user in dem zusätzlichen Argumente übergeben, so nach unten bricht, weil es neu zu definieren get_query_set stark auf beruht.

Gibt es eine Möglichkeit, dies zu tun, ohne alle meine Methoden in den Unterklassen QuerySet und Manager neu zu definieren?

+0

Warnung: Die ausgewählte Antwort von T.Stone führt zu einer schwerwiegenden Leistungseinbuße (von Millisekunden-Antwortzeiten zu Antworten mit mehreren Sekunden), wenn die Methoden .defer oder only verwendet werden. Zum Beispiel, in Django 1.3 eine Abfrage wie zum Beispiel: MyModel.objects.only ('some_field'). Get (id = 1) => gibt in 3.7ms zurück, aber, fügen Sie den CustomManager wie oben beschrieben hinzu, und ich bekomme: MyModel.objects .only ('some_field'). get (id = 1) => kehrt in ~ 357ms zurück –

+0

Hat jemand anderes dies reproduziert? Was ist mit Django 1.4? – fletom

+0

Okay. Aber warum und wie passiert das? Sind die Abfragen anders oder haben Sie diese Operation profiliert, ohne tatsächlich auf die Datenbank zu treffen? –

Antwort

45

Django hat sich geändert! Bevor Sie den Code in dieser Antwort verwenden, der 2009 verfasst wurde, sollten Sie sich die restlichen Antworten und die Django-Dokumentation ansehen, um zu sehen, ob es eine geeignetere Lösung gibt.


So wie ich dies umgesetzt haben, ist durch die tatsächliche get_active_for_account als Methode eines benutzerdefinierten QuerySet hinzufügen. Dann machen sie den Manager abarbeiten, können Sie einfach fangen die __getattr__ und senden Sie es entsprechend

Um dieses Muster wiederverwendbar zu machen, ich habe die Manager Bits zu einem separaten Modell-Manager extrahiert aus:

custom_queryset/models.py

from django.db import models 
from django.db.models.query import QuerySet 

class CustomQuerySetManager(models.Manager): 
    """A re-usable Manager to access a custom QuerySet""" 
    def __getattr__(self, attr, *args): 
     try: 
      return getattr(self.__class__, attr, *args) 
     except AttributeError: 
      # don't delegate internal methods to the queryset 
      if attr.startswith('__') and attr.endswith('__'): 
       raise 
      return getattr(self.get_query_set(), attr, *args) 

    def get_query_set(self): 
     return self.model.QuerySet(self.model, using=self._db) 

Sobald Sie das bekam, auf Ihre Modelle alles, was Sie tun müssen, ist eine QuerySet als benutzerdefinierte innere Klasse definieren und den Manager zu Ihrer benutzerdefinierten Manager eingestellt:

your_app/models.py

from custom_queryset.models import CustomQuerySetManager 
from django.db.models.query import QuerySet 

class Inquiry(models.Model): 
    objects = CustomQuerySetManager() 

    class QuerySet(QuerySet): 
     def active_for_account(self, account, *args, **kwargs): 
      return self.filter(account=account, deleted=False, *args, **kwargs) 

Mit diesem Muster, eine dieser funktioniert:

>>> Inquiry.objects.active_for_account(user) 
>>> Inquiry.objects.all().active_for_account(user) 
>>> Inquiry.objects.filter(first_name='John').active_for_account(user) 
+1

Das ist so cool – wakandan

+0

können Sie entscheiden, was mit http://stackoverflow.com/edit-suggestions/1216 getan werden sollte –

+0

@Sam Saffron würde ich nehmen, bearbeiten –

-1

Folgendes funktioniert für mich.

def get_active_for_account(self,account,*args,**kwargs): 
    """Returns a queryset that is 
    Not deleted 
    For the specified account 
    """ 
    return self.filter(account = account,deleted=False,*args,**kwargs) 

Dies ist auf dem Standard-Manager; also habe ich etwas gemacht wie:

Model.objects.get_active_for_account(account).filter() 

Aber es gibt keinen Grund, warum es nicht für einen sekundären Manager funktionieren sollte.

+3

Probieren Sie einen 'Filter' aus und verwenden Sie dann' get_active_for_account'. Es funktioniert in Ihrem Beispiel, aber nicht, nachdem Sie bereits einen 'Filter' verwendet haben, und arbeiten dann mit einem' QuerySet', was mein Beispiel ist. –

3

Eine leicht verbesserte Version von T.Stein Ansatz:

def objects_extra(mixin_class): 
    class MixinManager(models.Manager, mixin_class): 
     class MixinQuerySet(QuerySet, mixin_class): 
      pass 

     def get_query_set(self): 
      return self.MixinQuerySet(self.model, using=self._db) 

    return MixinManager() 

Klasse Dekorateure machen Nutzung so einfach wie:

class SomeModel(models.Model): 
    ... 
    @objects_extra 
    class objects: 
     def filter_by_something_complex(self, whatever parameters): 
      return self.extra(...) 
     ... 

Update: Unterstützung für Nicht-Standard-Manager und QuerySet Basisklassen, e. G. @objects_extra (django.contrib.gis.db.models.GeoManager, django.contrib.gis.db.models.query.GeoQuerySet):

def objects_extra(Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet): 
    def oe_inner(Mixin, Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet): 
     class MixinManager(Manager, Mixin): 
      class MixinQuerySet(QuerySet, Mixin): 
       pass 

      def get_query_set(self): 
       return self.MixinQuerySet(self.model, using=self._db) 

     return MixinManager() 

    if issubclass(Manager, django.db.models.Manager): 
     return lambda Mixin: oe_inner(Mixin, Manager, QuerySet) 
    else: 
     return oe_inner(Mixin=Manager) 
+0

Das ist ziemlich toll, sie haben solche Dekorateur in der Django selbst –

+1

Mein Django will 'get_queryset' überschrieben werden, nicht' get_query_set'. –

10

Sie können die Methoden auf dem Manager zur Verfügung stellen und queryset eine mixin mit . Siehe die folgende Technik:

http://hunterford.me/django-custom-model-manager-chaining/

Dies vermeidet auch die Verwendung eines __getattr__() Ansatz.

from django.db.models.query import QuerySet 

class PostMixin(object): 
    def by_author(self, user): 
     return self.filter(user=user) 

    def published(self): 
     return self.filter(published__lte=datetime.now()) 

class PostQuerySet(QuerySet, PostMixin): 
    pass 

class PostManager(models.Manager, PostMixin): 
    def get_query_set(self): 
     return PostQuerySet(self.model, using=self._db) 
22

Der Django 1.7 veröffentlicht eine neue und einfache Art und Weise kombiniert queryset und Modell-Manager zu erstellen:

class InquiryQuerySet(models.QuerySet): 
    def for_user(self): 
     return self.filter(
      Q(assigned_to_user=user) | 
      Q(assigned_to_group__in=user.groups.all()) 
     ) 

class Inquiry(models.Model): 
    objects = InqueryQuerySet.as_manager() 

Siehe Creating Manager with QuerySet methods für weitere Details.

+0

haha, chinesische Antwort ist das Beste für mich! – tcpiper

+1

Dies ist der beste Weg, um es zu tun, aber es wäre ein Beispiel dafür, wie die 'for_user'-Methode einen Benutzer nehmen und' self zurückgeben [...] soll, um mehrere Operationen zu verketten. –

Verwandte Themen