Як клонувати об’єкт екземпляра моделі Django і зберігати його в базі даних?


260
Foo.objects.get(pk="foo")
<Foo: test>

У базу даних я хочу додати ще один об'єкт, який є копією об'єкта вгорі.

Припустимо, у мого столу є один ряд. Я хочу вставити об'єкт першого рядка в інший рядок з іншим первинним ключем. Як я можу це зробити?

Відповіді:


437

Просто змініть основний ключ вашого об'єкта та запустіть save ().

obj = Foo.objects.get(pk=<some_existing_pk>)
obj.pk = None
obj.save()

Якщо ви хочете автоматично згенерувати ключ, встановіть новий ключ на "Нічого".

Більше про ОНОВЛЕННЯ / ВСТАВКА тут .

Офіційні документи щодо копіювання моделей примірників: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-in вещества


2
Варто зауважити, що це цитує Django 1.2, ми тепер перейшли до Django 1.4. Не перевіряли, чи працює це чи ні, але не використовуйте цю відповідь, не впевнившись, що вона працює для вас.
Джо

7
Добре працює в 1.4.1. Це, мабуть, одна з тих речей, яка буде працювати довгий час.
frnhr

8
Мені довелося встановити і те, obj.pkі інше, obj.idщоб зробити цю роботу в Джанго 1.4
Петро Пеллер

3
@PetrPeller - документи пропонують це тому, що ви використовуєте успадкування моделі.
Домінік Роджер

12
Примітка: речі можуть бути дещо складнішими, якщо є сторонні ключі, задіяні one2one та m2m (тобто можуть бути складніші сценарії "глибокої копії")
Бен Робертс

135

Документація Django для запитів до бази даних включає розділ про копіювання екземплярів моделі . Припускаючи, що ваші первинні ключі автоматично генеруються, ви отримуєте об'єкт, який потрібно скопіювати, встановлюєте первинний ключ Noneі знову зберігаєте об’єкт:

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

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

У цьому фрагменті перший save()створює оригінальний об'єкт, а другий save()створює копію.

Якщо ви продовжуєте читати документацію, є також приклади того, як обробляти два більш складні випадки: (1) копіювання об'єкта, що є екземпляром модельного підкласу, і (2) також копіювання пов'язаних об'єктів, включаючи об'єкти в багато-до -мало стосунків.


Примітка до відповіді miah: Встановлення pk Noneвказується у відповіді miah, хоча воно не представлено спереду та в центрі. Тож моя відповідь в основному служить для того, щоб підкреслити цей метод як рекомендований джанго спосіб.

Історична примітка: Це не було пояснено в документах Django до версії 1.4. Однак це можливо ще до 1,4 року.

Можлива функціональність у майбутньому: Згадані зміни в документах були внесені в цей квиток . У темі коментаря до квитка також було обговорено питання щодо додавання вбудованої copyфункції для модельних класів, але, наскільки я знаю, вони ще не вирішили вирішити цю проблему. Тож цей "ручний" спосіб копіювання, мабуть, доведеться робити зараз.


46

Будьте обережні тут. Це може бути надзвичайно дорого, якщо ви знаходитесь в певному циклі і ви шукаєте об'єкти по одному. Якщо ви не хочете дзвінка в базу даних, просто зробіть:

from copy import deepcopy

new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()

Це робить те саме, що і деякі інші відповіді, але він не робить виклик бази даних для отримання об'єкта. Це також корисно, якщо ви хочете зробити копію об'єкта, який ще не існує в базі даних.


1
Це чудово спрацьовує, якщо у вас є об'єкт, ви можете глибоко скопіювати оригінальний об’єкт перед тим, як внести зміни, внести зміни в новий об’єкт і зберегти його. Потім ви можете зробити перевірку стану та залежно від того, чи вони проходять, тобто об'єкт знаходиться в іншій таблиці, яку ви перевіряєте, ви можете встановити new_instan.id = original_instan.id та зберегти :) Дякую!
radtek

2
Це не працює, якщо модель має кілька рівнів успадкування.
Девід Чеун

1
у моєму випадку я хотів створити метод клонування для моделі, який би використовував змінну "self", і я не можу просто встановити self.pk None, тому це рішення спрацювало як шарм. Я подумав про рішення model_to_dict нижче, але це вимагає додаткового кроку, і у нього виникне та сама проблема з наскрізними відносинами, з якими я маю справу в будь-якому випадку вручну, щоб це не мало для мене великого впливу.
Андерсон Сантос

32

Використовуйте наведений нижче код:

from django.forms import model_to_dict

instance = Some.objects.get(slug='something')

