2010-11-04 15 views
18

Usemos el ejemplo clásico de amigos.¿Cómo modelo una relación simétrica con django?

class Friendship(models.Model): 
    user1 = models.ForeignKey(User, related_name='friends1') 
    user2 = models.ForeignKey(User, related_name='friends2') 
    handshakes = models.PositiveIntegerField() 
    hugs = models.PositiveIntegerField() 
    # other silly data 

Dos amigos en una amistad (usuario1 y usuario2) deben ser completamente iguales. Debería poder decir que (usuario1, usuario2) son únicos_juntos y no tiene que preocuparse (usuario2, usuario1) por aparecer accidentalmente. Debería poder obtener fácilmente todos los amigos de un usuario determinado, pero en su lugar tendría que escribir un administrador personalizado o crear alguna otra forma de obtener todas las Amistades donde ese usuario es usuario1 en la relación, y todas las Amistades donde ese usuario es usuario2.

Estoy considerando intentar escribir mi propia SymmetricKey. Alguien por favor detenerme.

+10

1 para "por favor me deje" –

+0

Una buena lectura sobre este tema: https://charlesleifer.com/ blog/self-referencing-many-many-through/ – Anto

Respuesta

7

Eche un vistazo a la opción symmetrical de ManyToManyField en the docs - parece que puede hacer lo que quiera.

por la forma específica que lo estás haciendo, me gustaría hacer algo como

class LameUserExtension(User): 
    friends = ManyToManyField("self", through=Friendship) 

class Friendship(models.Model): 
    # the stuff you had here 
+0

Realmente había considerado esto, pero no sonaba tan bien como lo hace ahora. Realmente no quería usar varios modelos para que esto ocurra, pero supongo que no es una mala solución. –

+0

Oh, también va a cambiar mi título para decir simétrico ya que eso es lo que quise decir, y no reflexivo. –

+0

Resulta que esto no funcionará. De los documentos: "Al definir una relación de muchos a muchos desde un modelo a sí mismo, utilizando un modelo de intermediario, debe usar simétrico = Falso" –

2

me encontré con un buen artículo discutiendo que hace algún tiempo, los conceptos básicos son los siguientes:

class Person(models.Model): 
    name = models.CharField(max_length=100) 
    relationships = models.ManyToManyField('self', through='Relationship', 
              symmetrical=False, 
              related_name='related_to+') 

RELATIONSHIP_FOLLOWING = 1 
RELATIONSHIP_BLOCKED = 2 
RELATIONSHIP_STATUSES = (
    (RELATIONSHIP_FOLLOWING, 'Following'), 
    (RELATIONSHIP_BLOCKED, 'Blocked'), 
) 

class Relationship(models.Model): 
    from_person = models.ForeignKey(Person, related_name='from_people') 
    to_person = models.ForeignKey(Person, related_name='to_people') 
    status = models.IntegerField(choices=RELATIONSHIP_STATUSES) 

Tenga en cuenta el signo más al final de related_name. Esto le indica a Django que la relación inversa no debe exponerse. Dado que las relaciones son simétricas, este es el comportamiento deseado, después de todo, si soy amigo de la persona A, entonces la persona A es amiga mía. Django no creará las relaciones simétricas para usted, así que un poco necesita conseguir añadido a los métodos add_relationship y remove_relationship para manejar de forma explícita el otro lado de la relación:

def add_relationship(self, person, status, symm=True): 
    relationship, created = Relationship.objects.get_or_create(
     from_person=self, 
     to_person=person, 
     status=status) 
    if symm: 
     # avoid recursion by passing `symm=False` 
     person.add_relationship(self, status, False) 
    return relationship 

def remove_relationship(self, person, status, symm=True): 
    Relationship.objects.filter(
     from_person=self, 
     to_person=person, 
     status=status).delete() 
    if symm: 
     # avoid recursion by passing `symm=False` 
     person.remove_relationship(self, status, False) 

Ahora, cada vez que creamos una relación que va uno manera, su complemento es creado (o eliminado). Dado que las relaciones van en ambas direcciones, simplemente podemos utilizar:

def get_relationships(self, status): 
    return self.relationships.filter(
     to_people__status=status, 
     to_people__from_person=self) 

Fuente: Self-referencing many-to-many through

Cuestiones relacionadas