This week I was doing some refactoring, and started getting the following exception in Django's admin site. This was under Django 1.1.1, and Python 2.6.4.

Traceback:
File "/usr/lib/pymodules/python2.6/django/core/handlers/base.py" in get_response
  92.                 response = callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/pymodules/python2.6/django/contrib/admin/sites.py" in root
  490.                 return self.model_page(request, *url.split('/', 2))
File "/usr/lib/pymodules/python2.6/django/views/decorators/cache.py" in _wrapped_view_func
  44.         response = view_func(request, *args, **kwargs)
File "/usr/lib/pymodules/python2.6/django/contrib/admin/sites.py" in model_page
  509.         return admin_obj(request, rest_of_url)
File "/usr/lib/pymodules/python2.6/django/contrib/admin/options.py" in __call__
  1098.             return self.change_view(request, unquote(url))
File "/usr/lib/pymodules/python2.6/django/db/transaction.py" in _commit_on_success
  240.                 res = func(*args, **kw)
File "/usr/lib/pymodules/python2.6/django/contrib/admin/options.py" in change_view
  840.             form = ModelForm(instance=obj)
File "/home/chase/bullhorn/branches/powerfill/django/powerfill/search/admin.py" in __init__
  113.         super(CompanyAdminForm, self).__init__(*args, **kwargs)
File "/usr/lib/pymodules/python2.6/django/forms/models.py" in __init__
  222.             object_data = model_to_dict(instance, opts.fields, opts.exclude)
File "/usr/lib/pymodules/python2.6/django/forms/models.py" in model_to_dict
  140.                 data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
File "/usr/lib/pymodules/python2.6/django/db/models/fields/related.py" in value_from_object
  964.         return getattr(obj, self.attname).all()
File "/usr/lib/pymodules/python2.6/django/db/models/manager.py" in all
  105.         return self.get_query_set()
File "/usr/lib/pymodules/python2.6/django/db/models/fields/related.py" in get_query_set
  424.             return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters))
File "/usr/lib/pymodules/python2.6/django/db/models/query.py" in filter
  498.         return self._filter_or_exclude(False, *args, **kwargs)
File "/usr/lib/pymodules/python2.6/django/db/models/query.py" in _filter_or_exclude
  516.             clone.query.add_q(Q(*args, **kwargs))
File "/usr/lib/pymodules/python2.6/django/db/models/sql/query.py" in add_q
  1675.                             can_reuse=used_aliases)
File "/usr/lib/pymodules/python2.6/django/db/models/sql/query.py" in add_filter
  1569.                     negate=negate, process_extras=process_extras)
File "/usr/lib/pymodules/python2.6/django/db/models/sql/query.py" in setup_joins
  1737.                             "Choices are: %s" % (name, ", ".join(names)))

Exception Type: FieldError at /admin/search/company/2273/
Exception Value: Cannot resolve keyword 'company' into field.

Having made quite a number of changes before noticing this, it took some time to track down. It turned out that it started happening when I moved an import. Eventually, I found Django Ticket #1796. While that ticket is marked as "Fixed", it does not actually appear to be fixed in all cases.

The problem is deep in the Django stack, and involves class loading at the Python level as well. It's in a piece of the Django code that is only executed for ManyToMany relationships. Read the ticket if you're interested in the details.

In my case, the ManyToMany relationship in question was a field on a Company model which referenced a Django User model:

class Company(models.Model):

    ...

    connection_users = models.ManyToManyField(
        User,
        symmetrical=False,
        blank=True,
        null=True
        )

I also have a UserProfile model which extends the base User model:

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    company = models.ForeignKey(Company)
    phone = PhoneNumberField()

Essentially, the django.contrib.auth.models.User model is necessarily loaded first, then my related UserProfile model is loaded before the Company model due to Python class loading behavior. When Company finally loads, the Django bug kicks in and it confuses the company field on UserProfile with a symmetric version of the ManyToMany relationship connection_users on Company.

After much fiddling, the fix was to force the class loading order to load Company before UserProfile. A good spot to do this is at the top of the models module.

from company import Company, UserProfile