- Author:
- johnboxall
- Posted:
- January 13, 2009
- Language:
- Python
- Version:
- 1.0
- Score:
- 2 (after 2 ratings)
I've got a bunch of Models
that form a tree like structure. I'd like to duplicate them all changing one field to something else.
Say for example I've got a Website
which has Pages
and Links
and all kinds of other Models
. Each one of these belong to a User
(through a foreign key relation). I could use duplicate
to create a copy of an entire website and give it to another User
with something like this:
class Website(Model):
owner = ForeignKey('auth.User')
...
class Link(Model):
owner = ForeignKey('auth.User')
...
class Page(Model):
owner = ForeignKey('auth.User')
...
##################################
website = Website.objects.get(pk=1)
new_owner = User.objects.get(pk=1)
duplicate(website, new_owner, 'owner')
For a in depth example of the problem see: Duplicating Model Instances @ STO
Note
- Not tested with anything but simple Foreign Key relations - the model ordering is very naive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | from django.db.models.query import CollectedObjects
from django.db.models.fields.related import ForeignKey
def duplicate(obj, value, field):
"""
Duplicate all related objects of `obj` setting
`field` to `value`. If one of the duplicate
objects has an FK to another duplicate object
update that as well. Return the duplicate copy
of `obj`.
"""
collected_objs = CollectedObjects()
obj._collect_sub_objects(collected_objs)
related_models = collected_objs.keys()
root_obj = None
# Traverse the related models in reverse deletion order.
for model in reversed(related_models):
# Find all FKs on `model` that point to a `related_model`.
fks = []
for f in model._meta.fields:
if isinstance(f, ForeignKey) and f.rel.to in related_models:
fks.append(f)
# Replace each `sub_obj` with a duplicate.
sub_obj = collected_objs[model]
for pk_val, obj in sub_obj.iteritems():
for fk in fks:
fk_value = getattr(obj, "%s_id" % fk.name)
# If this FK has been duplicated then point to the duplicate.
if fk_value in collected_objs[fk.rel.to]:
dupe_obj = collected_objs[fk.rel.to][fk_value]
setattr(obj, fk.name, dupe_obj)
# Duplicate the object and save it.
obj.id = None
setattr(obj, field, value)
obj.save()
if root_obj is None:
root_obj = obj
return root_obj
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Great snippet! I would make just one fix to be sure that images and file fields copy the files too. See:
#
Hi I also posted this on stackoverflow in the related Q/A.
I reworked this a bit to be compatible with the Collector which replaced CollectedObjects in 1.3.
I didn't really test this too heavily, but did test it with an object with about 20,000 sub-objects, but in only about three layers of foreign-key depth. Use at your own risk of course.
For the ambitious guy who reads this post, you should consider subclassing Collector (or copying the entire class to remove this dependency on this unpublished section of the django API) to a class called something like "DuplicateCollector" and writing a .duplicate method that works similarly to the .delete method. that would solve this problem in a real way.
#
Please login first before commenting.