7

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

简单地说,如果我创建一个Client(因此是一个) ,Person我是否也可以OtherPerson使用Person. Client这样我可以将它们视为ClientOR OtherPerson,并且保存一个会影响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>
4

4 回答 4

5

好的,我讨厌回答我自己的问题,特别是因为它有点重复(Django 模型继承:创建现有实例的子实例(向下转型)?

@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 版本。

于 2013-08-08T18:06:09.630 回答
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

于 2013-08-08T16:03:19.653 回答
1

子类化你不能真正做到这一点。当你创建 subclass 时Person,你隐含地告诉 Django 你将创建子类,而不是Person对象。这是一个 PITA 将其Person变形为OtherPerson以后。

你可能想要一个OneToOneFieldClient和都OtherPerson应该是 的子类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/

于 2013-08-08T16:07:13.870 回答
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()会导致异常,因为我在父类中使用FieldTrackerfrom 。model_utils

于 2019-03-25T16:16:14.147 回答