【问题标题】:Django ManyToMany relation to 'self' without backward relationsDjango ManyToMany 与“self”的关系没有后向关系
【发布时间】:2013-11-07 13:54:03
【问题描述】:

这里有几个关于后向关系的问题,但我要么太笨,无法理解它们,要么认为它们不适合我的情况。

我有模特

class MyModel(models.Model)
    stuff = models.ManyToManyField('self', related_name = 'combined+')

我创建了表单,我将存储的信息组合在一起。它像这样在数据库中存储对象关系:

表:

id:from_stuff_id:to_stuff_id
1:original_object_id:first_related_object
2:original_object_id:second_related_object
3:first_related_object:original_object_id
4:second_related_object:original_object_id

所以当我显示对象 first_related_object 并检查与

myobject.stuff.all()

然后我得到“original_object”。但我不需要它。我希望它不会显示出那样的向后关系。

编辑1

所以我很讨厌解释自己。

也许这段代码能更好地说明我想要什么。

myobjectone = MyModel.objects.get(pk = 1)
myobjecttwo = MyModel.objects.get(pk = 2)
myobjectthree = MyModel.objects.get(pk = 3)
myobjectone.stuff.add(myobjecttwo)
myobjectone.stuff.add(myobjectthree)
myobjectone.stuff.all()
[myobjecttwo, myobjectthree] <-- this i want
myobjecttwo.stuff.all()
[myobjectone]<-- this i do not want
myobjectthree.stuff.all()
[myobjectone]<-- this i do not want

现在唯一的问题是 - 如果我不想要它们产生的结果,我是否应该使用 stuff.all() 并且应该编写我自己的管理器/方法来获取排除后向关系的对象列表。

/edit1

编辑2 回应亨利·弗洛伦斯:

好的 - 我确实用空基对其进行了测试,看起来对称 = False 确实存在数据库级别差异。我认为。

我创建了对称 = False 的空表,然后创建添加关系并没有产生反向关系。如果我创建了没有对称的空表 = False。然后他们是。设置symmetric = False 在创建表之后没有任何区别。所以我想差异在数据库级别。

/edit2 那么我应该在这里做什么呢?

写我自己的经理还是什么的?

艾伦

【问题讨论】:

  • 你的意思是从结果中排除当前对象,比如myobject.stuff.exclude(id=self.id)
  • 不。我的意思是在使用 myobject.stuff.all() 查询时从结果中排除表中的第 3 行和第 4 行,myobject 与 first_related_object 相比。我不希望 original_object 从其他对象 .all() 查询中显示。
  • 我不确定我是否理解这个问题,但我认为您可能正在寻找symmetrical 选项。尝试设置symmetrical=False
  • 是的,我知道 - 我不擅长解释。 Symmetrical 可能会做一些工作,但我没有看到想要的结果。

标签: django orm django-related-manager


【解决方案1】:

我对这个问题的理解是,多对多关系应该是一种方式,如果以下情况属实:

             --------------           ----------------
             | mymodelone |---------->|  mymodeltwo  |
             --------------     |     ----------------
                                |
                                |     ----------------
                                ----->| mymodelthree |
                                      ----------------

那么在另一个方向上应该没有隐含的关系:

             --------------           ----------------
             | mymodelone |<-----/----|  mymodeltwo  |
             --------------           ----------------

             --------------           ----------------
             | mymodelone |<-----/----| mymodelthree |
             --------------           ----------------

ManyToMany 字段有一个symmetrical 属性,默认为True,请参阅:here

创建一个新应用来演示非对称多对多字段:

创建一个新应用:

$ python ./manage.py startapp stuff

settings.py添加东西应用:

...
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'stuff'
)
....

