Maciej Witkowski
Maciej Witkowski

Reputation: 45

Setting a form field to the object id in Django's DetailView

Let me explain my problem using some code: I am writing a simple blog application. I have a Post ListView, which lists the blog posts (obviously), and a DetailView which shows the content of the selected post. The DetailView uses the default keyword object to reference the post instance shown in the DetailView. There is a comment section at the end of each blog post.

In forms.py I have a CommentForm class:

class CommentForm(ModelForm):
    class Meta:
        model = Comment
        fields = ['author_nickname', 'content']

And in models.py I have a Comment model:

class Comment(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    author_nickname = models.CharField(max_length=10)
    date_posted = models.DateTimeField(auto_now_add= True)
    content = models.TextField(max_length=90)
    post = models.ForeignKey(Post,on_delete=models.CASCADE)

I use a DetailView to show the content of the selected post, but since it also has a form for adding comments to the comment section, it inherits from DetailView and FormView, like so:

class PostDetailView(DetailView, FormView):
    model = Post
    form_class = CommentForm
    success_url = '/thanks/'

    def form_valid(self, form):
        form.save()
        return super().form_valid(form)

Now, here is the rub: I want the CommentForm to add a comment to the post that is shown by the DetailView, where the form is (obviously). I've added the form tag at the end of the DetailView's template. It lives right here:

<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Add</button>
</form>

Obviously when I submit this form, I get an error message, because the Post_id is missing. If I add the Post field to the CommentForm class in forms.py like so:

fields = ['author_nickname', 'content', 'post']

and then specify the Post manually and hit submit, the form works, and adds the comment correctly without errors (for developement purpouses it currently navigates the user to /thanks/) So, how do I specify the post_id field in the form without the field being visible? I tried to add something like this between the form tags in my DetailView template:

<input type="hidden" name="post_id" value="{{object.id}}">

But it doesn't work, I still get the error:

NOT NULL constraint failed: blog_comment.post_id

Can anyone please help me? :( My hands are tied :c I have no idea how to solve this issue. I have no idea how to access the object instance in views.py , so setting the initial field value doesn't help me.

Please help :<

Upvotes: 2

Views: 1585

Answers (2)

bkrop
bkrop

Reputation: 238

I created something like this:

from django.views.generic.edit import FormMixin

class PostDetailView(FormMixin, DetailView):
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    form_class = CommentForm

In get_context_data i initialize form and inside it I indicate the post by initial={'post': self.object}

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

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

def form_valid(self, form):
    form.instance.author = self.request.user
    form.instance.post = self.get_object()
    form.save()
    return super(PostDetailView, self).form_valid(form)

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

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476768

It might be better to make this a CreateView [Django-doc], and pass the Post object to the context yourself, like:

from django.shortcuts import get_object_or_404

class PostDetailView(CreateView):
    model = Comment
    form_class = CommentForm
    success_url = '/thanks/'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        obj = get_object_or_404(Post, pk=self.kwargs['pk'])
        context['object'] = context['post'] = obj
        return context

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

Here in case the form is valid, we thus set the post_id to the pk passed in the URL and then let the CreateView handle the logic further. This will save the form, and redirect to the success_url.

Upvotes: 2

Related Questions