Python 将同一对象添加到ManyToManyField两次

Python 将同一对象添加到ManyToManyField两次,python,django,django-models,Python,Django,Django Models,我有两个django模型类: class A(models.Model): name = models.CharField(max_length = 128) #irrelevant class B(models.Model): a = models.ManyToManyField(A) name = models.CharField(max_length = 128) #irrelevant 我想做的是: a1 = A() a2 = A() b =

我有两个django模型类:

class A(models.Model):
     name = models.CharField(max_length = 128)    #irrelevant

class B(models.Model):
     a = models.ManyToManyField(A)
     name = models.CharField(max_length = 128)    #irrelevant
我想做的是:

a1 = A()
a2 = A()
b = B()

b.a.add(a1)
b.a.add(a1)    #I want to have a1 twice
b.a.add(a2)

assert len(b.a.all()) == 3 #this fails; the length of all() is 2
我猜add()使用了一组语义,但我如何才能避免这种情况呢?我试着寻找定制经理,但我不确定这是否是正确的方法(似乎很复杂)


提前谢谢

Django使用关系数据库作为其底层存储,这在本质上确实具有“设置语义”:无法绕过这一点。因此,如果你想要一个“多集”的任何东西,你必须用一个数字字段来表示它,该字段计算每个项目“出现”的次数。ManyToManyField不这样做——因此,相反,您需要一个单独的模型子类,该子类显式指示它所关联的a和B,并具有一个整数属性来“计算多少次”。

一种方法:

class A(models.Model):
    ...

class B(models.Model):
    ...

class C(models.Model):
    a = models.ForeignKey(A)
    b = models.ForeignKey(B)
然后:

因此,我们只有:

>>> assert C.objects.filter(b=b).count == 3
>>> for c in C.objects.filter(b=b):
...     # do something with c.a

我认为您想要的是使用中介模型,使用ManyToManyField中的
关键字参数来形成M2M关系。有点像上面的第一个答案,但更多的是“Django-y”

当使用
关键字时,通常的M2M操作方法不再可用(这意味着
添加
创建
删除
,或使用
=
运算符赋值)。相反,您必须创建中介模型本身,如下所示:

 >>> C.objects.create(a=a1, b=b)
但是,您仍然可以对包含
ManyToManyField
的模型使用常规查询操作。换言之,以下各项仍然有效:

 >>> b.a.filter(a=a1)
但也许更好的例子是这样的:

>>> B.objects.filter(a__name='Test')
只要中介模型上的FK字段未指定为
唯一
,您就可以使用相同的FK创建多个实例。您还可以通过添加希望添加到
C
的任何其他字段来附加有关关系的附加信息


中间模型被记录下来

我认为这不完全正确。从A到B的关系是通过一个由三列组成的关系表实现的:id、A_id和B_id。id是该表的主键,因此在该表中有三行是可以的,其中两行分别具有相同的A_id和B_id条目。这就是我想要的,但我不知道如何告诉Django…@qolin,sos skil的回答为您提供了一种制作这样一个表的方法(当然不是通过manytomany)——我的想法只是在该表中添加一个计数字段,而不是依赖重复项(这只是实现多集的一种更紧凑的方法)@qollin:我不确定你是否可以相信关系表中总是有一个id——这可能是一个实现细节。您可以使用“through”表构建多对多表,该表可以包含其他列,如Alex建议的count字段。我知道,这并不完全是您想要的,而是一种类似的方法:/
 >>> b.a.filter(a=a1)
>>> B.objects.filter(a__name='Test')