Я экспериментировал с решением Стивена Г. Тагги и нашел его очень умным, но, к сожалению, он не будет работать в некоторых особых ситуациях.
Предположим, следующий сценарий:
class FattAqp(models.Model):
descr = models.CharField('descrizione', max_length=200)
ef = models.ForeignKey(Esercizio, ...)
forn = models.ForeignKey(Fornitore, ...)
class Periodo(models.Model):
# id usato per identificare i documenti
# periodo rilevato in fattura
data_i_p = models.DateField('data inizio', blank=True)
idfatt = models.ForeignKey(FattAqp, related_name='periodo')
class Lettura(models.Model):
mc_i = models.DecimalField(max_digits=7, ...)
faqp = models.ForeignKey(FattAqp, related_name='lettura')
an_im = models.ForeignKey('cnd.AnagImm', ..)
class DettFAqp(models.Model):
imponibile = models.DecimalField(...)
voce = models.ForeignKey(VoceAqp, ...)
periodo = models.ForeignKey(Periodo, related_name='dettfaqp')
В этом случае, если мы попытаемся глубоко скопировать экземпляр FattAqp, поля ef, forn, an_im и voce будут установлены неправильно; с другой стороны, idfatt, faqp, periodo будут.
Я решил проблему, добавив еще один параметр в функцию и слегка изменив код. Я проверил это с Python 3.6 и Django 2.2
Вот оно:
def duplicate_model_with_descendants(obj, whitelist, _new_parent_pk=None, static_fk=None):
kwargs = {}
children_to_clone = OrderedDict()
for field in obj._meta.get_fields():
if field.name == "id":
pass
elif field.one_to_many:
if field.name in whitelist:
these_children = list(getattr(obj, field.name).all())
if field.name in children_to_clone:
children_to_clone[field.name] |= these_children
else:
children_to_clone[field.name] = these_children
else:
pass
elif field.many_to_one:
name_with_id = field.name + '_id'
if _new_parent_pk:
kwargs[name_with_id] = _new_parent_pk
if name_with_id in static_fk:
kwargs[name_with_id] = getattr(obj, name_with_id)
elif field.concrete:
kwargs[field.name] = getattr(obj, field.name)
else:
pass
new_instance = obj.__class__(**kwargs)
new_instance.save()
new_instance_pk = new_instance.pk
for ky in children_to_clone.keys():
child_collection = getattr(new_instance, ky)
for child in children_to_clone[ky]:
child_collection.add(
duplicate_model_with_descendants(child, whitelist=whitelist, _new_parent_pk=new_instance_pk,static_fk=static_fk))
Пример использования:
original_record = FattAqp.objects.get(pk=4)
WHITELIST = ['lettura', 'periodo', 'dettfaqp']
STATIC_FK = ['fornitore_id','ef_id','an_im_id', 'voce_id']
duplicate_record = duplicate_model_with_descendants(original_record, WHITELIST, static_fk=STATIC_FK)