My approach to Class Based Views
- Transfer
Luke Plant is a freelance programmer with many years of experience, one of the key developers of Django.
I once wrote about my dislike of Class Based Views (CBV) in Django . Their use significantly complicates the code and increases its volume, while CBVs prevent some fairly common templates from being used (say, when two forms are presented in one view). And apparently, I am not the only one of the Django developers who adheres to this point of view.
But in this post I want to talk about a different approach that I applied in one of the projects. This approach can be described with one phrase: " Create your own base class ."
With a fairly simple model view, using CBV in Djangocan save time. But in more complex cases, you will encounter a number of difficulties, at least you have to immerse yourself in the study of documentation .
You can avoid all this, for example, using a simplified implementation of CBV . Personally, I went even further and started from scratch, writing my own base class, borrowing the best ideas and implementing only what I needed.
The as_view method provided by the class
Personally, I do not like the method
Therefore, I abandoned this method in favor of a simple function
Also, I don't like the fact that templates are automatically named based on model names, etc. This is programming by convention, which unnecessarily complicates life with code support. After all, someone will have to go to find out where the template is used. That is, when using such logic, you MUST KNOW where to look for information on whether the template is used at all and how it is used.
It is much easier to manage a relatively uniform set of base classes than a large set of mixins and base classes. Due to the uniformity of the stack, I can not write crazy hacks to interrupt the inheritance .
Among other things, in CBV Django, I don’t like forced verbosity when adding new data
In fact, it’s usually even worse, because those added to the
Searching for examples on Github, I reviewed hundreds of code samples like this:
I did not pay much attention to this until I realized: people use standard generators / snippets to create new CBVs ( example 1 , example 2 , example 3 ). If people need such tricks, it means that you have created too cumbersome APIs.
I can advise: imagine what API you would like to receive, and implement it . For example, for static adding in,
And for dynamic adding:
I would also like to automatically accumulate any
I would also like to be able to directly add data to
Of course, while nothing should be spoiled at the class level, and the isolation of the request should not be broken. Moreover, all methods should work predictably and without any difficulties. But at the same time, one cannot allow the possibility of a random change from within the method defined by the dictionary class
When you finish dreaming, you will probably find that your imaginary API is too difficult to implement due to the peculiarities of the language itself, you need to modify it somehow. However, the problem is solvable, although it does seem a little magic. Usually, defining a method in a subclass without using
I prefer to do this in a more transparent way, using the name for the class attribute and method
In the implementation , several tricks are used, and first of all this: with the help of
Note that the TemplateView.handle method is extremely simple, it only calls another method, which does all the work:
This means that the subclass defining
In addition, I use a number of hooks to handle things like AJAX validation when submitting a form, RSS / Atom loading for list views, etc. This is pretty simple as I control the base classes.
The basic idea is that you do not have to limit yourself to the capabilities of Django. Nothing that relates to CBV is deeply integrated into it, so your own implementations will be no worse, or even better. I recommend that you write exactly the code that is needed for your project, and then create a base class that will make it work .
The disadvantage of this approach is that you will not facilitate the work of programmers who will support your code if they have learned the API for Django CBV. After all, your project will use a different set of base classes. However, the benefits still more than compensate for this inconvenience.
I once wrote about my dislike of Class Based Views (CBV) in Django . Their use significantly complicates the code and increases its volume, while CBVs prevent some fairly common templates from being used (say, when two forms are presented in one view). And apparently, I am not the only one of the Django developers who adheres to this point of view.
But in this post I want to talk about a different approach that I applied in one of the projects. This approach can be described with one phrase: " Create your own base class ."
With a fairly simple model view, using CBV in Djangocan save time. But in more complex cases, you will encounter a number of difficulties, at least you have to immerse yourself in the study of documentation .
You can avoid all this, for example, using a simplified implementation of CBV . Personally, I went even further and started from scratch, writing my own base class, borrowing the best ideas and implementing only what I needed.
Borrowing good ideas
The as_view method provided by the class
View
in Django is a wonderful thing. This method has been implemented after numerous discussions to facilitate isolation of the request by creating a new instance of the class to handle each new request. I took this idea with pleasure.Refusing Bad Ideas
Personally, I do not like the method
dispatch
, because it involves completely different processing GET
and POST
, although they often overlap (especially in cases of processing typical forms). In addition, when viewing rejected POST requests, when it is enough to simply ignore certain data, this method requires writing additional code, which is a bug for me. Therefore, I abandoned this method in favor of a simple function
handle
that must be implemented when creating any logic.Also, I don't like the fact that templates are automatically named based on model names, etc. This is programming by convention, which unnecessarily complicates life with code support. After all, someone will have to go to find out where the template is used. That is, when using such logic, you MUST KNOW where to look for information on whether the template is used at all and how it is used.
Stack alignment
It is much easier to manage a relatively uniform set of base classes than a large set of mixins and base classes. Due to the uniformity of the stack, I can not write crazy hacks to interrupt the inheritance .
Writing the right API
Among other things, in CBV Django, I don’t like forced verbosity when adding new data
context
in fairly simple situations when instead of one line you have to write four:class MyView(ParentView):
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['title'] = "My title" # Это единственная строка, которую я хочу написать!
return context
In fact, it’s usually even worse, because those added to the
context
data can be calculated using another method and hang on self
so that they can be found get_context_data
. Also, the more code, the easier it is to make a mistake. For example, if you forget about the challenge super
, then everything can go awry. Searching for examples on Github, I reviewed hundreds of code samples like this:
class HomeView(TemplateView):
# ...
def get_context_data(self):
context = super(HomeView, self).get_context_data()
return context
I did not pay much attention to this until I realized: people use standard generators / snippets to create new CBVs ( example 1 , example 2 , example 3 ). If people need such tricks, it means that you have created too cumbersome APIs.
I can advise: imagine what API you would like to receive, and implement it . For example, for static adding in,
context
I would like to write this:class MyView(ParentView):
context = {'title': "My title"}
And for dynamic adding:
class MyView(ParentView):
def context(self):
return {'things': Thing.objects.all()
if self.request.user.is_authenticated()
else Thing.objects.public()}
# Или, возможно, используя lambda:
context = lambda self: ...
I would also like to automatically accumulate any
context
that is detected ParentView
, even if I do not call it super
explicitly. In the end, we almost always want to add data to context
. And, if necessary, the subclass should remove specific inherited data by assigning a key None
. I would also like to be able to directly add data to
context
for any method in my CBV. For example, setting up / updating an instance variable:class MyView(ParentView):
def do_the_thing(self):
if some_condition():
self.context['foo'] = 'bar'
Of course, while nothing should be spoiled at the class level, and the isolation of the request should not be broken. Moreover, all methods should work predictably and without any difficulties. But at the same time, one cannot allow the possibility of a random change from within the method defined by the dictionary class
context
. When you finish dreaming, you will probably find that your imaginary API is too difficult to implement due to the peculiarities of the language itself, you need to modify it somehow. However, the problem is solvable, although it does seem a little magic. Usually, defining a method in a subclass without using
super
means that super
you can ignore the class definition , and you cannot use it in class attributes at all super
.I prefer to do this in a more transparent way, using the name for the class attribute and method
magic_context
. So I do not add a pig to those who will then support the code. If something is called magic_foo
, then most people will wonder why it is “magic” and how it works. In the implementation , several tricks are used, and first of all this: with the help of
reversed(self.__class__.mro())
all super-classes and their attributes are extracted magic_context
, and also the dictionary containing them is iteratively updated. Note that the TemplateView.handle method is extremely simple, it only calls another method, which does all the work:
class TemplateView(View):
# ...
def handle(self, request):
return self.render({})
This means that the subclass defining
handle
to execute the necessary logic does not need to be called super
. It is enough for him to directly call the same method:class MyView(TemplateView):
template_name = "mytemplate.html"
def handle(self, request):
# логика здесь...
return self.render({'some_more': 'context_data'})
In addition, I use a number of hooks to handle things like AJAX validation when submitting a form, RSS / Atom loading for list views, etc. This is pretty simple as I control the base classes.
Finally
The basic idea is that you do not have to limit yourself to the capabilities of Django. Nothing that relates to CBV is deeply integrated into it, so your own implementations will be no worse, or even better. I recommend that you write exactly the code that is needed for your project, and then create a base class that will make it work .
The disadvantage of this approach is that you will not facilitate the work of programmers who will support your code if they have learned the API for Django CBV. After all, your project will use a different set of base classes. However, the benefits still more than compensate for this inconvenience.