2009-04-30 10 views
29

mi chiedevo se fosse possibile (e, in caso affermativo, come) per concatenare più gestori per produrre una serie di query che è influenzato sia dei singoli gestori. Mi spiego l'esempio specifico che sto lavorando su:Django Gestore concatenazione

ho più classi di modelli astratti che uso per fornire piccoli, funzionalità specifiche per gli altri modelli. Due di questi modelli sono un DeleteMixin e un GlobalMixin.

La DeleteMixin è definito come tale:

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

    class Meta: 
     abstract = True 

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

Fondamentalmente si fornisce una pseudo-delete (bandiera soppresso) anziché effettivamente eliminare l'oggetto.

Il GlobalMixin è definito come tale:

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

    objects = GlobalManager() 

    class Meta: 
     abstract = True 

Esso consente a qualsiasi oggetto per essere definita come un oggetto globale o di un oggetto privato (ad esempio, un post pubblico/privato).

Entrambi questi hanno i loro propri manager che influenzano il set di query che viene restituito. Il mio DeleteManager filtra il set di query per restituire solo i risultati che hanno il flag eliminato impostato su False, mentre il GlobalManager filtra il set di query per restituire solo i risultati contrassegnati come globali. Ecco la dichiarazione per entrambi:

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) 

la funzionalità desiderata sarebbe quella di avere un modello di estendere entrambi questi modelli astratti e concedere la possibilità di restituire solo i risultati che sono entrambi non-cancellato e globale. Ho eseguito un test case su un modello con 4 istanze: uno era globale e non eliminato, uno era globale e cancellato, uno era non globale e non cancellato, e uno era non globale e cancellato. Se provo a ottenere i set di risultati come tali: SomeModel.objects.all(), ottengo istanze 1 e 3 (le due non eliminate - grandiose!). Se provo SomeModel.objects.globals(), ottengo un errore che DeleteManager non ha un globali (questo è supponendo il mio modello di dichiarazione è come tale:. SomeModel (DeleteMixin, GlobalMixin) Se io invertire l'ordine, I don' t ottenere l'errore, ma non filtra quelli cancellati). Se cambio GlobalMixin di allegare GlobalManager a variabili globali invece di oggetti (in modo che il nuovo comando sarebbe SomeModel.globals.globals()), ottengo casi 1 e 2 (i due globali), mentre il mio risultato previsto sarebbe quello di ottenere solo esempio 1 (il globale, non cancellato).

io non ero sicuro se qualcuno avesse eseguito in qualsiasi situazione simile a questo ed era venuto ad un risultato. O un modo per farlo funzionare nel mio attuale pensiero o una rielaborazione che fornisce la funzionalità che sto cercando sarebbe molto apprezzato. So che questo post è stato un po 'prolisso. Se sono necessarie ulteriori spiegazioni, sarei lieto di fornirle.

Edit:

ho postato la soluzione finale che ho usato per questo problema specifico di seguito. Si basa sul collegamento al QuerySetManager personalizzato di Simon.

risposta

21

Vedi questo frammento su Djangosnippets: http://djangosnippets.org/snippets/734/

Invece di mettere i vostri metodi personalizzati in un manager, si sottoclasse il set di query stessa. È molto semplice e funziona perfettamente. L'unico problema che ho riscontrato riguarda l'ereditarietà del modello, devi sempre definire il gestore nelle sottoclassi del modello (solo: "objects = QuerySetManager()" nella sottoclasse), anche se erediteranno il set di query. Ciò avrà più senso quando si utilizza QuerySetManager.

+0

che sembra aver fatto il trucco. Avrei dovuto sapere di concentrarmi maggiormente sul set di query e meno sul gestore. Ho pubblicato la mia soluzione finale come risposta, anche se ho scelto il tuo come il migliore. – Adam

2

Ho passato un po 'di tempo a cercare un modo per costruire una bella fabbrica per farlo, ma mi sto imbattendo in molti problemi.

Il meglio che posso suggerirvi è di concatenare la vostra eredità. Non è molto generico, quindi non sono sicuro di quanto sia utile, ma tutto quello che dovrebbe fare è:

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) 

