Domanda

Mi chiedevo se fosse possibile (e, in tal caso, come) mettere in catena più gestori per produrre un set di query che è influenzato da entrambi i singoli gestori. Spiegherò l'esempio specifico su cui sto lavorando:

Ho più classi di modelli astratti che utilizzo per fornire funzionalità piccole e specifiche ad altri modelli. Due di questi modelli sono DeleteMixin e GlobalMixin.

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 fornisce uno pseudo-cancellazione (il flag eliminato) invece di eliminare effettivamente l'oggetto.

GlobalMixin è definito come tale:

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

    objects = GlobalManager()

    class Meta:
        abstract = True

Permette a qualsiasi oggetto di essere definito come un oggetto globale o un oggetto privato (come un post di blog pubblico / privato).

Entrambi hanno i loro gestori che influenzano il queryset che viene restituito. Il mio DeleteManager filtra il set di query in modo da restituire solo i risultati con il flag eliminato impostato su False, mentre GlobalManager filtra il set di query in modo da 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 che estenda entrambi questi modelli astratti e garantisca la possibilità di restituire solo risultati non cancellati e globali. Ho eseguito un caso di prova su un modello con 4 istanze: una era globale e non eliminata, una era globale ed eliminata, una non globale e non eliminata e una non globale ed eliminata. Se provo a ottenere set di risultati come tali: SomeModel.objects.all (), ottengo l'istanza 1 e 3 (i due non eliminati - fantastico!). Se provo SomeModel.objects.globals (), visualizzo un errore nel fatto che DeleteManager non dispone di un globale (questo presuppone che la mia dichiarazione del modello sia tale: SomeModel (DeleteMixin, GlobalMixin). Se invertisco l'ordine, non posso " t ottiene l'errore, ma non filtra quelli eliminati). Se cambio GlobalMixin per collegare GlobalManager ai globali anziché agli oggetti (quindi il nuovo comando sarebbe SomeModel.globals.globals ()), ottengo le istanze 1 e 2 (i due globi), mentre il risultato previsto sarebbe solo quello di ottenere istanza 1 (quello globale, non cancellato).

Non ero sicuro se qualcuno si fosse imbattuto in una situazione simile a questa e fosse arrivato a un risultato. O un modo per farlo funzionare nel mio pensiero attuale 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.

Modifica

Ho pubblicato l'eventuale soluzione che ho usato per questo specifico problema di seguito. Si basa sul collegamento al QuerySetManager personalizzato di Simon.

È stato utile?

Soluzione

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

Invece di inserire i tuoi metodi personalizzati in un gestore, esegui la sottoclasse del set di query stesso. È molto semplice e funziona perfettamente. L'unico problema che ho avuto è con l'ereditarietà del modello, devi sempre definire il gestore nelle sottoclassi del modello (solo: " objects = QuerySetManager () " nella sottoclasse), anche se erediteranno il queryset. Ciò avrà più senso una volta che si utilizza QuerySetManager.

Altri suggerimenti

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

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 in futuro che desidera aggiungere funzionalità extra al set di query deve semplicemente estendere BaseMixin (o averlo 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 effettivamente (cioè, non estende tale mixin). Il filtro globale viene invocato usando globals (), mentre il filtro delete viene invocato automaticamente (se qualcosa viene eliminato, non voglio mai mostrarlo). L'uso di questo sistema consente 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 influirà sulle interfacce di amministrazione, perché il Manager predefinito viene dichiarato per primo (rendendolo predefinito). Non ricordo quando hanno cambiato l'amministratore per usare Model._default_manager invece di Model.objects, ma tutte le istanze eliminate appariranno comunque nell'amministratore (nel caso in cui sia necessario cancellarle).

Ho passato un po 'di tempo a cercare un modo per costruire una bella fabbrica per farlo, ma sto incontrando molti problemi con questo.

Il meglio che posso suggerirti è di concatenare la tua eredità. Non è molto generico, quindi non sono sicuro di quanto sia utile, ma tutto ciò che dovresti 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 vuoi qualcosa di più generico, il meglio che posso trovare è definire un Mixin e Manager che ridefinisca get_query_set () (Presumo che tu voglia farlo solo una volta; altrimenti le cose si complicano abbastanza) e poi passa un elenco di campi che vorresti aggiungere tramite Mixin s.

Sarebbe simile a questo (non testato affatto):

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 è brutto, ma cosa ti prende? In sostanza, è la stessa soluzione, ma molto più dinamica e un po 'più ASCIUTTA, sebbene più complessa da leggere.

Per prima cosa crei il tuo manager in modo dinamico:

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

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

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

Successivamente, crei il tuo modello di 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 vuoi che un diverso tipo di gestore abbia globals () , devi semplicemente chiamare create_manager con una base diversa. E puoi aggiungere tutti i nuovi metodi che desideri. Lo stesso per il gestore, continui ad aggiungere nuove funzioni che restituiranno query diverse.

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

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

Un'altra opzione che vale la pena considerare è PassThroughManager:

https: //django-model-utils.readthedocs. org / it / ultima / managers.html # passthroughmanager

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top