Unless a reply can be a reply to multiple posts, a ManyToManyField
isn't what you want. You just need a ForeignKey
:
class Discussion(models.Model):
message = models.TextField()
reply_to = models.ForeignKey('self', related_name='replies',
null=True, blank=True)
Then you can get to a Discussion's replies with Discussion.replies
.
Unfortunately, there's no way to do a recursion in Django's template language, so you have to either 1) run a recursive function to get a "flattened" list of replies, and put that in the context, or 2) write a function that can be called recursively that uses a template to generate each level, which would look something like:
_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()]
}))
Then in your top-level template, you just need:
<ul>
{% for d in discussions %}
{{ d.html }}
{% endfor %}
</ul>
Apply CSS as desired to make it look nice.
EDIT: Root discussions are those in Discussion.objects.filter(reply_to=None)
. And all the code, _DiscussionTemplate
included, goes in your models.py
. This way, _DiscussionTemplate
is initialized once when the module loads.
EDIT 2: Putting the HTML in a template file is pretty straightforward. Change the view code that sets _DiscussionTemplate
to:
_DiscussionTemplate = loader.get_template("discussiontemplate.html")
Then create discussiontemplate.html
:
<li>{{ discussion.message }}{% if replies %}
<ul>
{% for reply in replies %}
{{ reply }}
{% endfor %}
</ul>
{% endif %}</li>
Set the path to the template file as needed.