Cone16
Cone16

Reputation: 1

Django MPTT, can't get parent_id for threaded comments reply

My Problems are as follows:

Im relatively new to Django. so sorry for this mess. I've tried to implement threaded comments to my blog via MPTT. But when i pressing the 'Reply' Button django throws me a Type Error

Normal parent comments working correctly

When i just create a comment with via the admin site, i can create parent comments and child comments and they will be indented just as expected in the main Application too...

my Database is mySQL.

The Error:

in _calculate_inter_tree_move_values
    space_target = target_right - 1
                   ~~~~~~~~~~~~~^~~
TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'

my models.py:

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey

STATUS = (
    (0,"Draft"),
    (1,"Publish")
)

class Post(models.Model):
    title = models.CharField(max_length=200, unique=True)
    slug = models.SlugField(max_length=200, unique=True)
    author = models.ForeignKey(User, on_delete= models.CASCADE, related_name='blog_posts')
    updated_on = models.DateTimeField(auto_now=True)
    content = models.TextField()
    created_on = models.DateTimeField(auto_now_add=True)
    status = models.IntegerField(choices=STATUS, default=0)

    class Meta:
        ordering = ['-created_on']

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post_detail',args=[self.slug])


class Comment(MPTTModel):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    name = models.CharField(max_length=80)
    email = models.EmailField()
    body = models.TextField()
    created_on = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=False) 
    parent=TreeForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='children')

    class MPTTMeta:
        order_insertion_by = ['created_on',]

    def __str__(self):
        return 'Comment {} by {}'.format(self.body, self.name)
    
    #just for the threaded comments
    def get_comments(self):
        return Comment.objects.filter(parent=self).filter(active=True)

views.py:

from django.shortcuts import render, get_object_or_404, redirect, HttpResponseRedirect
from django.views import generic
#from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
#from django.template import loader
from .models import Post, Comment
from .forms import CommentForm


class PostList(generic.ListView):
    queryset = Post.objects.filter(status=1).order_by('-created_on')
    template_name = 'index.html'
    paginate_by = 3

#class PostDetail(generic.DetailView):
#    model = Post
#    template_name = 'post_detail.html'

class AboutMe(generic.TemplateView):
    template_name = 'about_me.html'

class Contact(generic.TemplateView):
    template_name = 'contact.html'

class Impressum(generic.TemplateView):
    template_name = 'impressum.html'

class StaticContent(generic.TemplateView):
    template_name = 'static_html_content.html'

class SideBar(generic.TemplateView):
    template_name = 'sidebar.html'

#class PostDetail(generic.DetailView):
#    model = Post
#    context_object_name = "post"
#    template_name = "post_detail.hhml"

def post_detail(request, post):
    template_name = 'post_detail.html'
    post = get_object_or_404(Post, slug=post, status=1)
    comments = post.comments.filter(active=True)

    new_comment = None
    #comment posted
    if request.method == 'POST':
        comment_form = CommentForm(request.POST)
        if comment_form.is_valid():

            # Create comment object but dont save to database yet
            new_comment = comment_form.save(commit=False)
            # Assign thecurrent post to the comment
            new_comment.post = post
            #save the comment to the database
            new_comment.save()
            #redirect to the same page and focus on that comment
            return HttpResponseRedirect('/' + post.slug)
            #return redirect(post.get_absolute_url()+'#'+str(new_comment.id))
    else:
        comment_form = CommentForm()

    return render(request, template_name, {'post': post, 'comments': comments, 'new_comment': new_comment, 'comment_form': comment_form})


def reply_page(request):
    if request.method == 'POST':
        form = CommentForm(request.POST)

        if form.is_valid():
            post_id = request.POST.get('post_id')
            parent_id = request.POST.get('parent')
            post_url = request.POST.get('post_url')

            print(post_id)
            print(parent_id)
            print(post_url)
            print('was here reply!')

            reply = form.save(commit=False)

            reply.post = Post(id=post_id)
            reply.parent = Comment(id=parent_id)
            reply.save()

            return redirect(post_url+'#'+str(reply.id))
    
    return redirect('/')

forms.py:

from .models import Comment
from django import forms
from mptt.forms import TreeNodeChoiceField

