20

I am trying to create a complete copy of a survey instance, which has several sections, and each section has several questions and finally each question has several options. I am using standard django 1.3.1, with MySQL. I need to be able to create a complete copy of all these elements, for a different survey owner. What I currently have in the view is:

    survey_new = survey
    survey_new.title = survey.title + ' -- Copy'
    survey_new.owner = str(new_owner_id)
    survey_new.created = datetime.now()
    survey_new.pk = None
    survey_new.save()

    for sec in survey.sections.all().order_by('order'):
        sec_n = sec
        sec_n.survey_id = survey_new.id 
        sec_n.pk = None
        sec_n.save()

        for q in sec.questions.all().order_by('order'):
            q_n = q
            q_n.section_id = sec_n.id
            q_n.pk = None
            q_n.save()

            for op in q.options.all().order_by('order'):
                op_n = op
                op_n.question_id = q_n.id
                op_n.pk = None
                op_n.save()

However, this seems to run through all the loops without any errors, and just creating a copy of survey. I was hoping that this would copy the survey, sections, questions, options for that survey instance. Just can't seem to figure out what I am doing wrong here.

Priyeshj
  • 1,295
  • 2
  • 17
  • 32
  • 2
    A couple of observations: 1. You should read [`select_related()`](https://docs.djangoproject.com/en/1.3/ref/models/querysets/#select-related) and see if it looks interesting. 2. Be very careful in copying the id values or you may unintentionally overwrite the *original* item in the DB. – Peter Rowell Jan 18 '12 at 21:56
  • Why aren't you using `Options.objects.create( ... )` and `Section.objects.create(...)` etc.? Why are you trying to fool around with the PK's? – S.Lott Jan 18 '12 at 21:57
  • @Peter: Thanks for the pointer to select_related(). That should optimize my code a bit :) – Priyeshj Jan 18 '12 at 23:12
  • @S.Lott: Could you please provide a pointer to objects.create() functionality? I can't seem to figure out how an object can be cloned, while editing fields in it using create() unless I manually assign all the fields of new object to the values of old object fields, and edit the changed ones. Would greatly appreciate your insight into the solution. – Priyeshj Jan 18 '12 at 23:12
  • "assign all the fields of new object to the values of old object fields, and edit the changed ones" is the solution I'm familiar with. Why aren't you using it? It works. You might be able short-cut some of it by building attribute dictionaries. – S.Lott Jan 18 '12 at 23:19
  • Actually I just wanted to keep the code base somewhat clean, and there are more than a couple dozen fields in some tables which would have to be manually assigned to the new entry if using .create(). I found that the reason why above code doesn't work is coz of the stupid reassignment of objects. Within each loop sec_n = sec, and then save() actually changes both the object's pointers to be the same object which is newly created. I had to save the object_id to be able to fetch the related objects after the new objects are saved. Thanks for the tips though. Always helps in thinking broadly. – Priyeshj Jan 19 '12 at 00:06
  • @S.Lott It is not that easy if model has too many fields – Mohammed Shareef C May 17 '21 at 12:41

2 Answers2

26

Googling "django deep copy" returns this on the first page: Copy Model Object in Django | Nerdy Dork

The code sample given is:

from copy import deepcopy
old_obj = deepcopy(obj)
old_obj.id = None
old_obj.save()

If I understand your question correctly, you will want to change some more fields before saving.

sebtheiler
  • 2,159
  • 3
  • 16
  • 29
Marcin
  • 48,559
  • 18
  • 128
  • 201
  • 13
    No pun intended but shouldn't the name, "old_obj" be appropriately called "new_obj" instead? I see since, we are trying to make a copy of obj and thereby making a new object? Just curious. – hlkstuv_23900 Jan 05 '15 at 09:05
  • 1
    @tilaprimera It makes no difference to functionality. It's named that way because `old_obj` is, apart from the id, value identical to obj at the point of copying. – Marcin Jan 05 '15 at 10:18
  • 5
    Just a note, this will not copy the relations of the object you're cloning. Which may or may not be the desired behavior, but it was for me, so I need to do something else. :) – bwest87 Oct 17 '15 at 04:50
  • 3
    @bwest87: The approach of this answer actually copies ForeignKey and OneToOneField attributes declared in the model which instance is copied, but not ManyToMany or the reverse of the above referencing it. – Tiphareth Dec 07 '16 at 13:13
1

I think this happens because survey assigned to survey_new on this line of code

survey_new = survey

An then when survey_new saved

survey_new.title = survey.title + ' -- Copy'
survey_new.owner = str(new_owner_id)
survey_new.created = datetime.now()
survey_new.pk = None
survey_new.save()

survey became equal to survey_new. For example you can check it so

id(survey)
# 50016784
id(survey_new)
# 50016784

or Django equivalent

survey.id
# 10
survey_new.id
# 10

To figure out the issue all required objects have to be collected before assignment

survey_section = survey.sections.all().order_by('order')
# ... all other questions and options 

survey_new = survey
survey_new.title = survey.title + ' -- Copy'
survey_new.owner = str(new_owner_id)
# ... your remaining code
Alexey Savanovich
  • 1,893
  • 11
  • 19