Django-反向查询名称冲突

Yun*_*nti 8 django django-models

我在Django中放置了一个圆形外键,这导致名称冲突。我不明白为什么会发生冲突或如何最好地补救。(这是我第一次在模型中使用循环引用-因此我不确定这是否可能是问题的根本原因?)

对于以下内容:

class Supplier(models.Model):
    unique_id = models.IntegerField(unique=True)
    name = models.CharField(max_length=255, unique=True)
    rating = models.FloatField(null=True)
    last_updated = models.DateTimeField(auto_now=True)
    default_tariff = models.ForeignKey('Tariff')

class Tariff(models.Model):
    name = models.CharField(max_length=255)
    supplier = models.ForeignKey(Supplier)
    end_date = models.DateField(null=True, blank=True)
    payment_method = models.ManyToManyField(PaymentMethod) 
    region = models.ManyToManyField(Region)
Run Code Online (Sandbox Code Playgroud)

错误是:

“ Supplier.default_tariff”的反向查询名称与字段名称“ Tariff.supplier”冲突。提示:重命名字段“ Tariff.supplier”,或在字段“ Supplier.default_tariff”的定义中添加/更改related_name参数。

Goc*_*cht 10

为了避免这种情况,您可以使用related_name

class Supplier(models.Model):
    unique_id = models.IntegerField(unique=True)
    name = models.CharField(max_length=255, unique=True)
    rating = models.FloatField(null=True)
    last_updated = models.DateTimeField(auto_now=True)
    default_tariff = models.ForeignKey('Tariff', related_name='+')

class Tariff(models.Model):
    name = models.CharField(max_length=255)
    supplier = models.ForeignKey(Supplier)
    end_date = models.DateField(null=True, blank=True)
    payment_method = models.ManyToManyField(PaymentMethod) 
    region = models.ManyToManyField(Region)
Run Code Online (Sandbox Code Playgroud)

  • `related_name="+"` 有什么作用?除了修复错误之外,还会发生什么?什么停止工作? (3认同)
  • 谢谢@Gocht,你能举个例子吗? (2认同)
  • 这是一个可怕的答案,没有解决错误发生的原因,也没有解释如何解决它。 (2认同)
  • @Silidrone嗨,避免可怕的评论并用缺失的信息补充答案。樱桃! (2认同)

mgo*_*etz 5

错误消息本身是一个非常正确的语句(您省略了输出中给出的错误代码,我认为它是“E303”),但我们需要了解它的实际含义。

\n

我不会问为什么在这种情况下不使用多对多关系,让我们关注“向后”对象关系如何工作:

\n

例子:

\n
from django.db import models\n\nclass Blog(models.Model):\n    name = models.CharField(max_length=100)\n    def __str__(self):\n        return self.name\n\nclass Entry(models.Model):\n    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)\n    headline = models.CharField(max_length=100)\n    def __str__(self):\n        return self.headline\n
Run Code Online (Sandbox Code Playgroud)\n

以下关系 \xe2\x80\x9cbackward\xe2\x80\x9d

\n

一对多关系并不以“前向”方式存在,因为它是“多方”,持有密钥,但 Django 为 \xe2\x80\x9cother\xe2\x80\x9d 方创建 API 访问器关系 \xe2\x80\x93 从相关模型到定义该关系的模型的链接。例如,Blog 对象 b 可以通过 entry_set 属性访问所有相关 Entry 对象的列表:b.entry_set.all()

\n

...

\n

如果模型具有外键,则外键模型的实例将有权访问返回第一个模型的所有实例的管理器。默认情况下,该管理器名为 FOO_set,其中 FOO 是源模型名称,小写。该管理器返回查询集,可以按照上面\xe2\x80\x9c检索对象\xe2\x80\x9d部分中的描述对其进行过滤和操作。

\n

请点击文档链接获取更多信息/示例。

\n

这是因为这些 API 访问器“lated_name”(用抽象类中的 related_name来补充此信息)被实现,以避免字段名和字段名之间的命名冲突。

\n
    \n
  1. accessor_name(这将是管理器的名称),\n和/或
  2. \n
  3. query_name(模型名称))
  4. \n
\n

让我们看一下 Django 代码以便更好地理解:

\n

就是你的错误的来源:

