Django:两个不同的子类指向同一个父类

时间:2013-08-08 15:31:48

标签: django orm

我有一个模型Person,它存储有关人员的所有数据。我还有一个Client模型,它扩展了Person。我有另一个扩展模型OtherPerson,它还扩展了Person模型。我想创建一个指向Person的客户端,并且还创建一个指向OtherPerson的{​​{1}}记录。基本上,我希望将一个Person对象视为PersonClient,具体取决于当前视图。这是否可以使用Django的ORM,或者我是否需要以某种方式编写Raw查询来创建此场景。我很确定它可以从数据库方面进行,因为两个子类只会指向具有person_ptr_id字段的父Person类。

简单地说,如果我创建OtherPerson(以及Client),我还可以使用{{1}中的基础Person创建OtherPerson对象吗? }}。这样我可以将其视为PersonClient,保存一个会影响每个Client字段吗?

“水平”多态?

以下是我的模型的简化版本,以便澄清:

OtherPerson

4 个答案:

答案 0 :(得分:3)

好吧,我讨厌回答我自己的问题,特别是因为它有点重复(Django model inheritance: create sub-instance of existing instance (downcast)?

@Daniel Roseman再次让我脱离果酱。得爱那个人!

person = Person.objects.get(id=<my_person_id>)
client = Client(person_ptr_id=person.id)
client.__dict__.update(person.__dict__)
client.save()
other_person = OtherPerson(person_ptr_id=person.id)
other_person.__dict__.update(person.__dict__)
other_person.save()

如果我有一个Client,并希望从他们那里制作一个OtherPerson,这是我的确切用例,我就是这样做的:

client_id = <ID of Client/Person I want to create an OtherPerson with>
p = Person.objects.get(id=client_id)
o = OtherPerson(person_ptr_id=p.id) # Note Person.id and Client.id are the same.
o.__dict__.update(p.__dict__)
o.save()

现在,此人在客户端屏幕上显示为客户端,在另一个人屏幕上显示为OtherPerson。我可以获得具有所有OtherPerson详细信息和功能的Person的OtherPerson版本,或者我可以获得具有所有客户端详细信息和功能的该Person的客户端版本。

答案 1 :(得分:2)

你正在做的事情是不可能的,Django有特定的继承规则

唯一可能的架构是:

class Parent(models.Model):
    class Meta:
        abstract = True # MUST BE !!! This results in no relation generated in your DB

    field0 = models.CharField(...
    ...

    # here you're allowed to put some functions and some fields here


class Child(models.Model):
    field1 = models.CharField(...
    ...

    # Anything you want, this model will create a relation in your database with field0, field1, ...


class GrandChild(models.Model):
    class Meta:
        proxy = True # MUST BE !!! This results in no relation generated in your DB

    # here you're not allowed to put DB fields, but you can override __init__ to change attributes of the fields: choices, default,... You also can add model methods.

这是因为大多数DBGS都没有DB继承。因此,您需要使您成为父类abstract

答案 2 :(得分:1)

你不能用子类化来做到这一点。当你继承Person时,你隐含地告诉Django你将创建子类,而不是Person个对象。这是一个PITA,可以将Person转换为OtherPerson以后的OneToOneField

您可能需要ClientOtherPersonmodels.Model都应该是class Client(models.Model): person = models.OneToOneField(Person, related_name="client") # ... class OtherPerson(models.Model): person = models.OneToOneField(Person, related_name="other_person") # ... 的子类:

pers = Person(...)
pers.save()
client = Client(person=pers, ...)
client.save()
other = OtherPerson(person=pers, ...)
other.save()

pers.other.termination_date = datetime.now()
pers.other.save()

然后你可以做以下事情:

{{1}}

有关详情,请参阅https://docs.djangoproject.com/en/dev/topics/db/examples/one_to_one/

答案 3 :(得分:1)

正如已经在评论中提到的,有一个针对这个问题的公开票: https://code.djangoproject.com/ticket/7623

与此同时,有一个建议的补丁(https://github.com/django/django/compare/master...ar45:child_object_from_parent_model)没有使用obj.__dict__ 但会创建一个字典,其中所有字段值都在所有字段上循环。 这是一个简化的功能:

def create_child_from_parent_model(parent_obj, child_cls, init_values: dict):
    attrs = {}
    for field in parent_obj._meta._get_fields(reverse=False, include_parents=True):
        if field.attname not in attrs:
            attrs[field.attname] = getattr(parent_obj, field.attname)
    attrs[child_cls._meta.parents[parent_obj.__class__].name] = parent_obj
    attrs.update(init_values)
    print(attrs)
    return child_cls(**attrs)

person = Person.objects.get(id=<my_person_id>)
client = create_child_from_parent_model(person, Client, {})
client.save()

如果要创建同级:

client_person = getattr(person, person._meta.parents.get(Person).name)
other_person = create_child_from_parent_model(person, OhterPerson, {})
other_person.save()

此方法的优点在于,被子级覆盖的方法不会被原始的父级方法替换。 对我来说,使用原始答案obj.__dict__.update()会导致异常,就像我在父类中使用FieldTracker中的model_utils一样。