5
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.
1
Check out django-threadedcomments.
Also, the parent-reply relationship isn’t really a ManyToMany
—it’s a a parent-child OneToMany
, because a comment (in traditional threaded comment models, anyway) can only be a reply to, at most, one other comment.
- [Django]-Store django forms.MultipleChoiceField in Models directly
- [Django]-Django Validators in Serializer vs Constraints in Models
- [Django]-Get the values of multiple checkboxes in django
- [Django]-ORM in Django vs. PHP Doctrine
1
The first step is fixing your model. Look up rather than looking down.
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)
When something doesn’t have a parent, it’s a root thread. When it does, it’s a reply.
Your display logic needs to change a bit. Instead of iterating all comments, iterate the top-level posts. Your comment.html
file might look like this:
{{ comment.message }}
{% for comment in comment.get_children %}
{% include comment.html %}
{% endfor %}
In your main template you’d have:
{% for comment in base_comments %}
{% include 'comment.html' %}
{% endfor %}
And in your view, add 'base_comments':Discussion.objects.filter(parent=None)
to your context.
There’s of course a UI element to this where you need to format things and handle the replying process, but I’ll leave that up to you.
And don’t forget that you can outsource all this very easily. I use Disqus very effectively.
- [Django]-Django: Long field (BigIntegerField) For MongoDB
- [Django]-How can I display an jupyter notebook on a homepage built with django?
- [Django]-Django QuerySet querying or filtering "Odd" and/or "Even" value in a particular field