views:

36

answers:

1

This is a fun one :-)

Working on an EAV, we inject a generic relationship handler at runtime in a model.

model_cls is any class, and a EavValue class have a generic relation pointing to it. It works fine from EavValues to a model_cls, but on the other way we need to inject an accessor to ease things:

generic_relation = generic.GenericRelation(EavValue,
                                               object_id_field='entity_id',
                                               content_type_field='entity_ct',
                                               related_name=model_cls.__name__)
generic_relation.contribute_to_class(model_cls, 'eav_values')

Again, we do that at runtime because we want to make it work with untouchable 3rd party libs.

While unittesting with a Patient class as model_cls, we get the following error:

eav_ng.patient: Accessor for m2m field 'eav_values' clashes with related m2m field 'EavValue.Patient'. Add a related_name argument to the definition for 'eav_values'.

Now, we thought the easy fix was to change either the second parameter of contribute_to_class or related_name in GenericRelation, but it doesn't! We get exactly the same error, only with different name.

Second strange thing, running the same unittests with Sqlite instead of MySql: all pass.

What's more, no matters the order or the tests, we always get this error at the second tests. Since this process happen in a register method and that we call register and unregister at setup and tear down, I'm guessing our unregister method is imperfect.

Last strange fact: we get the error while running unittest, but we are unable to reproduce it manually. Worst, on my colleague computer, it doesn't get the error while we are using the same version of Python, Django, Ubuntu and MySQL.

We solved a lot of hard ones but we are kind of stuck on this one so any clue appreciated.

UPDATE:

New clues for this great game:

Errors are raised from this snippet in django.core.management.validation, on line 245 (django 1.2.1):

for r in rel_opts.get_all_related_many_to_many_objects():
    if r.field is not f:
        if r.get_accessor_name() == rel_name:
            e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
        if r.get_accessor_name() == rel_query_name:
            e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))

For us r.get_accessor_name() == rel_name is True, as both are "Patient".

UPDATE 2:

When we add an app that register a model. Any model, the problem doesn't appear anymore. So much for the unregister theory...

We at two symmetrical errors (both sides of the relation). Removing related_name suppress one of the errors 0_o

A: 

Found the solution

Adding a generic relation on put a reference in the model class _meta.local_many_to_many attribute which is a list. Django check against that but provides no way to get rid of it. Fix is:

    # remove remaining reference to the generic relation
    for field in model_cls._meta.local_many_to_many:
        if field.name == 'eav_value': # your related name
            model_cls._meta.local_many_to_many.remove(field)
            break
e-satis