Pregunta

Me preguntaba si era posible (y, de ser así, cómo) encadenar a varios gerentes para producir un conjunto de consultas afectado por ambos gerentes individuales. Explicaré el ejemplo específico en el que estoy trabajando:

Tengo varias clases de modelos abstractos que utilizo para proporcionar una funcionalidad pequeña y específica a otros modelos. Dos de estos modelos son DeleteMixin y GlobalMixin.

El DeleteMixin se define como tal:

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

    class Meta:
        abstract = True

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

Básicamente, proporciona una pseudo-eliminación (el indicador eliminado) en lugar de eliminar realmente el objeto.

El GlobalMixin se define como tal:

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

    objects = GlobalManager()

    class Meta:
        abstract = True

Permite que cualquier objeto se defina como un objeto global o un objeto privado (como una publicación de blog pública / privada).

Ambos tienen sus propios administradores que afectan el queryset que se devuelve. Mi DeleteManager filtra el conjunto de consultas para que solo devuelva resultados que tienen el indicador eliminado establecido en Falso, mientras que GlobalManager filtra el conjunto de consultas para que solo devuelva resultados que están marcados como globales. Aquí está la declaración para ambos:

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 funcionalidad deseada sería hacer que un modelo extienda ambos modelos abstractos y otorgue la capacidad de devolver solo los resultados que no son eliminados y globales. Ejecuté un caso de prueba en un modelo con 4 instancias: una fue global y no eliminada, una fue global y eliminada, otra no global y no eliminada, y una no global y eliminada. Si intento obtener conjuntos de resultados como tales: SomeModel.objects.all (), obtengo las instancias 1 y 3 (las dos no eliminadas, ¡genial!). Si intento SomeModel.objects.globals (), recibo un error de que DeleteManager no tiene globals (esto supone que mi declaración de modelo es la siguiente: SomeModel (DeleteMixin, GlobalMixin). Si revierto el pedido, no lo hago ' No aparece el error, pero no filtra los eliminados). Si cambio GlobalMixin para adjuntar GlobalManager a globales en lugar de objetos (por lo que el nuevo comando sería SomeModel.globals.globals ()), obtengo las instancias 1 y 2 (las dos globales), mientras que mi resultado previsto sería obtener solo una instancia 1 (el global, no eliminado).

No estaba seguro de si alguien se había encontrado con una situación similar a esta y había llegado a un resultado. Una forma de hacer que funcione en mi pensamiento actual o una reelaboración que brinde la funcionalidad que busco sería muy apreciada. Sé que esta publicación ha sido un poco larga. Si se necesita más explicación, me complacería brindarla.

Editar:

He publicado la solución final que usé para este problema específico a continuación. Se basa en el enlace al QuerySetManager personalizado de Simon.

¿Fue útil?

Solución

Vea este fragmento en Djangosnippets: http://djangosnippets.org/snippets/734/

En lugar de poner sus métodos personalizados en un administrador, subclase el queryset en sí. Es muy fácil y funciona perfectamente. El único problema que he tenido es con la herencia del modelo, siempre debe definir el administrador en las subclases del modelo (solo: " objects = QuerySetManager () " en la subclase), a pesar de que heredarán el conjunto de consultas. Esto tendrá más sentido una vez que esté utilizando QuerySetManager.

Otros consejos

Aquí está la solución específica a mi problema usando el QuerySetManager personalizado de Simon al que Scott se vinculó.

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

Cualquier mixin en el futuro que quiera agregar funcionalidad adicional al conjunto de consultas simplemente necesita extender BaseMixin (o tenerlo en algún lugar de su jerarquía). Cada vez que trato de filtrar la consulta establecida, la envuelvo en un try-catch en caso de que ese campo no exista (es decir, no extiende ese mixin). El filtro global se invoca usando globals (), mientras que el filtro de eliminación se invoca automáticamente (si algo se elimina, nunca quiero que se muestre). El uso de este sistema permite los siguientes tipos de comandos:

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 a tener en cuenta es que el filtro de eliminación no afectará a las interfaces de administración, porque el Administrador predeterminado se declara primero (lo que lo convierte en el predeterminado). No recuerdo cuándo cambiaron el administrador para usar Model._default_manager en lugar de Model.objects, pero las instancias eliminadas seguirán apareciendo en el administrador (en caso de que necesite eliminarlas).

Pasé un tiempo tratando de encontrar una manera de construir una buena fábrica para hacer esto, pero me encuentro con muchos problemas con eso.

Lo mejor que puedo sugerirte es encadenar tu herencia. No es muy genérico, así que no estoy seguro de lo útil que es, pero todo lo que tendría que hacer es:

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)

Si desea algo más genérico, lo mejor que se me ocurre es definir un Mixin y un Manager base que redefinan get_query_set () (Asumo que solo quieres hacer esto una vez; de lo contrario, las cosas se complican bastante) y luego pasa una lista de campos que te gustaría agregar a través de Mixin s.

Se vería así (no probado en absoluto):

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, entonces esto es feo, pero ¿qué te pasa? Esencialmente, es la misma solución, pero mucho más dinámica y un poco más SECA, aunque más compleja de leer.

Primero creas tu administrador dinámicamente:

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

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

Esto crea un nuevo administrador que es una subclase de DeleteManager y tiene un método llamado globals .

A continuación, creas tu modelo mixin:

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

Como he dicho, es feo. Pero significa que no tiene que redefinir globals () . Si desea que un tipo diferente de administrador tenga globals () , simplemente llame a create_manager nuevamente con una base diferente. Y puedes agregar tantos nuevos métodos como quieras. Lo mismo para el administrador, solo sigue agregando nuevas funciones que devolverán diferentes conjuntos de consultas.

Entonces, ¿esto es realmente práctico? Tal vez no. Esta respuesta es más un ejercicio en (ab) usando la flexibilidad de Python. No he intentado usar esto, aunque sí uso algunos de los principios subyacentes de las clases que se extienden dinámicamente para facilitar el acceso a las cosas.

Avíseme si algo no está claro y actualizaré la respuesta.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top