Many-to-many Relationship Between Django Apps Fails

56 views
Skip to first unread message

Paul Childs

unread,
Oct 21, 2014, 9:32:04 AM10/21/14
to django...@googlegroups.com

What's been done so far:

  • I was on Django 1.5 and upgraded to 1.6 (cannot go higher as we are on Python 2.6) and this did not solve the problem.
  • I have researched this issue to death and cannot seem to find a definite answer. Looking through the Django Project Bug Tracker, I have seen similar issues but none seem to fit my particular case
  • I have resolved the problem in the past using a raw SQL call to replace for example affpart.damage_types.all() with a custom function but this is starting to happen more frequently now and is becoming a real pain.

Description:

I have two Django apps under one project. One of the apps makes use of models in another app using a many-to-many relationship.

This has been working smoothly for months, and in fact it works fine on my production machine but fails on my development machine. The scenario lately has been that I am asked to add a new feature and when I start to work on it I get a FieldError in related code which I haven't even touched.

The offending line of code for this latest issue is: for dt in affpart.damage_types.all()

The error is:

Cannot resolve keyword u'affectedpart' into field. Choices are: cgs_damage_code, description, id, reg_exp, sti

The error occurs in the bowels of Django in the query.py module.

From a high-level, this error occurs when I am trying to use a Many-to-many between models in different Django apps. For example, an affected part can have more than one type of damage and a damage type can be found on different affected parts.

The two apps are: trending and sitar

sitar was built first and has the models that I want to use from trending.

In trending, my models.py file has an AffectedPart model something like this:

from sitar.models import (Part, DamageType, Aircraft)

class AffectedPart(models.Model):

.
.
.

    occurrence_date = models.DateField()
    partnumber = models.ForeignKey(Part)
    damage_types = models.ManyToManyField(DamageType, null=True, blank=True)
    repair_types = models.ManyToManyField(RepairType, null=True, blank=True)

.If anyone has a solution to this or knows of best practices for models in one application having many-to-many relationships with models in another application I would love to hear it.

Thanks

Collin Anderson

unread,
Oct 21, 2014, 11:52:45 AM10/21/14
to django...@googlegroups.com
Hi Paul,

Interesting. Your code should work fine. So if you run this code in the shell it gives a FieldError?

affpart.damage_types.all()

What do your sitar models look like? There should not be a ManyToManyField in the other direction.

Collin

Daniel Roseman

unread,
Oct 21, 2014, 11:58:44 AM10/21/14
to django...@googlegroups.com
There aren't any problems that I'm aware of with many-to-many fields in any recent version of Django, and the fact that some of them are in other apps should not make the slightest difference.

However in order for us to help you you're going to need to provide more information: in particular, you should show the exact template code you're using, the other model, and most importantly the full traceback for the error.
--
DR.

Paul Childs

unread,
Oct 21, 2014, 12:00:36 PM10/21/14
to django...@googlegroups.com
Hey Collin,

I had no problem in the shell...


In [4]: AffectedPart.objects.all().count()
Out[4]: 4090
        # pick a random AffectedPart
In [5]: affpart = AffectedPart.objects.all()[22]

In [6]: affpart.damage_types.all()
Out[6]: [<DamageType: Dent>]

I'm not sure why this doesn't work running under the server.

My models look like this:

from sitar.models import (Part, DamageType, Aircraft)

# this model is in trending.models
class AffectedPart(models.Model):
    res = models.ForeignKey(Res, null=True)
    arising = models.ForeignKey(Arising, null=True)

    aircraft = models.ForeignKey(Aircraft)
    # filled out automatically only if part to Damage/Repair type matching done
    maintenance_phase = models.CharField(max_length=10,
                                         choices=MAINTENANCE_PHASE_CHOICES)

    occurrence_date = models.DateField()
    partnumber = models.ForeignKey(Part)
    damage_types = models.ManyToManyField(DamageType, null=True, blank=True)
    repair_types = models.ManyToManyField(RepairType, null=True, blank=True)


    def __unicode__(self, ):
        if self.res:
            parent = self.res.number

        else:
            parent = str(self.arising)

        return '{0} - {1}'.format(self.partnumber.number, parent)

# The following models are in sitar.models    
class Part(models.Model):
    ''' This model is used to create pick-lists so the user can associate
        one or more applicable parts from a pre-defined list to
        a tracked item.

        It will also allow for regular CRUD functionality which is
        implemented by taking advantage of the Django admin interface. '''

    # Added to associate a zone with a part
    zones = models.ManyToManyField("Zone", null=True, blank=True)

    number = models.CharField(max_length=50, unique=True)
    description = models.CharField(max_length=100, blank=True)
    comments = models.TextField(blank=True)
    material = models.CharField(max_length=100, blank=True)

    class Meta:
        ''' Order by part number field (ascending) when presenting data '''
        ordering = ['number']


    def __unicode__(self):
        ''' Return unicode description of a part instance '''
        if self.description:
            return '%s -- %s' % (self.number, self.description)
        else:
            return self.number


    def get_encoded_part_number(self):
        '''
           This method will remove any '/' in part numbers and replace them
           with '~' so that they can be used in URLs.
        '''
        return self.number.replace('/','~')


