2010-05-28 3 views
2

J'ai une application qui essentiellement un système de conversation (un peu comme reddit).Conversation multi-thread dans django (comme Reddit)

Lorsqu'un poste peut avoir plusieurs réponses, et une réponse et ont multiplications, et une réponse à une réponse peut avoir plusieurs réponses (etc.)

J'ai fait le modèle comme celui-ci:

class Discussion(models.Model): 
    message = models.TextField() 
    replies = models.ManyToManyField('self') 

et la vue:

discussions = Discussions.objects.all() 

et le modèle ressemble à ceci:

{% for discussion in discussions %} 
    {{ discussion.message }} 
{% endfor %} 

Comment est-ce que je ferais un système où je pourrais produire toutes les réponses comme ceci?

discussion 
    reply 
     reply 
    reply 
     reply 
      reply 
       reply 

Qui descendrait aussi loin qu'il le faudrait pour s'assurer que toutes les réponses sont listées.

Répondre

4

À moins qu'une réponse puisse être une réponse à plusieurs messages, un ManyToManyField n'est pas ce que vous voulez. Vous avez juste besoin d'un ForeignKey:

class Discussion(models.Model): 
    message = models.TextField() 
    reply_to = models.ForeignKey('self', related_name='replies', 
     null=True, blank=True) 

Ensuite, vous pouvez obtenir les réponses de une discussion avec Discussion.replies.Malheureusement, il n'y a pas moyen de faire une récursion dans le langage template de Django, donc vous devez soit 1) exécuter une fonction récursive pour obtenir une liste de réponses "aplatie", et mettre cela dans le contexte, soit 2) écrire une fonction qui peut être appelée récursive qui utilise un modèle pour générer chaque niveau, ce qui ressemblerait à quelque chose comme:

_DiscussionTemplate = Template(""" 
<li>{{ discussion.message }}{% if replies %} 
    <ul> 
     {% for reply in replies %} 
     {{ reply }} 
     {% endfor %} 
    </ul> 
{% endif %}</li> 
""".strip()) 

class Discussion(models.Model): 
    message = models.TextField() 
    reply_to = models.ForeignKey('self', related_name='replies', 
     null=True, blank=True) 

    @property 
    def html(self): 
     return _DiscussionTemplate.render(Context({ 
      'discussion': self, 
      'replies': [reply.html() for reply in self.replies.all()] 
     })) 

Ensuite, dans votre modèle de haut niveau, il vous suffit:

<ul> 
    {% for d in discussions %} 
    {{ d.html }} 
    {% endfor %} 
</ul> 

Appliquer CSS comme désiré pour le rendre joli.

EDIT: Les discussions racine sont celles de Discussion.objects.filter(reply_to=None). Et tout le code, _DiscussionTemplate inclus, va dans votre models.py. De cette manière, _DiscussionTemplate est initialisé une fois lorsque le module est chargé.

EDIT 2: Mettre le code HTML dans un fichier modèle est assez simple. Modifiez le code de la vue qui définit _DiscussionTemplate à:

_DiscussionTemplate = loader.get_template("discussiontemplate.html") 

Ensuite, créez discussiontemplate.html:

<li>{{ discussion.message }}{% if replies %} 
    <ul> 
     {% for reply in replies %} 
     {{ reply }} 
     {% endfor %} 
    </ul> 
{% endif %}</li> 

Définissez le chemin du fichier de modèle au besoin.

+0

Où va _DiscussionTemplate? Est-ce dans mon modèle? Dois-je importer le modèle? – dotty

+0

symmetrical = Faux vomir une erreur donc je l'ai enlevé, cela fonctionnera-t-il encore? – dotty

+0

Comment puis-je savoir quelles sont les discussions racine? – dotty

1

Découvrez django-threadedcomments.

En outre, la relation parent-réponse n'est pas vraiment ManyToMany -it aa parent-enfant OneToMany, car un commentaire (dans les modèles de commentaires threads traditionnels, de toute façon) ne peut être qu'une réponse à, au plus, un autre commentaire .

+0

Ce lien est bien au-dessus de ma tête. D'autres solutions? – dotty

1

La première étape consiste à réparer votre modèle. Levez les yeux plutôt que de baisser les yeux.

class Discussion(models.Model): 
    message = models.TextField() 
    parent = models.ForeignKey(Discussion, null=True, blank=True) 

    def get_children(self): 
     return Discussion.objects.filter(parent=self) 

Lorsque quelque chose n'a pas de parent, c'est un thread racine. Quand c'est le cas, c'est une réponse.

Votre logique d'affichage doit changer un peu. Au lieu d'itérer tous les commentaires, répétez les messages de niveau supérieur. Votre fichier comment.html pourrait ressembler à ceci:

{{ comment.message }} 
{% for comment in comment.get_children %} 
    {% include comment.html %} 
{% endfor %} 

Dans votre modèle principal, vous auriez:

{% for comment in base_comments %} 
    {% include 'comment.html' %} 
{% endfor %} 

Et à votre avis, ajouter 'base_comments':Discussion.objects.filter(parent=None) à votre contexte. Il y a bien sûr un élément d'interface utilisateur dans lequel vous devez formater des choses et gérer le processus de réponse, mais je vous laisse le soin de le faire.

Et n'oubliez pas que vous pouvez externaliser tout cela très facilement. J'utilise Disqus très efficacement.