I have an app which essentially a conversation system (much like reddit).
Where a post can have multiple replies, and a reply and have multiplies, and a reply to a reply can have multiple replies (etc.)
I've made the model like this:
class Discussion(models.Model):
message = models.T开发者_如何学JAVAextField()
replies = models.ManyToManyField('self')
and the view:
discussions = Discussions.objects.all()
and the template looks like this:
{% for discussion in discussions %}
{{ discussion.message }}
{% endfor %}
How would I go about making a system where I can output all replies like this?
discussion
reply
reply
reply
reply
reply
reply
Which would go down as far as it needs to to ensure all replies are listed.
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.
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.
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.
精彩评论