Наследование модели Django: создать субэкземпляр существующего экземпляра (downcast)?

Я пытаюсь интегрировать стороннее приложение Django, которое приняло неудачное решение наследовать от django.contrib.auth.models.User, который является большим нет-нет для подключаемых приложений. Цитируя Малкольма Трединника:

Что еще более важно, так же, как и в Python, вы не можете "опуститься" с наследованием модели Django. То есть, если вы уже создали экземпляр User, вы не можете, не ковыряясь под покровом, заставить этот экземпляр соответствовать экземпляру подкласса, который вы еще не создали.

Ну, я в ситуации, когда мне нужно интегрировать это стороннее приложение с моими существующими пользовательскими экземплярами. Так что, если гипотетически я действительно хочу подшить под одеяло, каковы мои варианты? Я знаю, что это не работает:

extended_user = ExtendedUser(user_ptr_id=auth_user.pk)
extended_user.save()

Там нет исключений, но это ломает все виды вещей, начиная с перезаписи всех столбцов из django.contrib.auth.models.User с пустыми строками...

5 ответов

Решение

Это должно работать:

extended_user = ExtendedUser(user_ptr_id=auth_user.pk)
extended_user.__dict__.update(auth_user.__dict__)
extended_user.save()

Здесь вы просто копируете значения из версии auth_user в расширенную версию и сохраняете ее заново. Не очень элегантно, но это работает.

Я нашел этот ответ, спросив в списке рассылки django-user:

https://groups.google.com/d/msg/django-users/02t83cuEbeg/JnPkriW-omQJ

Это не является частью общедоступного API, но вы можете положиться на то, как Django загружает данные изнутри.

parent = Restaurant.objects.get(name__iexact="Bob's Place").parent
bar = Bar(parent=parent, happy_hour=True)
bar.save_base(raw=True)

Имейте в виду, что это может сломаться с любой новой версией Django.

Если тебе не нравится __dict__.update Решение вы можете сделать это:

for field in parent_obj._meta.fields
    setattr(child_obj, field.attname, getattr(parent_obj, field.attname))

Я использую Django 1.6, и мой ExtendedUser модель от OSQA (forum.models.user.User). По какой-то странной причине вышеуказанные решения с dict.__update__ и с setattr иногда не удается. Это может быть связано с некоторыми другими моими моделями, которые накладывают ограничения на пользовательские таблицы. Вот еще два обходных пути, которые вы можете попробовать:

Обходной путь № 1:

extended_user = ExtendedUser(user_ptr_id = user.pk)
extended_user.save() # save first time
extended_user.__dict__.update(user.__dict__)
extended_user.save() # save second time

Обходной путь № 2:

extended_user = ExtendedUser(user_ptr_id = user.pk)
extended_user.__dict__.update(user.__dict__)
extended_user.id=None
extended_user.save()

То есть иногда сохранение нового дочернего экземпляра завершается неудачно, если вы установили оба pk а также id, но вы можете установить только pk, сохранить его, и тогда все, кажется, работает нормально.

Для этого вопроса есть открытая ошибка: 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(child_cls, parent_obj, 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)

create_child_from_parent_model(ExtendedUser, auth_user, {})

Преимущество этого метода заключается в том, что методы, которые перезаписываются дочерним элементом, не заменяются исходными родительскими методами. Для меня, используя оригинальные ответы obj.__dict__.update() привело к исключениям, так как я использовал FieldTracker от model_utils в родительском классе.

Как насчет чего-то вроде этого:

from django.forms.models import model_to_dict

auth_user_dict = model_to_dict(auth_user)
extended_user = ExtendedUser.objects.create(user_ptr=auth_user, **auth_user_dict)

Ответ @guetti работал для меня с небольшим обновлением => Ключ был parent_ptr

parent_object = parent_model.objects.get(pk=parent_id)  
new_child_object_with_existing_parent = Child(parent_ptr=parent, child_filed1='Nothing')
new_child_object_with_existing_parent.save()

Я хотел создать запись в моей модели профиля для существующего пользователя, моя модель была похожа

from django.contrib.auth.models import User as user_model
class Profile(user_model):
     bio = models.CharField(maxlength=1000)
     another_filed = models.CharField(maxlength=1000, null=True, blank=True)

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

Пример, который работал для меня

from meetings.user import Profile
from django.contrib.auth.models import User as user_model

user_object = user_model.objects.get(pk=3)  
profile_object = Profile(user_ptr=user_object, bio='some')
profile_object.save()
Другие вопросы по тегам