class CommentForm(forms.ModelForm):
    parent = TreeNodeChoiceField(queryset=Comment.objects.all())

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields['parent'].widget.attrs.update(
            {'class': 'd-none'})
        self.fields['parent'].label = ''
        self.fields['parent'].required = False


    class Meta():
        model = Comment
        fields = ('name', 'parent', 'email', 'body')

        widgets = {
            'name': forms.TextInput(attrs={'class': 'col-sm-12'}),
            'email': forms.TextInput(attrs={'class': 'col-sm-12'}),
            'body': forms.Textarea(attrs={'class': 'form-control'}),
        }

    def save(self, *args, **kwargs):
        Comment.objects.rebuild()
        return super(CommentForm, self).save(*args, **kwargs)

post_details.html:

{% extends 'base.html' %} 
{% load static %}
{% load crispy_forms_tags %}
{% load mptt_tags%}
{% block content %}
<header class="masthead" style="height: 13.9em;">
     <!-- javascript add -->
     <script src="{% static 'js/main.js'%}"></script>
</header>
<div class="container">
  <div class="row">
    <div class="col-md-8 card mb-4 mt-3 left top">
      <div class="card-body">
        <h1>{% block title %} {{ post.title }} {% endblock title %}</h1>
        <p class=" text-muted">{{ post.author }} | {{ post.created_on }}</p>
        <p class="card-text ">{{ post.content | safe }}</p><br>
        <a href="{% url 'home' %}" style="float: right;"
        class="btn btn-danger">Go Back</a><br><br>
      </div>
    </div>
    {% block sidebar %} {% include 'sidebar.html' %} {% endblock sidebar %}
  </div>
  <div class="row">
    <div class="col-md-8 card mb-4 mt-3 left top">
      <div class="card-body">
        <h3>Add Comment</h3>
        <form method="POST" action="">
          {% csrf_token %}
          {{ comment_form | crispy }}
          <button type="submit" class="btn btn-danger">Comment</button>
        </form>

        <!-- count comments section-->
        {% with comments.count as total_comments %}
          <h3 class="mt-5">
            {{ total_comments }} comment{{ total_comments|pluralize }}
          </h3>
        {% endwith %}

        <!-- show comment section -->
        <div>
          <ul style="list-style-type: none;">
          {% recursetree comments %}
            <li>
            
            <!-- parent body -->
            <div>
              <div class="border p-4 rounded">
              <h4>{{ node.name }}</h4>
              <small class="text-muted">On {{ node.created_on }}</small>
              <h5>{{ node.body }}</h5> <br>
              <button class="btn btn-danger btn-sm" onclick="handleReply({{node.id}})">Reply</button>
              <div id="reply-form-container-{{node.id}}" style="display:none">
      
                <form method="POST" action="{% url 'reply' %}" class="mt-3">
                    {% csrf_token %}
                    <input type="hidden" name="post_id" value="{{post.id}}">
                    <input type="hidden" name="parent" value="{{comment.id}}">
                    <input type="hidden" name="post_url" value="{{post.get_absolute_url}}">
                    {{comment_form|crispy}}
                    <div>
                        <button type="button" onclick="handleCancel({{node.id}})" class="btn btn-danger border btn-sm">Cancel</button>
                        <button type="submit" class="btn btn-danger btn-sm">Submit</button>
                    </div>
                </form>
              </div>
              </div>
            </div>
            <br>

            <!-- if children -->
            {% if not node.is_leaf_node %}
            <ul ul style="list-style-type: none;">

              <div class="children pl-2 pl-md-5">
                {{ children }}

            </div>
            </ul>
            {% endif %}


            </li>
          {% endrecursetree%}
          </ul>
        </div>


      </div>
    </div>
  </div>
</div>
{% endblock content %}

I've tried to change the values in the formfields of the post_details.html to node.parent.id but nothing seems to happen.

Also i tried to change the parent_id's datatype to see if there something happens. but because it runs in this Opperant-Error i think it could be an issue with saving to the mySQL Database...

Upvotes: 0

Views: 161

Answers (1)

Cone16
Cone16

Reputation: 1

Ok... Now i have have found the answer. In views.py i've edited these lines:

parent_id = request.POST.get('parent')

and replace 'parent' with 'commend_id'

parent_id = request.POST.get('comment_id')

In post_detail.html i have changed the form:

<input type="hidden" name="parent" value="{{comment.id}}">

to...

<input type="hidden" name="comment_id" value="{{node.id}}">

Now it runs as expected! YAY!

Upvotes: 0

Related Questions