Luke Plant (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 the use of some fairly common templates (for example, when two forms are presented in the same view). And apparently, I'm
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, which I applied in one of the projects. This approach can be described in one phrase: "
Create your own base class ."
With a fairly simple model view, using
CBV in Django can save time. But in more complex cases, you will encounter a number of difficulties, at least you will have to immerse yourself in the
study of documentation .
')
All this can be avoided, for example, by using a
simplified CBV implementation . 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 Django
View
class is a great thing. This method was implemented after numerous discussions to facilitate isolation of the request by creating a new instance of the class to handle each new request. I gladly borrowed this idea.
Giving up bad ideas
Personally, I don’t like the
dispatch
method, because it involves completely different processing of
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 just to ignore certain data, this method requires writing additional code, which for me is a bug.
Therefore, I abandoned this method in favor of the simple function
handle
, which must be implemented when creating any logic.
Also, I don’t like that templates are automatically named based on model names, etc. This programming is by convention, which unnecessarily complicates life with the support of the code. After all, someone will have to frap to find out where the pattern is used. That is, when using such logic, you MUST KNOW where to look for information about whether a 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 impurity classes (mixins) and base classes. Due to the uniformity of the stack, I can not write
crazy hacks to interrupt inheritance .
Writing the right API
Among other things, I don’t like forced verbosity in CBV Django when adding new data in
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"
In fact, it is usually still worse, since the data added to the
context
can be calculated using another method and hang on
self
so that
get_context_data
can find
get_context_data
. In addition, the more code, the easier it is to make a mistake. For example, if you forget about the call to
super
, then everything can go awry.
Searching for examples on Github, I reviewed hundreds of code samples like this:
class HomeView(TemplateView):
I didn’t pay much attention to this until I realized that people use standard generators / snippets to create new CBVs (
example 1 ,
example 2 ,
example 3 ). If people need these tricks, it means that you have created a very cumbersome API.
I can advise:
imagine what you would like to get an API, and implement it . For example, for a static add 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()}
I would also like to automatically accumulate any
context
defined by
ParentView
, even if I do not explicitly call
super
. In the end, we almost always want to add data to
context
. And, if necessary, the subclass must remove the specific inherited data by assigning the key
None
.
I would also like to be able to directly add data in
context
for any method in my CBV. For example, setting / updating an instance variable:
class MyView(ParentView): def do_the_thing(self): if some_condition(): self.context['foo'] = 'bar'
Of course, nothing should be corrupted at the class level, and the isolation of the request should not be broken. In this case, all methods should work predictably and without any difficulty. At the same time, one cannot allow the possibility of accidental changes from within the method defined by the class of the dictionary of
context
.
When you finish dreaming, you will probably find that your imaginary API is too difficult to implement due to the nature of the language itself, you need to modify it somehow. Nevertheless, the problem is solved, although it looks a bit magic. Usually, the definition of a method in a subclass without using
super
means that the definition of the class
super
can be ignored, and you cannot use
super
at all in class attributes.
I prefer to do this in a more transparent way, using the name
magic_context
for the class attribute and method. So I don’t give a pig to those who will then maintain the code. If something is called
magic_foo
, then most people are curious about why it is “magic” and how it works.
The
implementation uses several tricks, and first of all this: using
reversed(self.__class__.mro())
, all super-classes and their
magic_context
attributes are
magic_context
, and the dictionary containing them is updated iteratively.
Please note that the
TemplateView.handle method is extremely simple; it only calls another method that does all the work:
class TemplateView(View):
This means that the subclass defining the
handle
to execute the necessary logic does not need to call
super
. It is enough for him to directly call the same method:
class MyView(TemplateView): template_name = "mytemplate.html" def handle(self, request):
In addition, I use a series of hooks to handle things like AJAX validation when submitting a form, uploading RSS / Atom for list views, etc. This is quite simple, since I control the base classes.
Finally
The basic idea is that you are not bound to be limited to Django's capabilities. Nothing deeply related to CBV is integrated deep into it, so your own implementations will be no worse or even better. I recommend that you
write exactly the code you need 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 Django CBV API. After all, your project will use a different set of base classes. However, the advantages still more than compensate for this inconvenience.