Django model inheritance - creating child objects based on parent instance

Viewed 205

I am using Django 4.0

I have the following models:

class Parent(models.Model):
    # fields ommitted for the sake of brevity
    pass

class Child(Parent):
   child_only_field = models.CharField(max_length=64)

code

p = Parent.objects.create(**fields_dict)
c1 = Child.objects.create(child_only_field='Hello 123', p) # Obviously won't work
# c2 = ...

Is there a way to create child objects from an instance of a parent (without manually "unpacking" the fields of the parent)?

4 Answers

it is easy, if you understand how Django works with implizit o2o relation.

class Parent(Model):
    # fields

# implizit o2o
class Child(Parent):
   child_only_field = models.CharField(max_length=64)

# explizit o2o
class Child_1(Parent):
  parent = models.OneToOneField(Parent)
  child_only_field = models.CharField(max_length=64)

How to save or create child with Implizit o2o relation.

1.Save child object by existed Parent class object DON'T USE IT IN NORMAL CODE.

parent = Parent.objects.get(pk=some_pk)
child = Child(child_only_field='Hello 123', pk=parent.pk)
child.save_base(raw=True) # important part of code

Model.save_base - internal method to save information only for child fields. BUT parent data should already exists.

2.Save created Parent as child. DON'T USE IT IN NORMAL CODE:

parent = Parent.objects.get(pk=some_pk)
parent.child_only_field = 'Hello 123'
parent.save_base(class=Child)

Model.save_base method saves information for whole chain of inheritance.

3.Save created child normally. PLEASE USE IT IN NORMAL CODE:

child = Child(child_only_field='Hello 123', **fields_dict)
child.save()

I especially don't use DataManager. I can say, this is more as internal method, than public interface.

  1. Save child through DataManager.

    child = Child.objects.create(child_only_field='Hello 123', **fields_dict)

    objects.create made point 3. from my answer. You can see it in django.db.models.query.py

I hope it helps.

Convert fields of p to dict by model_to_dict and p._meta.fields.

from django.forms.models import model_to_dict

class Parent(models.Model):
    # fields ommitted for the sake of brevity

    def fields_dict(self):
        return model_to_dict(
            p,
            fields=[
                field.name
                for field in self._meta.fields
                if field.name in self.__dict__
                and field.name != self._meta.pk.name
            ]
        )

class Child(Parent):
   child_only_field = models.CharField(max_length=64)

p = Parent.objects.create(**fields_dict)

c1 = Child.objects.create(child_only_field='Hello 123', **p.fields_dict())

It is also possible to overwrite create method.

from django.forms.models import model_to_dict

class MyManager(models.Manager):
    def create(self, *args, **kwargs):
        if 'parent_obj' in kwargs:
            parent_fields = parent_obj.fields_dict()
        return super().create(*args, **kwargs, **parent_fields)

class Parent(models.Model):
    # fields ommitted for the sake of brevity

    def fields_dict(self):
        return model_to_dict(
            p,
            fields=[
                field.name
                for field in self._meta.fields
                if field.name in self.__dict__
                and field.name != self._meta.pk.name
            ]
        )

class Child(Parent):
    objects = MyManager()

    child_only_field = models.CharField(max_length=64)
    

p = Parent.objects.create(**fields_dict)

c1 = Child.objects.create(child_only_field='Hello 123', parent_obj=p)

The following 1-liner will produce a new field dictionary containing only shared fields, looping through Django's _meta.fields (does not include M2M fields), which you can then pass to the child instance.

p = Parent.objects.create(**fields_dict)

new_fields_dict = {f.name: getattr(p, f.name) for f in p._meta.fields
                   if f in Child._meta.fields and f != p._meta.pk}

c = Child.objects.create(child_only_field='Hello 123', **new_fields_dict)

You can do it by assigning parent instance to child's parent_ptr attribute like:

p = Parent.objects.create(**fields_dict)
c1 = Child.objects.create(child_only_field='Hello 123', parent_ptr=p)

But apparently you have to populate all the parent-specific fields as well, because Django will not propagate those values (which is an awkward behavior); And if there is a nullable field in parent model which is initialized in fields_dict, it will be reset after creating c1 (also awkward). So maybe if you seek a way to only use a parent instance and nothing more, maybe there is no straightforward solution.

More details

By inheriting models by defining a model as a superclass, the tables will be created as followed:

ER sample for models

And the parent instance is accessible via parent_ptr attribute of the child instance, so you can assign it when creating the child as well like the above code, and doing c1.parent_field = "Hello" will also change its value in Parent table.

Related