Se volete qualcosa di più generico, il meglio che posso venire con è quello di definire un base Mixin e Manager che ridefinisce get_query_set() (presumo che si desideri eseguire questa operazione solo una volta, altrimenti le cose si complicano notevolmente) e quindi passare un elenco di campi che si desidera aggiungere tramite Mixin s.

Sarebbe sembrare qualcosa di simile (non testato a tutti):

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, quindi questo è brutto, ma che cosa si ottiene? Essenzialmente, è la stessa soluzione, ma molto più dinamica, e un po 'più SECCA, sebbene più complessa da leggere.

Prima si crea il tuo manager dinamico:

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

GlobalDeleteManager = create_manager(DeleteManager, globals=globals) 

Questo crea un nuovo manager che è una sottoclasse di DeleteManager e ha un metodo chiamato globals.

Avanti, si crea il tuo modello mixin:

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

Come ho detto, è brutto. Ma significa che non devi ridefinire globals(). Se si desidera un diverso tipo di gestore per avere globals(), è sufficiente chiamare create_manager di nuovo con una base diversa. E puoi aggiungere tutti i nuovi metodi che vuoi. Lo stesso per il manager, continuate ad aggiungere nuove funzioni che restituiranno diversi set di query.

Quindi, è davvero pratico? Forse no. Questa risposta è più un esercizio in (ab) che usa la flessibilità di Python. Non ho provato a utilizzare questo, anche se io uso alcuni dei principi sottostanti di estendere dinamicamente le classi per rendere le cose più facili da accedere.

Fatemi sapere se qualcosa non è chiaro e aggiornerò la risposta.

+0

Concatenare l'ereditarietà funzionerebbe sicuramente, ma purtroppo avrebbe vanificato lo scopo dei miei mix. Ci sono casi (non sono sicuro di quando), dove vorrei qualcosa semplicemente avere GlobalMixin o semplicemente avere DeleteMixin (o avere entrambi). Dovrò sicuramente esaminare il tuo altro suggerimento. Sono d'accordo che è un po 'brutto, ma forse può essere pulito per fornire la funzionalità simile in un pacchetto più pulito. – Adam

+0

Puoi farli con questo metodo semplicemente passando nei modelli.Modello come mixin e model base. Gestisci come base manager, ma vale la pena se hai molte permutazioni di mixin e manager. – tghw

8

Ecco la soluzione specifica al mio problema utilizzando il QuerySetManager personalizzato di Simon a cui è collegato Scott.

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 

Qualsiasi mixin nel futuro che vuole aggiungere ulteriori funzionalità alla query impostare semplicemente ha la necessità di estendere la BaseMixin (o di avere da qualche parte nella sua gerarchia). Ogni volta che provo a filtrare la query impostata, l'ho racchiusa in un try-catch nel caso in cui quel campo non esista realmente (cioè non estenda tale mixin). Il filtro globale viene richiamato utilizzando globals(), mentre il filtro di eliminazione viene automaticamente richiamato (se qualcosa viene eliminato, non lo voglio mai mostrare). L'utilizzo di questo sistema permette i seguenti tipi di comandi:

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 

Una cosa da notare è che il filtro di eliminazione non influenzerà le interfacce di amministrazione, in quanto il manager di default viene dichiarata prima (diventando così il default). Non ricordo quando hanno cambiato l'amministratore per utilizzare Model._default_manager anziché Model.oggetti, ma tutte le istanze cancellate verranno comunque visualizzate nell'amministratore (nel caso in cui sia necessario annullarle).

+1

Per fare in modo che l'amministratore usi il Manager che desideri, dai un'occhiata qui: http://stackoverflow.com/questions/1545067/django-specify-which-model-manager-django-admin-should-use –