Django - proper way to implement threaded comments
Asked Answered
L

1

24

I'm developing a blog site using Django. My site will allow users to comment on any of my blog posts and also reply to each other and will be displayed using a 'threaded comments' structure (I haven't started user functionality yet, just comments). I've got the threaded comments to work properly using django-mptt (at least, for now), but I have NO CLUE if the route or steps I'm taking are in the right direction. Almost all the tutorials I've gone through only scratch the surface when it comes to comments and doesn't talk about threaded comments in django. I want some experienced/professional advice on what I might be doing wrong and what I could be doing better. The last thing I want is to find out there was a much more acceptable way of going about, after hours of work put in.

So, here is a list of what I need clarity on:

  1. django-mptt:

    • I chose this because I can afford slower write times. My site will have more reads than writes. Is this option okay for my case? Is there a better alternative I don't know about?
    • What should I do if my site does end up having lots of commenting activity? What could I do to optimize tree restructuring? Or would I be better off switching to an adjacency list?
    • My MPTT comment model has a ForeignKey referenced to itself (for replies). Is this the right way? Or should I create a separate reply model?
    • The way I insert a reply to another user's comment in the tree is using a hidden input within the form that's within the mptt recursive template tags, and return the input value (which is the id of the comment that the reply is for) and set the parent of the reply to that input value. Is this an accepted method?
  2. Multiple forms on one HTML page

    • I have two forms on my blog post HTML page. One to comment on the blog post, and one to reply to a user's comment. Is this accepted? Or should I create different URLs and view functions for different forms? I did it this way because I wanted a Reddit style commenting system. I don't want it to have to go to a different page to comment or reply.
    • If a user comments on my blog post, the hidden input value within the reply form returns nothing, therefore I get an error when trying to assign it to a variable in the views.py function. I used a try/except block to fix it. Is there a better way around this?

I'm sorry if these are noob questions and for my post being so long. I just want to do things the best way possible using realistic solutions for a beginner. Any feedback would help. Thank you! Here's my code for my blog app.

models.py

    from django.db import models

    from mptt.models import MPTTModel, TreeForeignKey

    class Post(models.Model):
        """Blog post"""
        title = models.CharField(max_length=200)
        body = models.TextField()
       date_added = models.DateTimeField(auto_now_add=True)

        def __str__(self):
            return self.body[:50] + '...'

    class Comment(MPTTModel):
        """User comment"""
        post = models.ForeignKey(Post, related_name='comments',on_delete=models.CASCADE)
        parent = TreeForeignKey('self', null=True, blank=True, related_name='children',db_index=True, on_delete=models.CASCADE)

        user_comment = models.CharField(max_length=500, unique=True)
        date_added = models.DateTimeField(auto_now_add=True)
        # approved = models.BooleanField(default=False)

        class MPTTMeta:
            order_insertion_by = ['date_added']

        def __str__(self):
            return self.user_comment[:20]

'approved' is commented out because I get a 'no such column: approved' error for some weird reason.

forms.py

    from django import forms

    from .models import Post, Comment

    class CommentForm(forms.ModelForm):
        class Meta:
            model = Comment
            fields = ['user_comment']

views.py

    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    from django.urls import reverse

    from .models import Post
    from .forms import CommentForm

    def posts(request):
        """Show all blog posts"""

        posts = Post.objects.order_by('-date_added')

        context = {
            'posts': posts
        }
        return render(request, 'posts/posts.html', context)

    def post(request, post_id):
        """Show single blog post"""

        post = Post.objects.get(id=post_id)
        comments = post.comments.all()

        if request.method != 'POST':
            comment_form = CommentForm()

        else:
            comment_form = CommentForm(data=request.POST)
            try:
                parent_id = request.POST['comment_id']
            except:
                pass
            if comment_form.is_valid():
                comment = comment_form.save(commit=False)
                comment.post = post
                comment.parent = comments.get(id=parent_id)
                comment.save()
                return HttpResponseRedirect(reverse('posts:post', args=[post_id]))

        context = {
            'post': post,
            'comment_form': comment_form,
            'comments': comments,
        }
        return render(request, 'posts/post.html', context)

post.html

    {% extends 'posts/base.html' %}

    {% block blog_content %}

        <h1>Post page!</h1>

        <h3>{{ post.title }}</h3>
        <h4>{{ post.date_added }}</h4>
        <p>{{ post.body }}</p>

        <form method="post" action="{% url 'posts:post' post.id %}">
          {% csrf_token %}
          {{ comment_form.as_p }}
          <button type="submit">Add comment</button>
        </form>

        {% load mptt_tags %}
          {% recursetree comments %}
          <h5>{{ node.date_added }}</h5>
          <p>{{ node.user_comment }}</p>
              <form method="post" action="{% url 'posts:post' post.id %}">
              {% csrf_token %}
              {{ comment_form.as_p }}
              <input type="hidden" name="comment_id" value="{{ node.id }}">
              <button type="submit">Reply</button>
              </form>
          {% if not node.is_leaf_node %}
            <div style="padding-left: 20px">
            {{ children }}
            </div>
          {% endif %}
          {% endrecursetree %}


    {% endblock %}

urls.py

    from django.urls import path

    from . import views

    app_name = 'posts'
    urlpatterns = [
        path('posts/', views.posts, name='posts'),
        path('posts/<int:post_id>/', views.post, name='post'),
    ]
Lentiginous answered 8/2, 2018 at 3:52 Comment(10)
Have you looked at Mezzanine? It comes with a threaded comment model out of the box. You can use it as-is or extend and modify it for your needs. You can also look at Drum, which is implemented using Mezzanine, as an example.Ander
Thanks! These are great recommendations. I was looking for something Django 2 compatible. I believe Mezzanine uses Django 1.8-1.10 only if I'm not mistaken. But, I'll definitely do some thorough research on it.Lentiginous
Mezzanine has been ported to 1.11 (but you have to pip install the latest from github), but not 2 yet as far as I know.Ander
Great question. I am also interested in learning a better solution for this.Hazem
I think your question is a bit too broad. There are multiple questions asked which are more or less "am I doing it well or not?" Can you please narrow your question so we can provide a clear answer? Thanks.Halifax
Try django-comments-xtd. It supports threaded comments with custom thread level.Alonaalone
Try using parent=ForeignKey('self', related_name='children', ...) and then add methods: def get_parent(self): if self.parent:return self.parent else: return self and def get_thread(self): return self.children.all()Straighten
Why are you reinventing the wheel? Everything is done already. github.com/HonzaKral/django-threadedcommentsGunyah
Have a look into this article : engineering.hackerearth.com/2015/01/27/…Overnice
@BryanHinchliffe you can user django-contrib-comments for commenting. django-contrib-comments.readthedocs.io/en/latest/…Attorney
C
3

MPTT trees are great for getting a list of subnodes or node counts. They are costly for adding/inserting nodes and the cost increases linearly with the size of the three. They are designed to fit tree data into relational databases. Also, don't get fooled by "I'll have much more reads than writes". Ideally, most of the reads should hit a cache, not the database under it.

Why not skip the relational database and go with a NoSQL database that can natively store trees? There are easy integrations for Django and pretty much every NoSQL database you can thing about.

Catarinacatarrh answered 6/9, 2019 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.