Django 应用程序之间的多对多关系失败

Posted

技术标签:

【中文标题】Django 应用程序之间的多对多关系失败【英文标题】:Many-to-many Relationship Between Django Apps Fails 【发布时间】:2014-12-16 17:49:53 【问题描述】:

到目前为止做了什么:

我使用 Django 1.5 并升级到 1.6(不能像使用 Python 2.6 那样升级),但这并没有解决问题。 我已经研究了这个问题,但似乎找不到明确的答案。查看 Django Project Bug Tracker,我看到了类似的问题,但似乎没有一个适合我的特殊情况 我过去曾使用原始 SQL 调用来解决此问题,例如用自定义函数替换 affpart.damage_types.all(),但现在这种情况开始变得更加频繁,并且变得非常痛苦。

说明:

我在一个项目下有两个 Django 应用程序。其中一个应用使用多对多关系在另一个应用中使用模型。

这几个月来一直运行顺利,实际上它在我的生产机器上运行良好,但在我的开发机器上却失败了。最近的情况是我被要求添加一个新功能,当我开始处理它时,我在相关代码中得到了一个FieldError,我什至没有接触过。

最新一期的违规代码行是: for dt in affpart.damage_types.all()

错误是:

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

错误发生在Django内部query.py模块中。

从高层次上看,当我尝试在不同 Django 应用程序中的模型之间使用多对多时会发生此错误。例如,一个受影响的部分可能有不止一种类型的损坏,并且可以在不同的受影响部分上找到一种损坏类型。

这两个应用是:trendingsitar

sitar 是最先构建的,并且具有我想从趋势中使用的模型。

trending 中,我的models.py 文件有一个类似这样的AffectedPart 模型:

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

# this model is in trendin.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()

堆栈跟踪

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

如果有人对此有解决方案或知道一个应用程序中模型与另一个应用程序中的模型具有多对多关系的最佳实践,我很想听听。

谢谢

【问题讨论】:

请包括AffectedPartDamageType 的整个模型类。 @schillingt -- 我也加入了 Part。 你添加的新功能是什么? 我可以评论的一件事是,您可以这样做damage_types = models.ManyToManyField('sitar.DamageType', null=True, blank=True)docs.djangoproject.com/en/dev/ref/models/fields/…,而不是从其他应用程序导入模型 你能包含堆栈跟踪吗?我不认为有问题的代码行会产生您给我们的错误。原因是它说affectedpart 不是一个字段,但我在给定代码的任何地方都找不到affectedpart。您尝试在过滤器中的某个位置使用affectedpart,但该模型上不存在。 【参考方案1】:

看起来像是您要呼叫的地方:

damage_type.affectedpart.all()

或者类似的东西。 要获得针对您的损坏类型的所有受影响部件,您需要执行以下操作:

damage_type.affectedpart_set.all()

您可以在documentation. 中阅读更多相关信息

如果这不是您的错误,请跟踪有问题的代码。

【讨论】:

谢谢,但这不是问题所在。 令我困惑的是,您说违规行是 affpart.damage_types.all() 但错误类似于:damage_type.affectedpart.all() 正好相反 这对我和你一样有意义。我只是跟着堆栈跟踪下来。第 918 行启动了导致 django.db.models.sql.query 模块中的错误的事件链【参考方案2】:

您是否将DamageType 关系从ForeignKey 更改为ManyToMany

我猜你这样做了,而 Django 从未创建过中间表,所以它试图使用 DamageType 表。这就是它寻找与AffectedPart 的关系的原因。

当您进行模型更改时,您应该使用 South 之类的东西进行迁移,因为更改为 ManyToMany 字段并不像听起来那么简单。

否则,请自行创建"through" 表。

【讨论】:

我从第一天起就一直建立这种关系。是的,我确实使用南方。过去我使用过中间表,但没有在应用程序之间使用“通过”。我会试一试。谢谢。 我明白了,我想不出任何其他的 atm。如果这不起作用,也许包括 /trending/trend/ 使用的视图...【参考方案3】:

发生错误是因为我忘记将"trending" 放入我的INSTALLED_APPS

由于它在生产环境中使用完全相同的设置文件,所以我从未想过要查看那里。

感谢 django-users@googlegroups.com 论坛上的 Collin Anderson。

Collin 感兴趣的一些 cmets:

    你怎么知道的!?! 我破解...开玩笑:)。我怀疑没有加载某些内容,已安装应用的列表在您发布的回溯中。

2.我在生产中使用了相同的代码,并且运行没有问题。知道为什么吗? 在开发中,大多数(如果不是全部)代码在启动时加载并运行,而在生产中(至少在 1.7 之前),实际上只是根据需要加载。基本上通过应用加载重构和 django.setup(),我们终于解决了这样的问题。

【讨论】:

以上是关于Django 应用程序之间的多对多关系失败的主要内容,如果未能解决你的问题,请参考以下文章

DJANGO:与附加字段的多对多关系

在 django 的多对多关系中添加额外的字段

Spring Boot中两个实体之间的多对多关系

如何在 Django 中创建和链接多对多表?

根据特定的多对多关系过滤 Django 查询集

核心数据:与状态的多对多关系