Как мне клонировать объект экземпляра модели Django и сохранить его в базе данных?

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

В базу данных я хочу добавить еще один объект, который является копией объекта выше.

Предположим, у моей таблицы есть один ряд. Я хочу вставить объект первой строки в другую строку с другим первичным ключом. Как я могу это сделать?

user426795
источник

Ответы:

438

Просто измените первичный ключ вашего объекта и запустите 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-instances.

Miah
источник
2
Стоит отметить, что это цитирует Django 1.2, теперь мы до Django 1.4. Не проверяли, работает ли это, или нет, но не используйте этот ответ, не будучи уверенным, что он работает для вас.
Джо
7
Прекрасно работает в 1.4.1. Вероятно, это одна из тех вещей, которые будут работать долго.
2012 г.
8
Я должен был установить оба obj.pkи obj.idсделать эту работу в Django 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 способ сделать это.

Историческая справка: Это не было объяснено в документации 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_instance.id = original_instance.id и сохранить :) Спасибо!
Радтек
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)
t_io
источник
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-instances

Официальный ответ аналогичен ответу 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
Это чрезвычайно важная информация, если вы выполняете клонирование в функции класса / миксина, так как в противном случае это приведет к путанице «я», и вы запутаетесь.
Андреас Бергстрем
5

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

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
Ardine
источник
3

Это еще один способ клонирования экземпляра модели:

d = Foo.objects.filter(pk=1).values().first()   
d.update({'id': None})
duplicate = Foo.objects.create(**d)
Ahtisham
источник
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()
Pulkit Pahwa
источник