Opened 7 years ago

Closed 7 years ago

Last modified 7 years ago

#28943 closed Cleanup/optimization (worksforme)

Avoid the need to call get_context_data() in TemplateView subclasses

Reported by: James Pic Owned by: nobody
Component: Generic views Version: 2.0
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by James Pic)

Currently, TemplateView inherits render_to_response(context) from TemplateResponseMixin which requires a context argument.

This means that when your TemplateView subclass wants to return the TemplateResponse with the default context, you still have to create and pass the default context:

class YourView(TemplateView):
    def post(self, request, *a, **k):
        if not self.dostuff():
            return http.HttpResponseBadRequest()

        context = self.get_context_data(*k)
        return self.render_to_response(context)

The reason for this is that ContentMixin defines get_context_data(), and TemplateResponseMixin defines render_to_response(context, ...), TemplateResponse mixes the two in get():

class TemplateView(TemplateResponseMixin, ContextMixin, View):
    """
    Render a template. Pass keyword arguments from the URLconf to the context.
    """
    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)

I think it would be more usable as such:

class TemplateView(TemplateResponseMixin, ContextMixin, View):
    """
    Render a template. Pass keyword arguments from the URLconf to the context.
    """
    def get(self, request, *args, **kwargs):
        return self.render_to_response(**kwargs)

    def render_to_response(self, context=None, **kwargs):
        context = context or self.get_context_data(**kwargs)
        return self.render_to_response(context)

Then, users could call render_to_response() in their code without dealing with a context they don't override because they are satisfied with the default (which adds view=self in the context), ie:

class YourView(TemplateView):
    def get(self, request, *a, **k):
        self.token = self.generatetoken()
        return self.render_to_response(**k)

    def post(self, request, *a, **k):
        if not self.dostuff():
            return http.HttpResponseBadRequest()
        return self.render_to_response(**k)

Change History (16)

comment:1 by James Pic, 7 years ago

Description: modified (diff)

comment:2 by James Pic, 7 years ago

Description: modified (diff)

comment:3 by Tim Graham, 7 years ago

Easy pickings: unset

The ticket summary is cryptic. Please describe the use case in more detail. I don't see an indication of where TemplateResponse.get(self, request, *args, **kwargs) lives.

comment:4 by James Pic, 7 years ago

Description: modified (diff)

comment:5 by James Pic, 7 years ago

Thanks for your feedback, I rewrote the issue, does it make any sense now ?

comment:6 by James Pic, 7 years ago

Description: modified (diff)

comment:7 by James Pic, 7 years ago

Description: modified (diff)

comment:8 by James Pic, 7 years ago

Description: modified (diff)

comment:9 by James Pic, 7 years ago

Description: modified (diff)

comment:10 by James Pic, 7 years ago

Description: modified (diff)

comment:11 by Tim Graham, 7 years ago

Summary: Unenforce manual get_context_data()Avoid the need to call get_context_data() in TemplateView subclasses

Is there a reason to duplicate the render_to_response() call in post()? I didn't test it, but I believe you could write both examples as:

class YourView(TemplateView):
    def post(self, request, *a, **k):
        if not self.dostuff():
            return http.HttpResponseBadRequest()

        return super().get(request, *a, **k)

comment:12 by Tim Graham, 7 years ago

Resolution: worksforme
Status: newclosed

comment:13 by James Pic, 7 years ago

Of course, that works, unless you've overridden get() to do things that you don't want to happen on post.

In post, i would like to render to template again, not run get() ;)

comment:14 by Tim Graham, 7 years ago

Unless I missed something, super().get() will call the TemplateView implementation, even if you override get() in a subclass.

comment:15 by James Pic, 7 years ago

Yes, returning super().get() allows to bypass the last level of get() override and i think this had a side effect in some code i'm trying to remember.

Last edited 7 years ago by James Pic (previous) (diff)

comment:16 by James Pic, 7 years ago

Unless I missed something, super().get() will call the TemplateView implementation, even if you override get() in a subclass.

That's True unless YourFooDetailView inherits from YourProjectDetailView which would inherit from django.views.generic.DetailView.

When you work as a Django user, you often want to add a project-specific layer between your actual user facing views and django views, ie. to refactor common features you have in all your {List,Detail,Update,Create,Form,Object,Model}View classes accross the project's app. Because when you are a coder with love you usually end up with your own default {create,delete,update,list,detail} templates (cause instead of {% extends 'myapp/base.html' %} in 'myapp/foo_list.html', you have {% extends 'list.html' %}, not only for the love of beauty, but to refactor as much template code as possible.

And anyway, I think get_context_data() deserves to be removed from Django's public API, this is made to support legacy templates, new templates just use {{ view.object }} than {{ object }} because then they make @object a memoized property which they can always use in {dispatch,get,post,delete,options} methods instead of thinking they're paid by the quantity of lines of code and take pride in overriding and decorating get_context_data() for no reason thanks to the visionary who made view=self a default in CBV.

Maybe what I'm saying doesn't make any sense to anybody than myself lol but at least you can point-godwin me because i don't consider the resolution of this ticket worksforme, but don't take it personnaly, you know i still admire you from the deepest of my heart Tim <3

But deep in your heart, you know that love between a context and a template must not be coupled to the get method's callback in TemplateView but should indeed be in its own method ending with:

class TemplateView..:

def get..:

return self.template_context_love()

Then you can:

def post..:

love()
return self.template_context_love()

Instead of

def post..:

return self.get() # but i just want template_context love not all my parent get logic :'(

With LOVE

Version 3, edited 7 years ago by James Pic (previous) (next) (diff)
Note: See TracTickets for help on using tickets.
Back to Top