Наследование модели 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()