Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

adding the same object twice to a ManyToManyField

I have two django model classes:

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

What I want to do is the following:

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

I am guessing that add() uses a set semantic, but how can I circumvent that? I tried looking into custom managers, but I am not sure if this the right way (seems complicated)...

Thanks in advance!

like image 968
qollin Avatar asked Sep 13 '09 14:09

qollin


3 Answers

I think what you want is to use an intermediary model to form the M2M relationship using the through keyword argument in the ManyToManyField. Sort of like the first answer above, but more "Django-y".

class A(models.Model):
    name = models.CharField(max_length=200)

class B(models.Model):
    a = models.ManyToManyField(A, through='C')
    ...

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

When using the through keyword, the usual M2M manipulation methods are no longer available (this means add, create, remove, or assignment with = operator). Instead you must create the intermediary model itself, like so:

 >>> C.objects.create(a=a1, b=b)

However, you will still be able to use the usual querying operations on the model containing the ManyToManyField. In other words the following will still work:

 >>> b.a.filter(a=a1)

But maybe a better example is something like this:

>>> B.objects.filter(a__name='Test')

As long as the FK fields on the intermediary model are not designated as unique you will be able to create multiple instances with the same FKs. You can also attach additional information about the relationship by adding any other fields you like to C.

Intermediary models are documented here.

like image 58
Jesse L Avatar answered Oct 12 '22 21:10

Jesse L


Django uses a relational DB for its underlying storage, and that intrinsically DOES have "set semantics": no way to circumvent THAT. So if you want a "multi-set" of anything, you have to represent it with a numeric field that counts how many times each item "occurs". ManyToManyField doesn't do that -- so, instead, you'll need a separate Model subclass which explicitly indicates the A and the B it's relating, AND has an integer property to "count how many times".

like image 4
Alex Martelli Avatar answered Oct 12 '22 21:10

Alex Martelli


One way to do it:

class A(models.Model):
    ...

class B(models.Model):
    ...

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

Then:

>>> a1 = A()
>>> a2 = A()
>>> b = B()
>>> c1 = C(a=a1, b=b)
>>> c2 = C(a=a2, b=b)
>>> c3 = C(a=a1, b=b)

So, we simply have:

>>> assert C.objects.filter(b=b).count == 3
>>> for c in C.objects.filter(b=b):
...     # do something with c.a
like image 2
Skylar Saveland Avatar answered Oct 12 '22 21:10

Skylar Saveland