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.copy
wydaje 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_cache
to 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.