Search code examples
djangodjango-templatesdjango-generic-views

Django CreateView get_context_context_data not passing additional object to template


I'm creating a forum app in django. A user can reply to a Thread by pressing a button. This takes them to a new page containing a form with a textbox to post their reply. The PK of the thread being replied to is passed it through the url and saved as the parent_post of the reply within the CreateView.

This all works fine (so far...) but I also want to display the title of the thread that is being replied to at the top of the page, but I can't seem to be able to use this passed in pk to display the title of the thread at the top of the html page.

URLS:

path('reply/<int:pk>/', NewReplyView.as_view(), name='new-reply'),

View:

class NewReplyView(generic.CreateView):
    form_class = NewReplyForm
    initial = {'key': 'value'}
    template_name = 'forum_app/new_reply.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['x'] = "hahaaha"
        p = Post.objects.get(pk=self.kwargs.get('pk')) # Why does this not work, seems to work in the def post() method
        context['thread'] = p
        return context
    
    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {'form':form})
    
    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            submission = form.save(commit=False)
            submission.author = request.user
            submission.parent_post = Post.objects.get(pk=self.kwargs.get('pk'))
            submission.save()
            #return redirect(to='/') # TODO: redirect to newly created thread
            return redirect(reverse('forum_app:thread-detail', kwargs={'pk': self.kwargs['pk']}))
            #return redirect(reverse_lazy('forum_app:thread-list')) 
        
        return render(request, self.template_name, {'form': form})

Template:

{% extends 'base.html'%}

{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url 'home' %}">Home</a></li>
    <li class="breadcrumb-item"><a href="{% url 'forum_app:thread-list' %}">Threads</a></li>
    <li class="breadcrumb-item active" aria-current="New Reply">New Reply</li>
{% endblock %}
<p>{{thread.post_title}}</p>
{% block content %}  
    <h5>Reply to thread: {{x}}</h5>
    <form method='POST'>
        {% csrf_token%}
        {{form}}
        <button class="btn-outline-primary btn-lg" name="submit_post" type="submit">Submit</button>
    </form>
{% endblock %}

I even tried putting a random string into the context, but it didn't work, something like this:

class ThreadDetailView(generic.DetailView):
    context_object_name = "post"
    queryset = Post.objects.all()
    template_name = 'forum_app/thread_detail.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['replies'] = Post.objects.filter(parent_post=self.object)
        context['c'] = "hahag"
        return context

The "hahag" passed into the context in this example works. I can just put <p>{{c}}</p> at the top of the template and it shows up.


Solution

  • The problem is in how you are handling the get request.. You are only rendering the form without any additional context being passed..(The only thing available in your context is the form and additional stuffs passed by context processors such as the request,user,...) CBVs are kinda tricky. You have to know which method to override, and in many cases you have to call the parent method using super() so as to keep the flow (Not always the case).

    First,remove this code-block

        def get(self, request, *args, **kwargs):
            form = self.form_class(initial=self.initial)
            return render(request, self.template_name, {'form':form})
    

    To pass initial values use

    def get_initial(self):
        initial=super().get_initial()
        initial.update({'key': 'value'})
        return initial
    

    Same for post method override the form_valid method instead

    def form_valid(self, form):
        submission=form.instance
        submission.author = request.user
        submission.parent_post = Post.objects.get(pk=self.kwargs.get('pk'))
        return super().form_valid(form)
    

    For redirecting after a successful form submission, you can either set success_url using reverse_lazy, or override the get_success_url method:

    The new CreateView fully could be rewritten as

    class NewReplyView(generic.CreateView):
        form_class = NewReplyForm
        initial = {"key": "value"}
        template_name = "forum_app/new_reply.html"
        success_url = reverse_lazy('namespace:url-name')#For simple urls
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["x"] = "hahaaha"
            p = Post.objects.get(
                pk=self.kwargs.get("pk")
            )  # Why does this not work, seems to work in the def post() method
            context["thread"] = p
            return context
    
        def get_initial(self):
            initial=super().get_initial()
            initial.update({'key': 'value'})
            return initial
        def form_valid(self, form):
            submission=form.instance
            submission.author = request.user
            submission.parent_post = Post.objects.get(pk=self.kwargs.get('pk'))
            return super().form_valid(form)
        def get_success_url(self):#OR use this if complex url pattern or to perform some logic before redirect
            return reverse('namespace:url-name', kwargs={'pk': self.object.pk})