从 Django 中的基本模型实例返回代理模型实例的正确方法?

发布于 2024-08-21 03:42:38 字数 813 浏览 10 评论 0原文

假设我有模型:

class Animal(models.Model):
    type = models.CharField(max_length=255)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"
    class Meta:
        proxy = True

class Cat(Animal):
    def make_sound(self):
        print "Meow!"
    class Meta:
        proxy = True

假设我想做:

 animals = Animal.objects.all()
 for animal in animals:
     animal.make_sound()

我想要取回一系列的汪汪声和喵声。显然,我可以在基于 Animal_type 分叉的原始模型中定义 make_sound,但是每次我添加新的动物类型(想象它们位于不同的应用程序中)时,我都必须进入并编辑 make_sound 函数。我宁愿只定义代理模型并让它们自己定义行为。据我所知,无法返回混合的 Cat 或 Dog 实例,但我想也许我可以在返回猫或狗模型的主类上定义一个“get_proxy_model”方法。

当然,您可以这样做,并传递诸如主键之类的内容,然后只需执行 Cat.objects.get(pk = Passed_in_primary_key) 即可。但这意味着对已有的数据进行额外的查询,这似乎是多余的。有什么方法可以有效地将动物变成猫或狗实例吗?实现我想要实现的目标的正确方法是什么?

Say I have models:

class Animal(models.Model):
    type = models.CharField(max_length=255)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"
    class Meta:
        proxy = True

class Cat(Animal):
    def make_sound(self):
        print "Meow!"
    class Meta:
        proxy = True

Let's say I want to do:

 animals = Animal.objects.all()
 for animal in animals:
     animal.make_sound()

