Django: Two different child classes point to same parent class

前端 未结 4 1139
名媛妹妹
名媛妹妹 2021-02-16 00:15

I have a model Person which stores all data about people. I also have a Client model which extends Person. I have another extending model OtherPe

相关标签:
4条回答
  • 2021-02-16 00:31

    What you are doing is not possible as you do it, Django has specific rules for inheritance

    The only possible schema is:

    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.
    

    This is because there is no DB inheritance in most DBGS. Thus you need to make you parent class abstract !

    0 讨论(0)
  • 2021-02-16 00:37

    You can't really do that with subclassing. When you subclass Person, you're implicitly telling Django that you'll be creating subclasses, not Person objects. It's a PITA to take a Person and transmogrify it into a OtherPerson later.

    You probably want a OneToOneField instead. Both Client and OtherPerson should be subclasses of 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")
        # ...
    

    Then you can do things like:

    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()
    

    See https://docs.djangoproject.com/en/dev/topics/db/examples/one_to_one/ for more.

    0 讨论(0)
  • 2021-02-16 00:46

    Okay, I hate to answer my own question, especially since it is sort of a repeat of (Django model inheritance: create sub-instance of existing instance (downcast)?

    @Daniel Roseman got me out of a jam AGAIN. Gotta love that guy!

    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()
    

    If I have an existing Client and want to make an OtherPerson from them, which is my exact use-case, I just do this:

    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()
    

    Now the person shows up as a Client on the clients screen and as an OtherPerson on the other person screen. I can get the OtherPerson version of the Person which has all the OtherPerson details and functions or I can get a Client version of that Person which has all the Client details and functions.

    0 讨论(0)
  • 2021-02-16 00:52

    As mentioned in a comment already, there is an open ticket for this very question: https://code.djangoproject.com/ticket/7623

    In the meanwhile there is a proposed patch (https://github.com/django/django/compare/master...ar45:child_object_from_parent_model) which not using obj.__dict__ but creates an dictionary with all field values cycling over all fields. Here a simplified function:

    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()
    

    If you want to create a sibling:

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

    This method has the advantage that methods that are overwritten by the child are not replaced by the original parent methods. For me using the original answers obj.__dict__.update() led to exceptions as I was using the FieldTracker from model_utils in the parent class.

    0 讨论(0)
提交回复
热议问题