Django Gerente de encadeamento

votos
29

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 abstratas que eu uso para fornecer funcionalidade pequena, específica para outros modelos. Dois desses modelos são uma DeleteMixin e uma GlobalMixin.

O DeleteMixin é definida 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 é definida 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 um post público / privado).

Ambos têm seus próprios gerentes que afetam o queryset que é retornado. Meu DeleteManager filtra o queryset a resultados que têm a bandeira excluídos definida como falsa retornar apenas, enquanto o GlobalManager filtra o queryset a resultados que são marcados como mundial retornar apenas. 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ídos 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).

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ê-la.

Editar:

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

Publicado 30/04/2009 em 22:33
fonte usuário
Em outras línguas...                            


4 respostas

votos
21

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.

Respondeu 01/05/2009 em 22:04
fonte usuário

votos
8

Aqui está a solução específica para o meu problema usando o QuerySetManager personalizado 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 a consulta definida simplesmente precisa estender BaseMixin (ou tê-lo em algum lugar na sua hierarquia). Qualquer vez que tentar filtrar a consulta definido abaixo, 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).

Respondeu 02/05/2009 em 07:00
fonte usuário

votos
2

Outra opção a considerar é a PassThroughManager:

https://django-model-utils.readthedocs.org/en/latest/managers.html#passthroughmanager

Respondeu 01/10/2012 em 23:41
fonte usuário

votos
2

Eu passei um tempo tentando chegar a uma maneira de construir uma boa fábrica de 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 uma base Mixine Managerque redefine get_query_set()(estou assumindo 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ê' d deseja adicionar 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 DeleteManagere 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_managernovamente 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, este é realmente prático? Talvez não. Esta resposta é mais um exercício de (ab) usando a flexibilidade do Python. Eu não tentei usar este, embora eu use alguns dos princípios subjacentes de aulas que se estendem de forma dinâmica para tornar as coisas mais fáceis de acessar.

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

Respondeu 01/05/2009 em 23:26
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more