Jak sklonować obiekt instancji modelu Django i zapisać go w bazie danych?

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

W bazie danych chcę dodać kolejny obiekt, który jest kopią powyższego obiektu.

Załóżmy, że mój stół ma jeden wiersz. Chcę wstawić obiekt pierwszego wiersza do innego wiersza za pomocą innego klucza podstawowego. Jak mogę to zrobić?

użytkownik426795
źródło

Odpowiedzi:

437

Po prostu zmień klucz podstawowy swojego obiektu i uruchom save ().

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

Jeśli chcesz automatycznie wygenerować klucz, ustaw nowy klucz na Brak.

Więcej na temat AKTUALIZACJI / WSTAW tutaj .

Oficjalne dokumenty dotyczące kopiowania instancji modeli: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances

Miah
źródło
2
Warto zauważyć, że cytuje Django 1.2, teraz jesteśmy w wersji Django 1.4. Nie przetestowałem, czy to działa, ale nie używaj tej odpowiedzi, nie upewniając się, że działa ona dla Ciebie.
Joe
7
Działa dobrze w 1.4.1 Jest to prawdopodobnie jedna z tych rzeczy, które będą działać przez długi czas.
frnhr
8
Musiałem ustawić oba obj.pki obj.idwykonać tę pracę w Django 1.4
Petr Peller
3
@PetrPeller - dokumenty sugerują, że używasz dziedziczenia modelu.
Dominic Rodger
12
Uwaga: sprawy mogą być nieco bardziej złożone, jeśli w grę wchodzą klucze obce, one2one i m2m (tzn. Mogą być bardziej złożone scenariusze „głębokiej kopii”)
Ben Roberts
135

Dokumentacja Django dla zapytań do bazy danych zawiera sekcję na temat kopiowania instancji modelu . Zakładając, że klucze podstawowe są generowane automatycznie, otrzymujesz obiekt, który chcesz skopiować, ustaw klucz podstawowy Nonei zapisz obiekt ponownie:

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

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

W tym fragmencie pierwszy save()tworzy oryginalny obiekt, a drugi save()tworzy kopię.

Jeśli nadal czytasz dokumentację, istnieją również przykłady obsługi dwóch bardziej złożonych przypadków: (1) kopiowanie obiektu będącego instancją podklasy modelu oraz (2) kopiowanie powiązanych obiektów, w tym obiektów w wielu -wiele relacji.


Uwaga na odpowiedź Miah: Ustawienie pk na Nonejest wspomniane w odpowiedzi Miah, chociaż nie jest przedstawione z przodu i na środku. Więc moja odpowiedź służy głównie podkreśleniu tej metody jako zalecanego przez Django sposobu jej wykonania.

Uwaga historyczna: Nie zostało to wyjaśnione w dokumentach Django do wersji 1.4. Było to jednak możliwe przed 1.4.

Możliwa przyszła funkcjonalność: wyżej wymieniona zmiana dokumentacji została wprowadzona w tym bilecie . W wątku komentarza do biletu pojawiła się także dyskusja na temat dodania wbudowanej copyfunkcji dla klas modeli, ale o ile wiem, zdecydowali się nie rozwiązywać tego problemu. Więc ten „ręczny” sposób kopiowania prawdopodobnie będzie na razie wystarczający.

S. Kirby
źródło
46

Uważaj tutaj. Może to być bardzo kosztowne, jeśli jesteś w jakiejś pętli i pobierasz obiekty jeden po drugim. Jeśli nie chcesz wywoływać połączenia z bazą danych, po prostu wykonaj:

from copy import deepcopy

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

Robi to samo, co niektóre z tych innych odpowiedzi, ale nie wywołuje wywołania bazy danych w celu pobrania obiektu. Jest to również przydatne, jeśli chcesz utworzyć kopię obiektu, który nie istnieje jeszcze w bazie danych.

Troy Grosfield
źródło
1
Działa to świetnie, jeśli masz obiekt, możesz głęboko skopiować oryginalny obiekt przed wprowadzeniem zmian, wprowadzić zmiany w nowym obiekcie i zapisać go. Następnie możesz wykonać sprawdzanie warunków i zależnie od tego, czy przejdą, tj. Obiekt znajduje się w innej sprawdzanej tabeli, możesz ustawić new_instance.id = original_instance.id i zapisać :) Dzięki!
radtek
2
To nie działa, jeśli model ma wiele poziomów dziedziczenia.
David Cheung,
1
w moim przypadku chciałem stworzyć metodę klonowania dla modelu, która używałaby zmiennej „self” i nie mogę po prostu ustawić self.pk Brak, więc to rozwiązanie działało jak urok. Pomyślałem o rozwiązaniu model_to_dict poniżej, ale wymaga to dodatkowego kroku i miałoby ten sam problem z relacjami przelotowymi, które i tak muszę sobie radzić ręcznie, więc nie ma to dla mnie większego wpływu.
Anderson Santos,
32

