【问题标题】:Django: Two different child classes point to same parent classDjango:两个不同的子类指向同一个父类
【发布时间】:2013-08-08 15:31:47
【问题描述】:

我有一个模型Person,它存储有关人员的所有数据。我还有一个扩展 Person 的 Client 模型。我有另一个扩展模型OtherPerson,它也扩展了Person 模型。我想创建一个指向Person 的客户端,并且还创建一个指向PersonOtherPerson 记录。基本上,我希望将一个Person 对象视为Client 和一个OtherPerson,具体取决于当前视图。这可能与 Django 的 ORM,还是我需要以某种方式编写一个原始查询来创建这个场景。我很确定这在数据库方面是可能的,因为两个子类都只会指向带有 person_ptr_id 字段的父 Person 类。

简单地说,如果我创建一个Client(因此是一个Person),我还可以使用来自Client 的基础Person 创建一个OtherPerson 对象。这样我可以将它们视为ClientOtherPerson,保存一个会影响每个Person 字段?

“水平”多态性?

这是我的模型的简化版本,以防万一:

class Person(models.Model):
    """
        Any person in the system will have a standard set of details, fingerprint hit details, some clearances and items due, like TB Test.
    """
    first_name = models.CharField(db_index=True, max_length=64, null=True, blank=True, help_text="First Name.")
    middle_name = models.CharField(db_index=True, max_length=32, null=True, blank=True, help_text="Middle Name.")
    last_name = models.CharField(db_index=True, max_length=64, null=True, blank=True, help_text="Last Name.")
    alias = models.CharField(db_index=True, max_length=128, null=True, blank=True, help_text="Aliases.")
    .
    .
    <some person methods like getPrintName, getAge, etc.>

class Client(Person):
    date_of_first_contact = models.DateField(null=True, blank=True)
    .
    .
    <some client methods>


class OtherPerson(Person):
    active_date = models.DateField(null=True, blank=True)
    termination_date = models.DateField(null=True, blank=True)
    .
    .
    <some other person methods>

【问题讨论】:

    标签: django orm


    【解决方案1】:

    好的,我不想回答我自己的问题,尤其是因为它有点重复 (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 版本,或者我可以获得具有所有 Client 详细信息和功能的该 Person 的 Client 版本。

    【讨论】:

    • 这是我提到的 PITA。下次不要在 OneToOneField 做得更好的地方使用子类化。
    • 为什么不能在这里使用继承?一个客户是一个人,一个其他人是一个人。在我看来,这似乎是 Django 中缺少的功能。
    【解决方案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

    【讨论】:

    • 请注意,您可以有许多抽象类(但不会只继承从左到右找到的第一个方法)、许多子类(因此您的数据库中有许多表)和许多代理(因此有很多管理员,...)
    • 感谢 Ricola3D 的回答。不幸的是,父表Person 已经存在,所以我无法更改为抽象表。你知道我可以采用一个存在的Person 对象并将它们用作新子Client 对象的超级对象吗?
    • 您的客户端模型是否需要在数据库中存储附加字段?
    【解决方案3】:

    子类化无法真正做到这一点。当您将Person 子类化时,您隐含地告诉Django 您将创建子类,而不是Person 对象。将Person 转化为OtherPerson 后,这是一个PITA。

    您可能想要OneToOneFieldClientOtherPerson 都应该是 models.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()
    

    请参阅https://docs.djangoproject.com/en/dev/topics/db/examples/one_to_one/ 了解更多信息。

    【讨论】:

    • 我非常喜欢将 O2O 外推到子类的想法,不幸的是,我已经创建了数百个 Person 对象。我找到了一个建议的补丁,用于使用构造函数中定义的父类创建子对象 (code.djangoproject.com/ticket/7623)。这正是我想要做的,但它已经 5 岁了,所以我不确定它是否会起作用。感谢您的回答,它将非常适合未来的实施并且很高兴知道;这种范式绝对优于我所拥有的。希望我能找到一种方法来解决我之前创建的场景。
    • 好吧,如果你卡住了,你就卡住了。仅供参考,我之前已经将东西从子类转换为非子类,南迁移很难做到。
    【解决方案4】:

    正如评论中已经提到的,这个问题有一张公开的票: 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() 会导致异常,因为我在父类中使用来自 model_utilsFieldTracker

    【讨论】:

      猜你喜欢
      • 2018-05-21
      • 2021-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-10
      • 1970-01-01
      • 2015-05-10
      • 1970-01-01
      相关资源
      最近更新 更多