kwargs = model_to_dict(instance, exclude=['id'])
new_instance = Some.objects.create(**kwargs)

8
model_to_dictбере excludeпараметр, що означає, що вам не потрібно окремого pop:model_to_dict(instance, exclude=['id'])
georgebrock

20

Там клон фрагмент тут , який ви можете додати до вашої моделі , яка робить це:

def clone(self):
  new_kwargs = dict([(fld.name, getattr(old, fld.name)) for fld in old._meta.fields if fld.name != old._meta.pk]);
  return self.__class__.objects.create(**new_kwargs)

@ user426975 - ах, ну добре (я видалив його зі своєї відповіді).
Домінік Роджер

Не впевнений, що це річ версії Django, але ifтепер це має бути if fld.name != old._meta.pk.name, тобто, nameвласність _meta.pkпримірника.
Кріс

20

Як це зробити, було додано до офіційних документів Django в Django1.4

https://docs.djangoproject.com/en/1.10/topics/db/queries/#copying-model-in вещества

Офіційна відповідь схожа з відповіддю miah, але документи вказують на певні труднощі з успадкуванням та пов’язаними об'єктами, тому, ймовірно, ви повинні переконатися, що ви прочитали документи.


коли ви відкриваєте посилання, там написано, що сторінка не знайдена
Amrit

Документи більше не існують для Django 1.4. Я оновлю відповідь, щоб вказати на останні документи.
Майкл Більстра

1
@MichaelBylstra Хороший спосіб мати вічнозелені посилання - це використовувати stableзамість номера версії в URL-адресі, наприклад, така: docs.djangoproject.com/en/stable/topics/db/queries/…
Flimm

8

Я зіштовхнувся з парою готчей із прийнятою відповіддю. Ось моє рішення.

import copy

def clone(instance):
    cloned = copy.copy(instance) # don't alter original instance
    cloned.pk = None
    try:
        delattr(cloned, '_prefetched_objects_cache')
    except AttributeError:
        pass
    return cloned

Примітка: тут використовуються рішення, які офіційно не дозволені в документах Django, і вони можуть перестати працювати в майбутніх версіях. Я перевірив це в 1.9.13.

Перше вдосконалення полягає в тому, що воно дозволяє продовжувати використовувати початковий екземпляр, використовуючи copy.copy. Навіть якщо ви не збираєтесь повторно використовувати екземпляр, це може бути безпечніше зробити цей крок, якщо екземпляр, який ви клонуєте, був переданий як аргумент функції. Якщо ні, абонент несподівано матиме інший примірник, коли функція повернеться.

copy.copyздається, створюється мілка копія екземпляра моделі Django потрібним чином. Це одна з речей, яку я не знайшов задокументованою, але вона працює шляхом маринування та відсікання, тому, ймовірно, це добре підтримується.

По-друге, затверджена відповідь залишить усі попередньо встановлені результати, додані до нового екземпляра. Ці результати не повинні пов'язуватися з новим екземпляром, якщо ви явно не скопіюєте зв'язки на багато. Якщо ви перейдете заздалегідь встановленими відносинами, ви отримаєте результати, які не відповідають базі даних. Порушення робочого коду при додаванні попереднього вибору може бути неприємним сюрпризом.

Видалення _prefetched_objects_cache- це швидкий і брудний спосіб зняти всі попередні вибори. Подальший доступ до багатьох працює так, ніби ніколи не було попереднього вибору. Використання недокументованого властивості, яке починається з підкреслення, ймовірно, вимагає проблем із сумісністю, але воно працює на даний момент.


Мені вдалося змусити це працювати, але, схоже, воно вже змінилося в 1.11, оскільки я мав виклик властивості _[model_name]_cache, якому, як тільки видалили, я зміг призначити новий ідентифікатор для цієї пов'язаної моделі, а потім зателефонувати save(). Ще можуть бути побічні ефекти, яких я ще не визначив.
trpt4him

Це надзвичайно важлива інформація, якщо ви займаєтесь клонуванням у функції класу / mixin, оскільки це в іншому випадку зіпсує «я», і ви заплутаєтесь.
Андреас Бергстрем

5

встановити pk на None не краще, sinse Django може правильно створити pk для вас

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()

3

Це ще один спосіб клонування модельного примірника:

d = Foo.objects.filter(pk=1).values().first()   
d.update({'id': None})
duplicate = Foo.objects.create(**d)

0

Для клонування моделі з декількома рівнями спадкування, тобто> = 2, або ModelC нижче

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)

Будь ласка , зверніться питання тут .


Так, але на це питання немає прийнятої відповіді! Шлях!
Бобборт

0

Спробуйте це

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.