I want to get back a series of Woofs and Meows. Clearly, I could just define a make_sound in the original model that forks based on animal_type, but then every time I add a new animal type (imagine they're in different apps), I'd have to go in and edit that make_sound function. I'd rather just define proxy models and have them define the behavior themselves. From what I can tell, there's no way of returning mixed Cat or Dog instances, but I figured maybe I could define a "get_proxy_model" method on the main class that returns a cat or a dog model.

Surely you could do this, and pass something like the primary key and then just do Cat.objects.get(pk = passed_in_primary_key). But that'd mean doing an extra query for data you already have which seems redundant. Is there any way to turn an animal into a cat or a dog instance in an efficient way? What's the right way to do what I want to achieve?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

春庭雪 2024-08-28 03:42:47

我尝试了很多方法来做到这一点。最后,最简单的似乎就是前进的道路。
覆盖基类的__init__

class Animal(models.Model):
    type = models.CharField(max_length=255)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__class__ = eval(self.type)

我知道 eval 可能很危险,等等,但是您始终可以在类型选择上添加保护/验证,以确保它是您想要看到的。
除此之外,我想不出任何明显的陷阱,但如果我发现任何陷阱,我会提及它们/删除答案! (是的,我知道这个问题非常老,但希望这能帮助其他遇到同样问题的人)

I played around with a lot of ways to do this. In the end the most simple seems to be the way forward.
Override __init__ of the base class.

class Animal(models.Model):
    type = models.CharField(max_length=255)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__class__ = eval(self.type)

I know eval can be dangerous, bla bla bla, but you can always add safeguarding/validation on the type choice to ensure it's what you want to see.
Besdies that, I can't think of any obvious pitfalls but if i find any i'll mention them/ delete the answer! (yeah i know the question is super old, but hopefully this'll help others with the same problem)

番薯 2024-08-28 03:42:47

您或许可以使用此处描述的方法使 Django 模型具有多态性。我相信该代码处于开发的早期阶段,但值得研究。

You can perhaps make Django models polymorphic using the approach described here. That code is in early stages of development, I believe, but worth investigating.

薯片软お妹 2024-08-28 03:42:47

这个答案可能在某种程度上回避了这个问题,因为它不使用代理模型。然而,正如问题所提出的,它确实让人们编写以下内容(如果添加新类型,则无需更新 Animal 类)——

animals = Animal.objects.all()
for animal in animals:
    animal.make_sound()

为了避免元类编程,可以使用 组合优于继承。例如,

class Animal(models.Model):

    type = models.CharField(max_length=255)

    @property
    def type_instance(self):
        """Return a Dog or Cat object, etc."""
        return globals()[self.type]()

    def make_sound(self):
        return self.type_instance.make_sound()

class Dog(object):
    def make_sound(self):
        print "Woof!"

class Cat(object):
    def make_sound(self):
        print "Meow!"

如果 DogCat 类需要访问 Animal 实例,您还可以调整 type_instance()<上面的 /code> 方法将其所需的内容传递给类构造函数(例如 self)。

This answer may be side-stepping the question somewhat because it doesn't use proxy models. However, as the question asks, it does let one write the following (and without having to update the Animal class if new types are added)--

animals = Animal.objects.all()
for animal in animals:
    animal.make_sound()

To avoid metaclass programming, one could use composition over inheritance. For example--

class Animal(models.Model):

    type = models.CharField(max_length=255)

    @property
    def type_instance(self):
        """Return a Dog or Cat object, etc."""
        return globals()[self.type]()

    def make_sound(self):
        return self.type_instance.make_sound()

class Dog(object):
    def make_sound(self):
        print "Woof!"

class Cat(object):
    def make_sound(self):
        print "Meow!"

If the Dog and Cat classes need access to the Animal instance, you could also adjust the type_instance() method above to pass what it needs to the class constructor (e.g. self).

硬不硬你别怂 2024-08-28 03:42:46

thedk 提出的元类方法确实是一种非常强大的方法,但是,我必须将其与问题的答案结合起来 此处让查询返回代理模型实例。适用于前面示例的代码的简化版本是:

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)

    def get_object(self):
        if self.object_class in SUBCLASSES_OF_ANIMAL:
            self.__class__ = SUBCLASSES_OF_ANIMAL[self.object_class]
        return self

class Dog(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Meow!"


SUBCLASSES_OF_ANIMAL = dict([(cls.__name__, cls) for cls in ANIMAL.__subclasses__()])

这种代理方法的优点是创建新子类时不需要数据库迁移。缺点是子类中不能添加特定的字段。

我很高兴收到有关此方法的反馈。

The Metaclass approach proposed by thedk is indeed a very powerful way to go, however, I had to combine it with an answer to the question here to have the query return a proxy model instance. The simplified version of the code adapted to the previous example would be:

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)

    def get_object(self):
        if self.object_class in SUBCLASSES_OF_ANIMAL:
            self.__class__ = SUBCLASSES_OF_ANIMAL[self.object_class]
        return self

class Dog(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Meow!"


SUBCLASSES_OF_ANIMAL = dict([(cls.__name__, cls) for cls in ANIMAL.__subclasses__()])

The advantage of this proxy approach is that no db migration is required upon creation of new subclasses. The drawback is that no specific fields can be added to the subclasses.

I would be happy to have feedback on this approach.

很酷不放纵 2024-08-28 03:42:46

人类已知的唯一方法是使用元类编程。

这是简短的答案:

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)
    def get_object(self):
        if not self.object_class or self._meta.module_name == self.object_class:
            return self
        else:
            return getattr(self, self.object_class)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    def make_sound(self):
        print "Meow!"

以及期望的结果:

shell$ ./manage.py shell_plus
From 'models' autoload: Animal, Dog, Cat
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> dog1=Dog(type="Ozzie").save()
>>> cat1=Cat(type="Kitty").save()
>>> dog2=Dog(type="Dozzie").save()
>>> cat2=Cat(type="Kinnie").save()
>>> Animal.objects.all()
[<Dog: Dog object>, <Cat: Cat object>, <Dog: Dog object>, <Cat: Cat object>]
>>> for a in Animal.objects.all():
...    print a.type, a.make_sound()
... 
Ozzie Woof!
None
Kitty Meow!
None
Dozzie Woof!
None
Kinnie Meow!
None
>>> 

它是如何工作的?

  1. 存储有关班级的信息
    动物的名字 - 我们使用
    object_class
  2. 删除“代理”元属性 - 我们需要
    Django 中的反向关系(不好的
    我们创建额外的数据库
    每个儿童模型的表格和
    为此浪费额外的数据库命中,
    好的一面是我们可以添加一些孩子
    模型相关字段)
  3. 为 Animal 自定义 save() 来保存类
    对象的 object_class 中的名称
    调用保存。
  4. 需要方法get_object来引用
    通过Django中的反向关系
    到名称缓存在的模型
    对象_类。
  5. 自动执行此 .get_object() “转换”
    每次 Animal 被实例化时
    重新定义动物元类
    模型。元类就像是
    类的模板(就像
    类是对象的模板)。

有关 Python 中元类的更多信息:http://www.ibm.com/ developerworks/linux/library/l-pymeta.html

the only way known to the human kind is to use Metaclass programming.

Here is short answer:

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)
    def get_object(self):
        if not self.object_class or self._meta.module_name == self.object_class:
            return self
        else:
            return getattr(self, self.object_class)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    def make_sound(self):
        print "Meow!"

and the desired result:

shell$ ./manage.py shell_plus
From 'models' autoload: Animal, Dog, Cat
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> dog1=Dog(type="Ozzie").save()
>>> cat1=Cat(type="Kitty").save()
>>> dog2=Dog(type="Dozzie").save()
>>> cat2=Cat(type="Kinnie").save()
>>> Animal.objects.all()
[<Dog: Dog object>, <Cat: Cat object>, <Dog: Dog object>, <Cat: Cat object>]
>>> for a in Animal.objects.all():
...    print a.type, a.make_sound()
... 
Ozzie Woof!
None
Kitty Meow!
None
Dozzie Woof!
None
Kinnie Meow!
None
>>> 

How does it work?

  1. Store information about class
    name of the animal - we use
    object_class for that
  2. Remove "proxy" meta attribute - we need to
    reverse relation in Django (the bad
    side of this we create extra DB
    table for every child model and
    waste additional DB hit for that,
    the good side we can add some child
    model dependent fields)
  3. Customize save() for Animal to save the class
    name in object_class of the object
    that invoke save.
  4. Method get_object is needed for referencing
    through reverse relation in Django
    to the Model with name cached in
    object_class.
  5. Do this .get_object() "casting" automatically
    every time Animal is instantiate by
    redefining Metaclass of Animal
    model. Metaclass is something like a
    template for a class (just like a
    class is a template for an object).

More information about Metaclass in Python: http://www.ibm.com/developerworks/linux/library/l-pymeta.html

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文