【问题标题】:Django - reverse query name clashDjango - 反向查询名称冲突
【发布时间】:2016-03-04 09:35:19
【问题描述】:

我在 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)

错误是:

“Supplier.default_tariff”的反向查询名称与字段冲突 名称“关税供应商”。 提示:重命名字段“关税供应商”,或添加/更改相关名称参数到字段定义 'Supplier.default_tariff'。

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    为避免这种情况,您可以使用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)
    

    从 cmets 编辑:

    当您设置 related_name="+" 时,Django 不会创建向后关系。在这种情况下,Tariff 与 Supplier 模型没有反向关系。

    【讨论】:

    • related_name="+" 是做什么的?除了修复错误,还会发生什么?什么停止工作?
    • @Toskan 它改变了你调用关系的方式。
    • 这是一个可怕的答案,没有说明错误发生的原因,也没有解释如何解决它。
    • @Silidrone 嗨,避免可怕的 cmets 并用缺失的信息补充答案。欢呼!
    • 是的,你是对的,我当时很沮丧,对不起。
    【解决方案2】:

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

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

    例子:

    from django.db import models
    
    class Blog(models.Model):
        name = models.CharField(max_length=100)
        def __str__(self):
            return self.name
    
    class Entry(models.Model):
        blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
        headline = models.CharField(max_length=100)
        def __str__(self):
            return self.headline
    

    追随“倒退”的关系

    一对多关系不以“前向”方式存在,因为它是“多方”持有密钥,但 Django 为关系的“另一方”创建 API 访问器——链接来自相关模型到定义关系的模型。例如,博客对象 b 可以通过 entry_set 属性访问所有相关条目对象的列表:b.entry_set.all()

    ...

    如果模型具有 ForeignKey,则外键模型的实例将有权访问返回第一个模型的所有实例的 Manager。默认情况下,这个 Manager 被命名为 FOO_set,其中 FOO 是源模型名称,小写。此管理器返回 QuerySet,可以按照上面“检索对象”部分的说明进行过滤和操作。

    关注documentation link 了解更多信息/示例。

    这是因为这些 API 访问器 'related_name'(用 related_name in Abstract Classes 补充此信息)被实现,以避免字段名和字段名之间的命名冲突

    1. accessor_name(这将是经理的名字), 和/或
    2. query_name(即型号名称))

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

    This 是您的错误来源:

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

    This 是 accessor_name 的诞生方式

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

    这是查询名称(也在related.py中)

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

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

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

    以及如何解决:

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

    希望对您有所帮助。我只是在处理一个相关问题))

    【讨论】:

      【解决方案3】:

      尝试使用以下解决方案。它适用于 django==3。

      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)s_tarriff")
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-05
        • 2023-03-21
        • 2023-03-31
        • 1970-01-01
        • 1970-01-01
        • 2017-10-31
        • 2023-02-10
        • 2014-04-27
        相关资源
        最近更新 更多