编辑`stuff/models.py:

from django.db import models

class MyModel(models.Model):
    stuff = models.ManyToManyField('self', related_name = 'combined+', symmetrical=False, blank = True, null = True, verbose_name = "description")

    def __unicode__(self):
        return "MyModel%i" % self.id

同步数据库:

$ python ./manage.py syncdb
Creating tables ...
Creating table stuff_mymodel_stuff
Creating table stuff_mymodel
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

然后在 django shell 中测试:

$ python ./manage.py shell
>>> from stuff.models import MyModel
>>> MyModel().save()
>>> MyModel().save()
>>> MyModel().save()
>>> MyModel.objects.all()
[<MyModel: MyModel1>, <MyModel: MyModel2>, <MyModel: MyModel3>]
>>> m1 = MyModel.objects.get(pk=1)
>>> m2 = MyModel.objects.get(pk=2)
>>> m3 = MyModel.objects.get(pk=3)
>>> m1.stuff.all()
[]
>>> m1.stuff.add(m2)
>>> m1.stuff.add(m3)
>>> m1.stuff.all()
[<MyModel: MyModel2>, <MyModel: MyModel3>]
>>> m2.stuff.all()
[]
>>> m3.stuff.all()
[]
>>> 

编辑 - 现有模型上的多对多关系

ManyToManyField 的对称性是在将模型写入数据库时​​创建的,而不是在读取它们时创建的。如果我们将模型更改为:

from django.db import models

class MyModel(models.Model):
    stuff = models.ManyToManyField('self', related_name = 'combined+')

def __unicode__(self):
    return "MyModel%i" % self.id

创建新的MyModel 实例:

>>> MyModel().save()
>>> MyModel().save()
>>> MyModel.objects.all()
[<MyModel: MyModel1>, <MyModel: MyModel2>, <MyModel: MyModel3>, <MyModel: MyModel4>, <MyModel: MyModel5>]
>>> m4 = MyModel.objects.get(pk=4)
>>> m5 = MyModel.objects.get(pk=5)
>>> m4.stuff.add(m5)
>>> m4.stuff.all()
[<MyModel: MyModel5>]
>>> m5.stuff.all()
[<MyModel: MyModel4>]

正如预期的那样,ManyToManyField 正在创建对称关系。如果我们然后将ManyToManyField 设置为symmetrical = False

>>> from stuff.models import MyModel
>>> MyModel().save()
>>> MyModel().save()
>>> MyModel.objects.all()
[<MyModel: MyModel1>, <MyModel: MyModel2>, <MyModel: MyModel3>, <MyModel: MyModel4>, <MyModel: MyModel5>, <MyModel: MyModel6>, <MyModel: MyModel7>]
>>> m6 = MyModel.objects.get(pk=6)
>>> m7 = MyModel.objects.get(pk=7)
>>> m6.stuff.all()
[]
>>> m7.stuff.all()
[]
>>> m6.stuff.add(m7)
>>> m6.stuff.all()
[<MyModel: MyModel7>]
>>> m7.stuff.all()
[]
>>> m5 = MyModel.objects.get(pk=5)
>>> m4 = MyModel.objects.get(pk=4)
>>> m4.stuff.all()
[<MyModel: MyModel5>]
>>> m5.stuff.all()
[<MyModel: MyModel4>]

可以看出m6m7 之间的新ManyToMany 关系不是对称的,但是现有的m4m5 之间的关系仍然是对称的,就像创建这些对象时所述的模型一样.

编辑 - 具有对称外键的附加数据库约束

对于这个答案的长度向读者道歉,我们似乎正在深入探索这个问题。

在 sql 中,多对多关系是通过创建一个表来建模的,该表包含该关系唯一的所有信息——通常只是两个表的主键值。

所以对于我们的MyModel,django 创建了两个表:

             -----------------           -----------------------
             | stuff_mymodel |---------->| stuff_mymodel_stuff |
             -----------------           -----------------------
                      ^                              |
                      |                              |
                      --------------------------------

图中显示的链接由架构中的主键或 id 值表示:

mysql> describe stuff_mymodel;
+-------+---------+------+-----+---------+----------------+
| Field | Type    | Null | Key | Default | Extra          |
+-------+---------+------+-----+---------+----------------+
| id    | int(11) | NO   | PRI | NULL    | auto_increment |
+-------+---------+------+-----+---------+----------------+
1 row in set (0.00 sec)

mysql> describe stuff_mymodel_stuff;
+-----------------+---------+------+-----+---------+----------------+
| Field           | Type    | Null | Key | Default | Extra          |
+-----------------+---------+------+-----+---------+----------------+
| id              | int(11) | NO   | PRI | NULL    | auto_increment |
| from_mymodel_id | int(11) | NO   | MUL | NULL    |                |
| to_mymodel_id   | int(11) | NO   | MUL | NULL    |                |
+-----------------+---------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

并显示为 Django manage.py 脚本的输出:

$ python ./manage.py sql stuff
    BEGIN;
CREATE TABLE `stuff_mymodel_stuff` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `from_mymodel_id` integer NOT NULL,
    `to_mymodel_id` integer NOT NULL,
    UNIQUE (`from_mymodel_id`, `to_mymodel_id`)
)
;
CREATE TABLE `stuff_mymodel` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY
)
;
ALTER TABLE `stuff_mymodel_stuff` ADD CONSTRAINT `from_mymodel_id_refs_id_7fa00238` FOREIGN KEY (`from_mymodel_id`) REFERENCES `stuff_mymodel` (`id`);
ALTER TABLE `stuff_mymodel_stuff` ADD CONSTRAINT `to_mymodel_id_refs_id_7fa00238` FOREIGN KEY (`to_mymodel_id`) REFERENCES `stuff_mymodel` (`id`);
COMMIT;

无论 django ManyToManyField 是否对称,此 sql 都是相同的。唯一的区别是stuff_mymodel_stuff 表中创建的行数:

mysql> select * from stuff_mymodel_stuff;
+----+-----------------+---------------+
| id | from_mymodel_id | to_mymodel_id |
+----+-----------------+---------------+
|  1 |               1 |             2 |
|  2 |               1 |             3 |
|  3 |               4 |             5 |
|  4 |               5 |             4 |
|  5 |               6 |             7 |
+----+-----------------+---------------+
5 rows in set (0.00 sec)

链接m4 -&gt; m5 是对称的,其他的不是。如果symmetricalTrue,则在 Django 源代码中挖掘我们可以找到负责在 sql 中创建“镜像”条目的代码:

    # If the ManyToMany relation has an intermediary model,
    # the add and remove methods do not exist.
    if rel.through._meta.auto_created:
        def add(self, *objs):
            self._add_items(self.source_field_name, self.target_field_name, *objs)

            # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
            if self.symmetrical:
                self._add_items(self.target_field_name, self.source_field_name, *objs)
        add.alters_data = True

目前是 github 上的第 605 行:https://github.com/django/django/blob/master/django/db/models/fields/related.py

希望这能回答您的所有疑问。

【讨论】:

  • 是的,这就是我要找的东西,但这不是我看到的,当我做你描述的同样的事情时。我有这个字段声明如下: stuff = models.ManyToManyField('self', related_name = 'combined+',symmetric=False, blank = True, null = True, verbose_name = _(u"description"))
  • 为什么你的评论中verbose_name的字符串是这样写的?它给我一个错误。我必须创建并保留新的 MyModels 才能使上面的代码正常工作,您的数据库中是否有过时的版本?
  • verbose_name 只是来自真实模型,而不是我在这里用来说明问题的简单示例。是的-自从模型更改以来,我还没有接触过数据库,因为我在某处读到这些相关名称和对称更改无论如何都不需要接触数据库。我错了吗?
  • 事实上他们似乎没有命中数据库原因:python manage.py schemamigration myapp --auto 似乎没有任何改变。
  • 啊好的,我编辑了我的帖子以展示如何在新应用程序中创建模型并演示如何创建非对称关系,这在 Django 1.4-1.6 中绝对有效,我复制了 shell 输出从壳。或者,您能否发布/链接到您的更多代码,我很乐意对其进行调试。
猜你喜欢
  • 2012-07-08
  • 2016-09-08
  • 1970-01-01
  • 2012-03-10
  • 2012-02-27
  • 2010-11-16
  • 2013-04-08
  • 2017-05-31
  • 1970-01-01
相关资源
最近更新 更多