class DamageType(models.Model):

    description = models.CharField(max_length=50, unique=True)
    # a regular expression to account for possible spelling mistakes when
    # querying the database
    reg_exp = models.CharField(max_length=50, blank=True)

    # Added to provide damage code for TRENDING
    cgs_damage_code = models.CharField(max_length=10, blank=True,
                                       verbose_name="CGS Damage Code")

    def __unicode__(self):
        ''' Return unicode representation of a DamageType instance. '''
        return self.description

    class Meta:
        ''' Order by description field (ascending) when presenting data '''
        ordering = ['description']

    def save(self):
        ''' Override the save method of the DamageType model in order to assign
            a regexp if one does not exist.'''

        # if the tracked item does not have a reg_exp just use
        # the description
        if not self.reg_exp:
            self.reg_exp = self.description

        super(DamageType,self).save()


/Paul

Paul Childs

unread,
Oct 21, 2014, 12:02:44 PM10/21/14
to django...@googlegroups.com
Here is the stack trace...

Environment:


Request Method: POST
Request URL: http://127.0.0.1:8000/trending/trend/

Django Version: 1.6
Python Version: 2.6.7
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'sitar')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "C:\virtual_env\sitar_env2\lib\site-packages\django\core\handlers\base.py" in get_response
  114.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\virtual_env\sitar_env2\lib\site-packages\django\contrib\auth\decorators.py" in _wrapped_view
  22.                 return view_func(request, *args, **kwargs)
File "C:\virtual_env\sitar_env2\cissimp\trending\views.py" in trend
  418.             list_result = utils.convert_queryset_to_lists(q_results, form)
File "C:\virtual_env\sitar_env2\cissimp\trending\utils.py" in convert_queryset_to_lists
  918.                            for dt in affpart.damage_types.all()]),
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\manager.py" in all
  133.         return self.get_queryset()
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\fields\related.py" in get_queryset
  539.                 return super(ManyRelatedManager, self).get_queryset().using(db)._next_is_sticky().filter(**self.core_filters)
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\query.py" in filter
  590.         return self._filter_or_exclude(False, *args, **kwargs)
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\query.py" in _filter_or_exclude
  608.             clone.query.add_q(Q(*args, **kwargs))
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\sql\query.py" in add_q
  1198.         clause = self._add_q(where_part, used_aliases)
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\sql\query.py" in _add_q
  1232.                     current_negated=current_negated)
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\sql\query.py" in build_filter
  1100.                     allow_explicit_fk=True)
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\sql\query.py" in setup_joins
  1351.             names, opts, allow_many, allow_explicit_fk)
File "C:\virtual_env\sitar_env2\lib\site-packages\django\db\models\sql\query.py" in names_to_path
  1274.                                      "Choices are: %s" % (name, ", ".join(available)))

Exception Type: FieldError at /trending/trend/
Exception Value: Cannot resolve keyword u'affectedpart' into field. Choices are: cgs_damage_code, description, id, reg_exp, sti

Collin Anderson

unread,
Oct 21, 2014, 12:10:28 PM10/21/14
to django...@googlegroups.com
Hi Paul,

This is great. The traceback helps. Could post more of your convert_queryset_to_lists. It looks like a list comprehension and there may be more complicated things going on there.

Thanks,
Collin

Collin Anderson

unread,
Oct 21, 2014, 12:12:33 PM10/21/14
to django...@googlegroups.com
Hi Paul,

Try putting 'trending' in INSTALLED_APPS.

Collin

Paul Childs

unread,
Oct 21, 2014, 12:30:23 PM10/21/14
to django...@googlegroups.com
I am SHOCKED. That was the problem. THANK YOU!!!!

I guess that leads to a couple of questions:

1. How did you know!?!
2.I have the same code in production and it was working with no problems. Any idea why?

Cheers!

Collin Anderson

unread,
Oct 21, 2014, 1:35:52 PM10/21/14
to django...@googlegroups.com
Hi Paul,

1. How did you know!?!
I hack... kidding :). I suspected something wasn't getting loaded, and the list of installed apps was in the traceback you posted.

2.I have the same code in production and it was working with no problems. Any idea why?
In development, most, if not all of your code is loaded and run on startup, and in production (at least before 1.7), things are really only loaded as needed. Basically with the app loading refactor and django.setup(), we're finally fixing problems like this.

Collin

Paul Childs

unread,
Oct 21, 2014, 1:38:50 PM10/21/14
to django...@googlegroups.com
Thanks man. This was a huge relief!

--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/4_Cdw5xpx-w/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/d1617a57-9a60-4727-a610-308e44789db1%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages