2015-05-28 20:28:21 +00:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
2015-05-29 19:15:36 +00:00
|
|
|
from django.db import models
|
2015-05-28 04:21:04 +00:00
|
|
|
|
2015-05-28 20:28:21 +00:00
|
|
|
|
|
|
|
class MergeException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class OneToOneConflict(MergeException):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
ERROR = 0
|
|
|
|
KEEP = 1
|
2015-05-28 20:45:07 +00:00
|
|
|
DELETE = 2
|
2015-05-28 20:28:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def merge(from_obj, to_obj, one_to_one_conflict=ERROR):
|
2015-05-28 19:55:35 +00:00
|
|
|
if not isinstance(from_obj, type(to_obj)):
|
|
|
|
raise ValueError("both objects must be of the same type")
|
|
|
|
|
2015-05-28 19:57:20 +00:00
|
|
|
if from_obj.pk == to_obj.pk:
|
|
|
|
raise ValueError("cannot merge object with itself")
|
|
|
|
|
2015-05-28 04:21:04 +00:00
|
|
|
for related in from_obj._meta.get_all_related_objects():
|
|
|
|
accessor_name = related.get_accessor_name()
|
|
|
|
varname = related.field.name
|
2015-05-28 19:07:51 +00:00
|
|
|
|
2015-05-29 19:15:36 +00:00
|
|
|
# in Django 1.8 can test for related.one_to_one
|
|
|
|
if isinstance(related.field, models.OneToOneField):
|
2015-05-28 12:41:38 +00:00
|
|
|
try:
|
2015-05-28 19:07:51 +00:00
|
|
|
field = getattr(from_obj, accessor_name)
|
|
|
|
try:
|
2015-05-28 20:45:07 +00:00
|
|
|
to_field = getattr(to_obj, accessor_name)
|
|
|
|
if one_to_one_conflict == KEEP:
|
|
|
|
pass # do nothing
|
|
|
|
elif one_to_one_conflict == DELETE:
|
|
|
|
to_field.delete()
|
|
|
|
setattr(field, varname, to_obj)
|
|
|
|
field.save()
|
|
|
|
else:
|
2015-05-29 19:15:36 +00:00
|
|
|
raise OneToOneConflict(
|
|
|
|
"both fields have an attribute set for {}".format(accessor_name))
|
2015-05-28 20:28:21 +00:00
|
|
|
except ObjectDoesNotExist:
|
2015-05-28 19:07:51 +00:00
|
|
|
# doesn't exist, safe to overwrite
|
|
|
|
setattr(field, varname, to_obj)
|
|
|
|
field.save()
|
2015-05-28 20:28:21 +00:00
|
|
|
except ObjectDoesNotExist:
|
2015-05-28 19:07:51 +00:00
|
|
|
# from_obj one to one isn't set, skip
|
|
|
|
pass
|
2015-05-29 19:15:36 +00:00
|
|
|
# in Django 1.8 can test for related.multiple
|
|
|
|
elif isinstance(related.field, models.ForeignKey):
|
|
|
|
field = getattr(from_obj, accessor_name)
|
|
|
|
field.all().update(**{varname: to_obj})
|
2015-05-28 12:41:38 +00:00
|
|
|
else:
|
2015-05-28 20:28:21 +00:00
|
|
|
raise NotImplementedError('unexpected relation type, please file a bug')
|
2015-05-28 04:21:04 +00:00
|
|
|
|
2015-05-28 18:53:05 +00:00
|
|
|
for related in from_obj._meta.get_all_related_many_to_many_objects():
|
2015-05-28 04:21:04 +00:00
|
|
|
accessor_name = related.get_accessor_name()
|
2015-05-28 20:06:48 +00:00
|
|
|
varname = related.field.name
|
|
|
|
|
|
|
|
if not accessor_name:
|
|
|
|
# not set in M2M to self, but varname will match
|
|
|
|
accessor_name = varname
|
|
|
|
|
|
|
|
field = getattr(from_obj, accessor_name)
|
2015-05-29 19:15:36 +00:00
|
|
|
for f in field.all():
|
|
|
|
getattr(f, varname).remove(from_obj)
|
|
|
|
getattr(f, varname).add(to_obj)
|