使用Django,我如何从超类对象实例构造代理对象实例?

8

我对Django中代理模型与其超类的关系仍然有些困惑。我的问题是如何从已检索到的超类实例中获取代理模型实例?

假设我有以下内容:

class Animal(models.Model):
   type = models.CharField(max_length=20)
   name = models.CharField(max_length=40)

class Dog(Animal):  
   class Meta:
       proxy = True

   def make_noise(self):  
       print "Woof Woof"  

Class Cat(Animal):  
   class Meta:
       proxy = True

   def make_noise(self):  
       print "Meow Meow"

animals = Animal.objects.all()
for animal in animals:
   if (animal.type == "cat"):
      animal_proxy = # make me a cat
   elif (animal.type == "dog"):
      animal_proxy = # make me a dog
   animal_proxy.make_noise()

好的。那么.. "# make me a cat" 中有哪些内容不需要向数据库发出查询,比如:

animal_proxy = Cat.objects.get(id=animal.id)

有没有一种简单的方法可以从我知道是猫的动物实例创建一个猫的实例?
2个回答

7
您正在尝试为继承层次结构实现持久性。使用一个具体的表和一个“类型”开关是一个不错的方法。然而,我认为您的实现,具体来说:
for animal in animals:
   if (animal.type == "cat"): 
      animal_proxy = # make me a cat

这种做法违背了Django的设计理念。打开类型应该不是代理(或模型)类的附加功能。

如果我是你,我会执行以下操作:

首先,为代理模型添加“类型感知”管理器。这将确保Dog.objects始终获取带有type="dog"Animal实例,而Cat.objects将获取带有type="cat"Animal实例。

class TypeAwareManager(models.Manager):
    def __init__(self, type, *args, **kwargs):
        super(TypeAwareManager, self).__init__(*args, **kwargs)
        self.type = type

    def get_query_set(self):
        return super(TypeAwareManager, self).get_query_set().filter(
              type = self.type)

class Dog(Animal):
    objects = TypeAwareManager('dog')
    ...

class Cat(Animal):
    objects = TypeAwareManager('cat')
    ...

其次,分别获取子类实例。然后在操作之前将它们组合起来。我使用了 itertools.chain 来组合两个 Querysets

from itertools import chain
q1 = Cat.objects.all() # [<Cat: Daisy [cat]>]

q2 = Dog.objects.all() # [<Dog: Bruno [dog]>]

for each in chain(q1, q2): 
    each.make_noise() 

# Meow Meow
# Woof Woof

我知道我在违背Django的惯例。我这样做是因为Django不允许我做我想做的事情,即获取存储在同一表中但具有不同属性的对象列表,而无需实际链接结果。我构建了一个类型感知管理器,但是在超类级别上,现在我只需要将返回的超类对象实例“转换”为代理类对象。有没有办法做到这一点? - Bubba Raskin
实际上,我已经完成了这个,但是我目前正在按照以下方式回调数据库:animal_proxy = Cat.objects.get(id=animal.id)。我想要像这样的东西 animal_proxy = (Cat) animal。我知道有Python技巧可以为我完成这个。 - Bubba Raskin
@Bubba:看一下这个问题。回答可能对你有用。https://dev59.com/X3E95IYBdhLWcg3wp_kg - Manoj Govindan
我看到了那个问题。我以那个问题为基础来举例。 - Bubba Raskin
1
我已经通过在Animal类上黑客查询集来获得混合的Cat+Dog结果。我只想避免在从查询集返回的Animal实例实例化Cat+Dog时调用数据库。 - Bubba Raskin

3
我会这样做:
def reklass_model(model_instance, model_subklass):

    fields = model_instance._meta.get_all_field_names()
    kwargs = {}
    for field_name in fields:
        try:
           kwargs[field_name] = getattr(model_instance, field_name)
        except ValueError as e: 
           #needed for ManyToManyField for not already saved instances
           pass

    return model_subklass(**kwargs)

animals = Animal.objects.all()
for animal in animals:
   if (animal.type == "cat"):
      animal_proxy = reklass_model(animal, Cat)
   elif (animal.type == "dog"):
      animal_proxy = reklass_model(animal, Cat)
   animal_proxy.make_noise()

# Meow Meow
# Woof Woof

我没有用 "动物园" 进行测试 ;) 但是与我的模型一起似乎可以使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接