Наследование модели Django - создание дочерних объектов на основе родительского экземпляра

Я использую Django 4.0

У меня есть следующие модели:

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

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) # Obviously won't work
# c2 = ...

Существует ли способ создания дочерних объектов из экземпляра родительского (без ручной "распаковки" полей родительского)?

Преобразование полей p в dict с помощью model_to_dict и 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())

это легко, если вы понимаете, как работает Django с 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)

Как сохранить или создать ребенка с помощью 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 - внутренний метод для сохранения информации только для дочерних полей. Но родительские данные уже должны существовать.

2.Сохраните созданный Parent как child. НЕ ИСПОЛЬЗУЙТЕ ЕГО В ОБЫЧНОМ КОДЕ:

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

Model.save_base метод сохраняет информацию для всей цепочки наследования.

3.Сохраните созданного ребенка в нормальном виде. ПОЖАЛУЙСТА, ИСПОЛЬЗУЙТЕ ЕГО В ОБЫЧНОМ КОДЕ:

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

Я особенно не использую DataManager. Я могу сказать, что это скорее внутренний метод, чем публичный интерфейс.

  1. Сохраните ребенка через DataManager.

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

    objects.create сделал пункт 3. из моего ответа. Вы можете увидеть его в django.db.models.query.py

    .

Надеюсь, это поможет.

Следующая 1-строка создаст новый словарь полей, содержащий только общие поля, в цикле Django _meta.fields (не включает поля M2M), который затем можно передать дочернему экземпляру.

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)

Вы можете сделать это, присвоив родительский экземпляр дочернему атрибуту parent_ptr следующим образом:

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

Но, очевидно, вам придется заполнить все специфические для родителя поля, потому что Django не будет распространять эти значения (что является неудобным поведением); И если в родительской модели есть nullable поле, которое инициализируется в fields_dict, оно будет сброшено после создания c1 (также неудобно). Поэтому, если вы ищете способ использовать только родительский экземпляр и ничего больше, возможно, прямого решения нет.

Подробнее

При наследовании моделей путем определения модели как суперкласса, таблицы будут созданы следующим образом:

ER sample for models

А родительский экземпляр доступен через атрибут parent_ptr дочернего экземпляра, поэтому вы можете присвоить его при создании дочернего экземпляра, как в приведенном выше коде, а выполнение c1.parent_field = "Hello" также изменит его значение в таблице Parent.

Вернуться на верх