Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create nested comment system with django-mptt

Tags:

I'm working on simple blog and I'm trying to implement a nested comment system for each post.

I created model for comments and it's works fine via Django Admin Page.

I don't know how to create form for posting new comment and replying.

Here is what I have so far:

models.py

(...)
class Post(models.Model):
    author = models.ForeignKey('Author', on_delete=models.CASCADE)
    title = models.CharField(max_length=250)
    slug = models.SlugField(unique=True, blank=True, max_length=250)
    created = models.DateTimeField(auto_now=False, auto_now_add=True)
    modified = models.DateTimeField(auto_now=True, auto_now_add=False)
    tags = TaggableManager(blank=True)
    image = models.ImageField(upload_to="images/%Y/%m/", blank=True, null=True)
    content = models.TextField()

    def get_absolute_url(self):
        return reverse('post_detail', kwargs={'slug': self.slug, })

    # create slug
    def save(self, *args, **kwargs):
        if not self.id:
            self.slug = slugify(unidecode(self.title))
        super(Post, self).save(*args, **kwargs)

    def __str__(self):
        return str(self.title)

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

    name = models.CharField(max_length=250)
    email = models.EmailField(max_length=250, blank=True, null=True)
    website = models.CharField(max_length=250, blank=True, null=True)
    created = models.DateTimeField(auto_now=False, auto_now_add=True)
    content = models.TextField()

    def __str__(self):
        return str("{}: {}...".format(self.name, self.content[:50]))

forms.py

class CommentForm(forms.ModelForm):

    class Meta:
        model = Comment
        fields = [ "name", 'email', 'website', 'content']

views.py

class PostDetailView(DetailView):
    model = Post

    def get_context_data(self, **kwargs):
        context = super(PostDetailView, self).get_context_data(**kwargs)
        context['form'] = CommentForm()
        return context


class PostCommentView(SingleObjectMixin, FormView):
    template_name = 'blog/post_detail.html'
    form_class = CommentForm
    model = Post

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(PostCommentView, self).post(request, *args, **kwargs)

    def get_success_url(self):
        return reverse('post_detail', kwargs={'slug': self.object.slug})

class PostDetail(View):

    def get(self, request, *args, **kwargs):
        view = PostDetailView.as_view()
        return view(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        view = PostCommentView.as_view()
        return view(request, *args, **kwargs)

comment_form.html

<div id="respond">
    <h2 class="respond-title">Leave a comment</h2>
    <span class="form-caution">Make sure you enter the (*) required information where indicated. HTML code is not allowed.</span>
    <form action="" method="post">
        {{ form.non_field_errors }}
        {% csrf_token %}
        {{  form }}
         <input name="submit" type="submit" id="submit" class="btn btn-primary" value="Submit Comment">
    </form>
</div>
like image 341
Konrados Avatar asked Jan 21 '17 14:01

Konrados


1 Answers

In general

Modified Preorder Tree Traversal is not necessary the best solution for building nested comments. Take into consideration that with each new node whole tree has to be rebuild. For example, lets imagine comments under post F:

//             F
//           / | \
//          B  X  G
//         / \
//        Z   Y

Notice that comment X has left value of 8 and right value of 9. But, with every new subcomment to B or X these values will change. E.g. with a new comment under comment Z values of X will change to lft = 10 and rht = 11. This could lead to awful performance and, as I imagine, loss of data in case of messenger means where information is sent only once (e.g. Django channels).


Customizing ready solutions

This post presents solution for achieving nested commend system by connecting inbuilt comment app and the mptt. It's an old post and the django.contrib.comments has been separated to a separate project since Django 1.6. Recently Tim Graham added changes to support Django 2.0, so it seems up-to-date.

The idea is to create a custom model and form that will add to the title field of Django_comments. The post suggest wiring it like that:

Models.py

from django_comments.models import Comment
from mptt.models import MPTTModel, TreeForeignKey

class MPTTComment(MPTTModel, Comment):
    """ Threaded comments - Add support for the parent comment store and MPTT traversal"""

    # a link to comment that is being replied, if one exists
    parent = TreeForeignKey('self', null=True, blank=True, related_name='children')

    class MPTTMeta:
        # comments on one level will be ordered by date of creation
        order_insertion_by=['submit_date']

    class Meta:
        ordering=['tree_id','lft']

Forms.py

from django import forms
from django.forms.widgets import widgets        
from django_comments.forms import CommentForm                            
from models import MPTTComment

class MPTTCommentForm(CommentForm):
    parent = forms.ModelChoiceField(queryset=MPTTComment.objects.all(),
        required=False, widget=forms.HiddenInput)

    def get_comment_model(self):
        # Use our custom comment model instead of the built-in one.
        return MPTTComment

    def get_comment_create_data(self):
        # Use the data of the superclass, and add in the parent field field
        data = super(MPTTCommentForm, self).get_comment_create_data()
        data['parent'] = self.cleaned_data['parent']
        return data

__init__.py in your app directory

from models import MPTTComment
from forms import MPTTCommentForm

def get_model():
    return MPTTComment

def get_form():
    return MPTTCommentForm

There is also another django package, this one with nested comments already built-in: django-threadedcomments. Worth checking out.

like image 82
Pawel Kam Avatar answered Sep 23 '22 10:09

Pawel Kam