\n
def _check_clashes(self):\n    """Check accessor and reverse query name clashes."""\n    from django.db.models.base import ModelBase\n\n    errors = []\n    opts = self.model._meta\n\n    # `f.remote_field.model` may be a string instead of a model. Skip if model name is\n    # not resolved.\n    if not isinstance(self.remote_field.model, ModelBase):\n        return []\n\n    # Consider that we are checking field `Model.foreign` and the models\n    # are:\n    #\n    #     class Target(models.Model):\n    #         model = models.IntegerField()\n    #         model_set = models.IntegerField()\n    #\n    #     class Model(models.Model):\n    #         foreign = models.ForeignKey(Target)\n    #         m2m = models.ManyToManyField(Target)\n\n    # rel_opts.object_name == "Target"\n    rel_opts = self.remote_field.model._meta\n    # If the field doesn\'t install a backward relation on the target model\n    # (so `is_hidden` returns True), then there are no clashes to check\n    # and we can skip these fields.\n    rel_is_hidden = self.remote_field.is_hidden()\n    rel_name = self.remote_field.get_accessor_name()  # i. e. "model_set"\n    rel_query_name = self.related_query_name()  # i. e. "model"\n    # i.e. "app_label.Model.field".\n    field_name = \'%s.%s\' % (opts.label, self.name)\n\n    # Check clashes between accessor or reverse query name of `field`\n    # and any other field name -- i.e. accessor for Model.foreign is\n    # model_set and it clashes with Target.model_set.\n    potential_clashes = rel_opts.fields + rel_opts.many_to_many\n    for clash_field in potential_clashes:\n        # i.e. "app_label.Target.model_set".\n        clash_name = \'%s.%s\' % (rel_opts.label, clash_field.name)\n        if not rel_is_hidden and clash_field.name == rel_name:\n            errors.append(\n                checks.Error(\n                    "Reverse accessor for \'%s\' clashes with field name \'%s\'." % (field_name, clash_name),\n                    hint=("Rename field \'%s\', or add/change a related_name "\n                          "argument to the definition for field \'%s\'.") % (clash_name, field_name),\n                    obj=self,\n                    id=\'fields.E302\',\n                )\n            )\n\n        if clash_field.name == rel_query_name:\n            errors.append(\n                checks.Error(\n                    "Reverse query name for \'%s\' clashes with field name \'%s\'." % (field_name, clash_name),\n                    hint=("Rename field \'%s\', or add/change a related_name "\n                          "argument to the definition for field \'%s\'.") % (clash_name, field_name),\n                    obj=self,\n                    id=\'fields.E303\',\n                )\n            )\n\n    # Check clashes between accessors/reverse query names of `field` and\n    # any other field accessor -- i. e. Model.foreign accessor clashes with\n    # Model.m2m accessor.\n    potential_clashes = (r for r in rel_opts.related_objects if r.field is not self)\n    for clash_field in potential_clashes:\n        # i.e. "app_label.Model.m2m".\n        clash_name = \'%s.%s\' % (\n            clash_field.related_model._meta.label,\n            clash_field.field.name,\n        )\n        if not rel_is_hidden and clash_field.get_accessor_name() == rel_name:\n            errors.append(\n                checks.Error(\n                    "Reverse accessor for \'%s\' clashes with reverse accessor for \'%s\'." % (field_name, clash_name),\n                    hint=("Add or change a related_name argument "\n                          "to the definition for \'%s\' or \'%s\'.") % (field_name, clash_name),\n                    obj=self,\n                    id=\'fields.E304\',\n                )\n            )\n\n        if clash_field.get_accessor_name() == rel_query_name:\n            errors.append(\n                checks.Error(\n                    "Reverse query name for \'%s\' clashes with reverse query name for \'%s\'."\n                    % (field_name, clash_name),\n                    hint=("Add or change a related_name argument "\n                          "to the definition for \'%s\' or \'%s\'.") % (field_name, clash_name),\n                    obj=self,\n                    id=\'fields.E305\',\n                )\n            )\n\n    return errors\n
Run Code Online (Sandbox Code Playgroud)\n

就是 accessor_name 的诞生方式

\n
def get_accessor_name(self, model=None):\n    # This method encapsulates the logic that decides what name to give an\n    # accessor descriptor that retrieves related many-to-one or\n    # many-to-many objects. It uses the lowercased object_name + "_set",\n    # but this can be overridden with the "related_name" option. Due to\n    # backwards compatibility ModelForms need to be able to provide an\n    # alternate model. See BaseInlineFormSet.get_default_prefix().\n    opts = model._meta if model else self.related_model._meta\n    model = model or self.related_model\n    if self.multiple:\n        # If this is a symmetrical m2m relation on self, there is no reverse accessor.\n        if self.symmetrical and model == self.model:\n            return None\n    if self.related_name:\n        return self.related_name\n    return opts.model_name + (\'_set\' if self.multiple else \'\')\n
Run Code Online (Sandbox Code Playgroud)\n

这是 query_name (也在 related.py 中)

\n
def related_query_name(self):\n    """\n    Define the name that can be used to identify this related object in a\n    table-spanning query.\n    """\n    return self.remote_field.related_query_name or self.remote_field.related_name or self.opts.model_name\n
Run Code Online (Sandbox Code Playgroud)\n

现在,您知道了命名是如何完成的以及反向关系是什么,您就会明白为什么

\n
\n

\'Supplier.default_tariff\' 的反向查询名称与字段名称 \'Tariff.supplier\' 冲突。

\n
\n

以及如何解决:

\n
\n

提示:重命名字段“Tariff.supplier”,或添加/更改字段“Supplier.default_tariff”定义中的 related_name 参数。

\n
\n

希望有帮助。我刚刚正在研究相关问题))

\n