2009-09-13 15 views
5

Tengo dos clases de modelo de Django:añadiendo el mismo objeto dos veces a un ManyToManyField

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 

Lo que quiero hacer es la siguiente:

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 

Supongo que añadir() utiliza un establecer la semántica, pero ¿cómo puedo evitar eso? Traté de buscar administradores personalizados, pero no estoy seguro si esto es el camino correcto (parece complicado) ...

¡Gracias de antemano!

Respuesta

3

Creo que lo que desea es utilizar un modelo intermediario para formar la relación M2M utilizando el argumento de palabra clave through en ManyToManyField. Algo así como la primera respuesta anterior, pero más "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) 

Cuando se utiliza la palabra clave through, los métodos habituales de manipulación de M2M ya no están disponibles (esto significa add, create, remove o asignación con = operador). En su lugar se debe crear el modelo propio intermediario, así:

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

Sin embargo, usted todavía será capaz de utilizar las operaciones de consulta de habituales en el modelo que contiene el ManyToManyField. En otras palabras, el siguiente seguirá funcionando:

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

Pero tal vez un mejor ejemplo es algo como esto:

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

Mientras los campos FK en el modelo intermediario no se designan como unique se quiere ser capaz de crear instancias múltiples con los mismos FK. También puede adjuntar información adicional acerca de la relación agregando cualquier otro campo que le guste al C.

Los modelos intermediarios están documentados here.

4

Django usa una base de datos relacional para su almacenamiento subyacente, y que intrínsecamente tiene "establecer semántica": no hay manera de sortear ESO. Entonces, si quiere un "conjunto múltiple" de cualquier cosa, debe representarlo con un campo numérico que cuente cuántas veces "se produce" cada elemento. ManyToManyField no hace eso; por lo tanto, necesitará una subclase de modelo separada que indique explícitamente la A y la B relacionada, Y tiene una propiedad entera para "contar cuántas veces".

+1

Creo que eso no es del todo correcto. La relación de A a B se realiza a través de una tabla de relaciones compuesta por tres columnas: id, a_id y b_id. id es la clave principal de esa tabla, por lo que estaría bien tener tres filas en esa tabla donde dos tienen la misma entrada para a_id y b_id, respectivamente. Y esto es lo que quiero, pero no sé cómo decirle a Django que ... – qollin

+0

@qolin, la respuesta de sos-skil te ofrece una manera de hacer una tabla (no a través de muchos), mi idea es solo agregar un campo de conteo a esa tabla en lugar de confiar en los duplicados (es solo una forma más compacta) para implementar bolsas multisector, también conocidas como bolsas). –

+1

@qollin: no estoy seguro de que pueda confiar en que siempre haya una identificación en la tabla de relaciones, que puede ser un detalle de implementación. Y puede crear muchos a muchos usando una tabla "directa" que puede tener columnas adicionales, como el campo de recuento que sugirió Alex. –

0

Una forma de hacerlo:

class A(models.Model): 
    ... 

class B(models.Model): 
    ... 

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

continuación:

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

Por lo tanto, simplemente tenemos:

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

Lo sé, no es exactamente lo que estás buscando, pero una forma de hacer algo similar: / –

Cuestiones relacionadas