form_valid method not called if post method not overridden

84 Views Asked by At

I have a blog that is composed by user's posts and post's comments. In my UpdateView, the model points to Post, so I can show the post in the template. In my form_valid method, I get the comment from the user through a form. The problem is that if I don't override the post method, the form_valid method is not called. When I post something through a form, what happens? What methods are called by django and what methods should I override?

class PostDetails(UpdateView):

    template_name = "posts/details.html"
    model = Post
    form_class = CommentForm
    context_object_name = 'post'
    # success_url = "posts/details.html"

    def form_valid(self, form):
        post = self.get_object()
        comments = Comments(**form.cleaned_data)
        comments.post = Post(post.id)

        if self.request.user.is_authenticated:
            comments.user = self.request.user

        comments.save()
        messages.success(self.request, "Post sent successfully")
        return redirect("posts:details", pk=post.id)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        post = self.get_object()
        comments = Comments.objects.filter(published=True,
                                           post=post.id)
        context['comments'] = comments

        return context
class CommentForm(ModelForm):

    def clean(self):
        data = super().clean()
        author = data.get("author")
        comment = data.get("comment")

        if len(comment) < 15:
            print("Entrou no if do comment")
            self.add_error("comment", "Comment must be 15 chars or more.")
        if len(author) < 5:
            self.add_error("author", "Field must contain 5 or more letters.")

    class Meta:
        model = Comments
        fields = ['author', 'email', 'comment']
{% extends 'base.html' %}
{% load user_is_none %}
{% block post_content %}

    <div class="row pt-5">
        <div class="col-xl"></div>
        <div class="col-xl-8 text-center">
            <h2>{{ post.title | safe}}</h2>
            <h5>{{ post.excerpt | safe}}</h5>
            <p class="text-muted mt-4">
                <small>
                    {{  post.author | user_not_defined}} {{ post.date}} | {{ post.category}}
                </small>
            </p>
        </div>
        <div class="col-xl"></div>
    </div>

    <div class="row pt-5">
        <div class="col-xl"></div>
        <div class="col-xl-8">

            {% if post.image %}
                <img class="img-fluid pb-5 " src="{{ post.image.url }}" alt="{{ post.title }}">
            {% endif %}

            {{ post.content|safe }}

        </div>
        <div class="col-xl"></div>
    </div>

    <div class="row pt-5">

    </div>
    <div class="col-xl"></div>
    <div class="row pt-5">
            <div class="col-xl"></div>
            <div class="col-xl-8">
                <h2 class="my-3">Comments ({{ comments.count }})</h2>

                {% for comment in comments %}
                <h5 class="mb-2 mt-5">{{ comment.author }} said:</h5>
                <small class="text-muted">{{ comment.data }}</small>
                <p class="mb-1">{{ comment.comment | safe }}</p>
                {% endfor %}

            </div>
            <div class="col-xl"></div>
        </div>
    <div class="row pt-5">
        <div class="col-xl"></div>
        <div class="col-xl-8">
            <h2 class="my-3">Type your message</h2>
            <form method="POST">
                {% csrf_token %}
                {% include 'partial/messages/_messages.html' %}
                <table class="table">
                    {{ form }}
                    <tr>
                        <td colspan="2">
                            <input id="envia_comentario" type="submit" class="btn btn-primary"                  value="Enviar">
                        </td>
                    </tr>
                </table>
            </form>
            </table>

        </div>
        <div class="col-xl"></div>
    </div>
{% endblock %}

I tried putting in my code the post method. After that, the form_valid was called.

class PostDetails(UpdateView):

    template_name = "posts/details.html"
    model = Post
    form_class = CommentForm
    context_object_name = 'post'
    # success_url = "posts/details.html"

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_invalid(self, form):
        post = self.get_object()
        print(post)
        return render(self.request, "posts/details.html", {"form": form, "post": post})

    def form_valid(self, form):
        post = self.get_object()
        comments = Comments(**form.cleaned_data)
        comments.post = Post(post.id)

        if self.request.user.is_authenticated:
            comments.user = self.request.user

        comments.save()
        messages.success(self.request, "Post sent successfully")
        return redirect("posts:details", pk=post.id)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        post = self.get_object()
        comments = Comments.objects.filter(published=True,
                                           post=post.id)
        context['comments'] = comments

        return context

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from category.models import Category


class Post(models.Model):

    title = models.CharField(max_length=200)
    author = models.ForeignKey(User, on_delete=models.DO_NOTHING, 
    null=True, blank=True)
    date = models.DateTimeField(default=timezone.now)
    content = models.TextField()
    excerpt = models.TextField()
    category = models.ForeignKey(Category,   
    on_delete=models.DO_NOTHING)
    image = models.ImageField(upload_to='image/%Y/%m/%d')
    published = models.BooleanField(default=False)

    def __str__(self):
        return self.title

from django.db import models
from posts.models import Post
from django.utils import timezone
from django.contrib.auth.models import User


class Comments(models.Model):

    author = models.CharField(max_length=100, blank=False)
    email = models.EmailField(max_length=100, blank=False)
    comment = models.TextField(blank=False, max_length=100)
    post = models.ForeignKey(Post, on_delete=models.CASCADE,  
    blank=False, related_name='comments')
    data = models.DateTimeField(blank=False, default=timezone.now)
    user = models.OneToOneField(User, 
    blank=True,on_delete=models.DO_NOTHING, null=True)
    published = models.BooleanField(default=False, blank=False)

    def __str__(self):
        return self.author
1

There are 1 best solutions below

10
On

Your clean method should return the cleaned data, but in this case, you make two individual cleans, so you can work with:

from django.core.exceptions import ValidationError

from django import forms


class CommentForm(forms.ModelForm):
    def clean_author(self):
        author = self.cleaned_data['author']
        if len(author) < 5:
            raise ValidationError('Field must contain 5 or more letters.')
        return author

    def clean_comment(self):
        comment = self.cleaned_data['comment']
        if len(comment) < 15:
            raise ValidationError('Comment must be 15 chars or more.')
        return comment

    class Meta:
        model = Comments
        fields = ['author', 'email', 'comment']

as for the view, you implement too much boilerplate code, and by working with an UpdateView, it will not create a comment, it will simply try to update the Post.

You can work with a CreateView and

from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import get_object_or_404
from django.views.generic.edit import CreateView


class PostDetails(SuccessMessageMixin, CreateView):
    template_name = 'posts/details.html'
    form_class = CommentForm
    success_message = 'Post sent successfully'

    def get_success_url(self):
        return redirect('posts:details', pk=self.kwargs['pk'])

    def form_valid(self, form):
        form.instance.post_id = self.kwargs['pk']
        if self.request.user.is_authenticated:
            form.instance.user = self.request.user
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        post = get_object_or_404(Post, pk=self.kwargs['pk'])
        return super().get_context_data(
            **kwargs, post=post, comments=post.comments_set.filter(published=True)
        )