Użyj poniższego kodu:

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
źródło
8
model_to_dictprzyjmuje excludeparametr, co oznacza, że ​​nie potrzebujesz osobnego pop:model_to_dict(instance, exclude=['id'])
georgebrock
20

Jest tu fragment klonu , który możesz dodać do swojego modelu, który to robi:

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)
Dominic Rodger
źródło
@ user426975 - ach, no cóż (usunąłem go z mojej odpowiedzi).
Dominic Rodger,
Nie jestem pewien, czy jest to wersja Django rzecz, ale ifteraz musi być if fld.name != old._meta.pk.name, czyli namewłaściwość _meta.pkinstancji.
Chris
20

Jak to zrobić, dodano do oficjalnych dokumentów Django w Django 1.4

https://docs.djangoproject.com/en/1.10/topics/db/queries/#copying-model-instances

Oficjalna odpowiedź jest podobna do odpowiedzi Miah, ale dokumenty wskazują na pewne trudności z dziedziczeniem i powiązanymi obiektami, więc prawdopodobnie powinieneś się upewnić, że czytasz dokumenty.

Michael Bylstra
źródło
po otwarciu linku jest napisane, że nie znaleziono strony
Amrit
Dokumenty już nie istnieją dla Django 1.4. Zaktualizuję odpowiedź, wskazując najnowsze dokumenty.
Michael Bylstra
1
@MichaelBylstra Dobrym sposobem na uzyskanie wiecznie zielonych linków jest użycie stablezamiast numeru wersji w adresie URL, na przykład: docs.djangoproject.com/en/stable/topics/db/queries/…
Flimm
8

Natknąłem się na kilka błędów z zaakceptowaną odpowiedzią. Oto moje rozwiązanie.

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

Uwaga: wykorzystuje to rozwiązania, które nie są oficjalnie sankcjonowane w dokumentach Django i mogą przestać działać w przyszłych wersjach. Testowałem to w wersji 1.9.13.

Pierwsze ulepszenie polega na tym, że pozwala ono dalej używać oryginalnej instancji za pomocą copy.copy. Nawet jeśli nie zamierzasz ponownie używać instancji, bezpieczniej jest wykonać ten krok, jeśli klonowana instancja została przekazana jako argument funkcji. Jeśli nie, program wywołujący nieoczekiwanie będzie miał inną instancję po powrocie funkcji.

copy.copywydaje się produkować płytką kopię instancji modelu Django w pożądany sposób. Jest to jedna z rzeczy, których nie udokumentowałem, ale działa ona poprzez trawienie i odprawianie, więc prawdopodobnie jest dobrze obsługiwana.

Po drugie, zatwierdzona odpowiedź pozostawi wszelkie wstępnie pobrane wyniki dołączone do nowej instancji. Te wyniki nie powinny być powiązane z nową instancją, chyba że jawnie skopiujesz wiele relacji. Jeśli przejdziesz przez wstępnie pobrane relacje, otrzymasz wyniki, które nie pasują do bazy danych. Złamanie działającego kodu podczas dodawania pobrania wstępnego może być przykrą niespodzianką.

Usuwanie _prefetched_objects_cacheto szybki i brudny sposób na usunięcie wszystkich prefiksów. Kolejne dostępy działają tak, jakby nigdy nie było pobrania wstępnego. Korzystanie z nieudokumentowanej właściwości, która zaczyna się od znaku podkreślenia, prawdopodobnie powoduje problemy ze zgodnością, ale na razie działa.

poranna gwiazda
źródło
Udało mi się to uruchomić, ale wygląda na to, że mogło się już zmienić w 1.11, ponieważ miałem właściwość o nazwie _[model_name]_cache, która po usunięciu mogłem przypisać nowy identyfikator dla tego powiązanego modelu, a następnie wywołać save(). Nadal mogą występować działania niepożądane, których jeszcze nie określiłem.
trpt4him
Jest to niezwykle ważna informacja, jeśli wykonujesz klonowanie w funkcji klasy / mixin, ponieważ w przeciwnym razie zepsuje to „ja” i wszystko się zdezorientuje.
Andreas Bergström,
5

ustawienie pk na None jest lepsze, sinse Django może poprawnie utworzyć pk dla ciebie

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
Ardine
źródło
3

Jest to kolejny sposób klonowania instancji modelu:

d = Foo.objects.filter(pk=1).values().first()   
d.update({'id': None})
duplicate = Foo.objects.create(**d)
Ahtisham
źródło
0

Aby sklonować model z wieloma poziomami dziedziczenia, tj.> = 2 lub ModelC poniżej

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)

Proszę odnieść się do pytania tutaj .

David Cheung
źródło
Ach tak, ale to pytanie nie ma zaakceptowanej odpowiedzi! Tak trzymać!
Bobort
0

Spróbuj tego

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
Pulkit Pahwa
źródło