Pergunta

Eu queria saber se era possível (e, em caso afirmativo, como) para encadear vários gerentes para produzir um conjunto de consulta que é afetado por ambos os gerentes individuais. Vou explicar o exemplo específico que eu estou trabalhando em:

Eu tenho várias classes de modelo abstrato que eu uso para fornecer funcionalidade pequena, específica para outros modelos. Dois desses modelos são uma DeleteMixin e uma GlobalMixin.

O DeleteMixin é definido 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()

Basicamente, ele fornece um (a bandeira excluído) pseudo-delete em vez de realmente excluir o objeto.

O GlobalMixin é definido como tal:

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

    objects = GlobalManager()

    class Meta:
        abstract = True

Ele permite que qualquer objeto a ser definida ou como um objeto global ou um objeto particular (como a / post público-privada).

Ambos têm seus próprios gerentes que afetam o queryset que é retornado. Meu DeleteManager filtra o queryset para apenas resultados de retorno que têm o conjunto de sinalizador apagado como False, enquanto o GlobalManager filtra o queryset para apenas resultados de retorno que são marcados como global. Aqui está a declaração 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)

A funcionalidade desejada seria ter um modelo estender ambos os modelos abstratos e conceder a capacidade para retornar apenas os resultados que são-não excluída e global. Corri um caso de teste em um modelo com 4 instâncias: uma era global e não-excluídos, uma era global e excluído, um era não global e não-excluído, e um deles era não global e excluídos. Se eu tentar obter conjuntos de resultados como tal: SomeModel.objects.all (), recebo exemplo 1 e 3 (os dois não excluídos - ótimo!). Se eu tentar SomeModel.objects.globals (), eu recebo um erro que DeleteManager não tem uma globals (isso está assumindo o meu modelo de declaração é como tal:. SomeModel (DeleteMixin, GlobalMixin) Se eu inverter a ordem, I don' t obter o erro, mas não filtra os excluídos). Se eu mudar GlobalMixin para anexar GlobalManager para globals em vez de objetos (por isso o novo comando seria SomeModel.globals.globals ()), eu obter instâncias 1 e 2 (os dois globals), enquanto o meu resultado pretendido seria conseguir única instância 1 (o global, não-eliminados um).

Eu não tinha certeza se alguém tinha executado em qualquer situação semelhante a este e tinha chegado a um resultado. Ou uma maneira de fazê-lo funcionar no meu pensamento atual ou um re-trabalho que fornece a funcionalidade que eu estou atrás seria muito apreciado. Eu sei que este post foi um pouco prolixo. Se mais nenhuma explicação é necessária, eu ficaria feliz em fornecê-lo.

Editar:

Eu afixei a eventual solução que eu usei para este problema específico abaixo. Ele é baseado no link para QuerySetManager costume de Simon.

Foi útil?

Solução

Veja esse trecho em Djangosnippets: http://djangosnippets.org/snippets/734/

Em vez de colocar seus métodos personalizados em um gerente, você subclasse a própria queryset. É muito fácil e funciona perfeitamente. O único problema que eu tive é com herança modelo, você sempre tem que definir o gerente em subclasses modelo (apenas: "objetos = QuerySetManager ()" na subclasse), mesmo que eles herdarão o queryset. Isto fará mais sentido uma vez que você estiver usando QuerySetManager.

Outras dicas

Aqui está a solução específica para o meu problema usando o costume QuerySetManager por Simon que Scott vinculado.

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

Qualquer mixin no futuro que quer para adicionar funcionalidade extra para o conjunto consulta simplesmente precisa estender BaseMixin (ou tê-lo em algum lugar na sua hierarquia). Qualquer tempo que tenta filtrar o conjunto para baixo de consulta, que envolveu em uma tentativa-captura no caso de que o campo na verdade não existe (isto é, que não se estende que mixin). O filtro global é invocado usando globals (), enquanto o filtro de exclusão é invocado automaticamente (se algo for excluído, eu nunca quero isso para mostrar). Usando este sistema permite que os seguintes 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

Uma coisa a notar é que o filtro de exclusão não afetará as interfaces de administração, porque o gerenciador padrão é declarado primeiro (tornando-se o padrão). Não me lembro quando eles mudaram o administrador usar Model._default_manager vez de Model.objects, mas todas as instâncias excluídos ainda aparecerá no admin (em caso de necessidade de un-excluí-los).

Eu passei um tempo tentando chegar a uma maneira de construir uma fábrica de bom para fazer isso, mas eu estou correndo em um monte de problemas com isso.

O melhor que posso sugerir-lhe é a cadeia de sua herança. Não é muito genérico, então eu não tenho certeza de como é útil, mas tudo que você tem a fazer é:

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 você quiser algo mais genérico, o melhor que eu posso vir acima com é definir um Mixin base e Manager que redefine get_query_set() (eu estou supondo que você só quer fazer isso uma vez, as coisas são muito complicadas de outra forma) e, em seguida, passar uma lista de campos que você gostaria adicionado via Mixins.

Seria algo parecido com isto (não testado em tudo):

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, então isso é feio, mas o que isso te? Essencialmente, é a mesma solução, mas muito mais dinâmico, e um pouco mais DRY, embora mais complexo para ler.

Primeiro você cria o seu gerente dinamicamente:

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

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

Isso cria um novo gerente que é uma subclasse de DeleteManager e tem um método chamado globals.

Em seguida, você cria seu modelo mixin:

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

Como eu disse, é feio. Mas isso significa que você não tem que redefinir globals(). Se você quer um tipo diferente de gestor ter globals(), você apenas chamar create_manager novamente com uma base diferente. E você pode adicionar tantos novos métodos como você gosta. Mesmo para o gerente, você apenas continuar adicionando novas funções que irá retornar diferentes querysets.

Então, isso é realmente prático? Talvez não. Esta resposta é mais um exercício de (ab) usando a flexibilidade do Python. Eu não tentei usar isso, embora eu use alguns dos princípios subjacentes de estender dinamicamente classes para tornar as coisas mais fáceis de acesso.

Deixe-me saber se alguma coisa não está claro e eu vou atualizar a resposta.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top