2

I have found this code http://djangosnippets.org/snippets/2283/ but I think it doesn't work with manytomanyfields which use another model (keyword through) - I get an AttributeError when trying to merge.

Do you know any way to fix this or another method for merging objects ?

Edit: more details

I have 3 models: A, B, C

A has a manytomany field "m2mfield" pointing to C through B.

When I run the code from django snippets, it fails with the exception

'ManyRelatedManager' object has no attribute 'remove'

I think this has something to do with a comment in Django source (django.db.models.fields.related.py line 499) which says:

# If the ManyToMany relation has an intermediary model,
# the add and remove methods do not exist.

I think the code snippet I got doesn't make any difference between ManyToMany relations with and without intermediary model. That's why I'm looking for some way to fix that code or another way to achieve what I want (merging).

Weier
  • 1,339
  • 1
  • 10
  • 20

2 Answers2

2

I eventually modified the code to handle the case of ManyToMany fields created 'through' a model. Here is what is to be modified:

# Migrate all many to many references from alias object to primary object.
for related_many_object in alias_object._meta.get_all_related_many_to_many_objects():
    alias_varname = related_many_object.get_accessor_name()
    obj_varname = related_many_object.field.name

    # Treatment depends on if the many_to_many field is created through another model
    if getattr(alias_object, alias_varname).through._meta.auto_created:
        if alias_varname is not None:
            # standard case
            related_many_objects = getattr(alias_object, alias_varname).all()
        else:
            # special case, symmetrical relation, no reverse accessor
            related_many_objects = getattr(alias_object, obj_varname).all()
        for obj in related_many_objects.all():
            getattr(obj, obj_varname).remove(alias_object)
            getattr(obj, obj_varname).add(primary_object)
    else:
        related_many_objects = getattr(alias_object, alias_varname).all()

        through_model = getattr(alias_object, alias_varname).through
        through_field_name = None
        for f in through_model._meta.fields:
            if isinstance(f, ForeignKey):
                if f.rel.to == primary_class :
                # f is the field in our 'through' model which points to an instance of primary_class
                    through_field_name = f.name

        for obj in related_many_objects.all():
            kwargs = {
                through_field_name: obj,
            }
            for through_obj in through_model.objects.filter(**kwargs):
                setattr(through_obj, through_field_name, primary_object)
                through_obj.save()
Weier
  • 1,339
  • 1
  • 10
  • 20
0

The error you have means that the manytomany field you are trying to populate is managed with a through model.

There is in the snippet you pasted a code that should be through models aware for the merge to work, it starts L55:

    # Migrate all many to many references from alias object to primary object.
    for related_many_object in alias_object._meta.get_all_related_many_to_many_objects():
        alias_varname = related_many_object.get_accessor_name()
        obj_varname = related_many_object.field.name

        if alias_varname is not None:
            # standard case
            related_many_objects = getattr(alias_object, alias_varname).all()
        else:
            # special case, symmetrical relation, no reverse accessor
            related_many_objects = getattr(alias_object, obj_varname).all()
        for obj in related_many_objects.all():
            getattr(obj, obj_varname).remove(alias_object)
            getattr(obj, obj_varname).add(primary_object)  # this can't work

You have to provide merge_model_objects a dictionary of functions where merge_model_objects will be able to pick a function to build the through class. Most likely this code should replace the last line of the snippet I took above.

But you should also want to take care of the fact that A1, A2 and C1, C2 might be equal while B1, B2 aren't, which current code doesn't handle too.

amirouche
  • 7,682
  • 6
  • 40
  • 94