Wielokrotne dziedziczenie modeli w Django

Wielokrotne dziedziczenie modeli w Django

Kolejny problem z którym się zetknąłem i postanowiłem się podzielić rozwiązaniem z resztą świata. Rozwiązanie przyszło z trudem mimo przekopania stackoverflow i dokumentacji. Rozwiązanie to oczywiście bazuje na wiedzy z dokumentacji i stackoverflow ale musiało zostać dostosowane do mojego specyficznego problemu, a mianowicie kopiowanie obiektu modelu w django w przypadku wielokrotnego dziedziczenia.

Jak głosi dokumentacja django (doc) wystarczy ustawić pk na None:


blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

I bardziej kompleksowy przykład dla dziedziczenia:


class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

Przede wszystkim w powyższym przykładzie z dokumentacji brakuje informacji "co dalej" jeśli mamy kolejne dziedziczenie.

W moim przypadku niestety rozwiązanie się nie sprawdziło, ponieważ dziedziczę kilkukrotnie. Posilę się tu przykładem który pozwolił mi zrozumieć "jak to działa" ze stackoverflow.


class ModelA(models.Model):
    info1 = models.CharField(max_length=64)

class ModelB(ModelA):
    info2 = models.CharField(max_length=64)

class ModelC(ModelB):
    info3 = models.CharField(max_length=64)

I co wtedy?

Okazuje się że wszystkie pośrednie id również powinny być ustawiane na None. Wszystkie pola które mają _ptr


c.pk =None
c.id=None
c.modela_ptr_id=None
c.modelb_ptr_id=None
c.save()

Dopiero takie podejście do problemu powoduje utworzenie nowego id dla skopiowanego modelu. No dobrze, mamy sposób, ale co jeśli modeli z wieokrotnym dziedziczeniem jest 18 jak w moim przypadku, a każdy z wynikowych modeli ma różną ilość rodziców? Tak powstała prosta funkcja ustawiająca wszystko "po drodze" do docelowego modelu na None:


def clear_pks(new_model):
    new_model.pk = None
    new_model.id = None
    for field in new_model._meta.fields:
        if field.name.endswith('_ptr'):
            setattr(mynew, field.name, None)
    return mynew

Mam nadzieję, że ktoś trafi na moje rozwiązanie i skorzysta, oszczędzając przy tym wiele czasu.