In Django version 1.3, class-based views were presented - a way to describe view as classes. Documentation, however, concerns only generic views, without describing the general technique of writing "views" in the form of classes. Having started using generic views and then trying to change their behavior, I gradually got to the point where I had to look at the source code to figure out how to do something. Therefore, I decided to write this text. Its purpose is to explain how to use the class-based view and how it can be useful.
Motivation
The idea is simple - there is some frequently used functionality (for example, displaying an object / list of objects from a database, passing context to a template, etc.), which is used very often. To avoid having to write a monotonous code, such views are described directly in the framework code. In order to use them, you need to communicate only specific parameters, such as the type of the object or the name of the template in which to transfer the context.
For example, we have a class Publisher, and we want to display on the page a list of all objects of this class. In Django 1.2, we use the generic view
like this :
We simply pass the queryset that interests us to the parameter dictionary, everything else is already implemented for us.
')
However, if the development process reveals the need to change the behavior of the "view", originally implemented as a generic (which happens quite often), problems arise:
- In the generic view, there are no parameters that change the behavior of interest.
- The code that forms the parameters for the generic view is comparable in size to the code of the separately written “view”, and lies in URLCONF, where it is clearly not the place.
I want to have a technology that allows you to use ready-made Django functional for writing a view, while having the ability to change its behavior in any part.
Generic views on classes
In order for generic views to be able to change anything, the developers decided to present them in the form of classes, in which the main parameters are represented by fields, and the functional is visually broken down into functions — separate scheduling, separate context creation, separate template rendering (more about see below for this). From this class, you can inherit your view, and overlap in them variables and functions instead of passing them in the parameters.
So look at the implementation of the example with Publisher on the classes:
In urls.py, instead of the “view” name, the as_view () method of the described class is now passed:
urlpatterns = patterns('', (r'^publishers/$', PublisherDetailView.as_view()), )
As parameters of this function, variables of the type model or template_name can be passed, in the same way as it was done with function-based views.
Now, if we need, for example, to change the context passed to the template, we can simply override in this class the function get_context_data (), which is engaged in the formation of the context. And the code will live where it is supposed to - in views.py. It remains only to figure out what functions in which generic view is, and what they do.
Impurities
Answers to questions about how you want to process the parameters of an incoming request, how you will select data from the database and how you will form the answer are often independent of each other. Sometimes, you want to give an HTML page as a response, sometimes a JSON object. If you want to write a common code for processing the request and working with the database, which will be used in both cases, you need to somehow select a group of all the methods responsible for forming the answer from the context. This problem can be solved with the help of a pattern of
impurities (
mixins ).
Each class-based generic view:
- Inherited from View base class;
- Inherited from one or more impurities .
So, for the expansion of the functional, a generic view is needed:
- See in the documentation what impurities it contains;
- Find in these impurities the function in which the behavior you are interested in is implemented;
- Redefine this function.
All "views" as classes
Using class-based generic views motivates writing all “views” in the form of classes, all from the same considerations of reducing repeatability and structuring. If none of the function-based generic views is often appropriate for you, for the reasons described above, then View-class will always be useful for you from class-based generic views.
View
All generic views are inherited from this class. It implements the dispatch () function, which accepts request and returns HttpReponse, which is what the view always did, being a function. It is worth noting that the function as_view () returns a pointer to dispatch (), which makes clear the analogy with function-based views — in its minimalist form, the View class is a class containing just one function that the controller calls from the URLCONF.
The View class can call its own functions get (), post (), etc. depending on the type of request, passing the request there.
Example
An example of a simple implementation of a view as a class can be viewed, for example,
here . Below is a complex example — my reworking of an example from
documentation — much more fully illustrating the convenience of writing “views” in the form of classes.
Task
The project requires that any data requested by the client can be returned either as a regular HTML page or as a JSON object, depending on the GET request parameter.
Decision
It is necessary to write a general class of "view", which is able to represent the answer in two forms, and from it inherit all subsequent "views" in the project.
In django, it is implemented
TemplateResponseMixin , the function render_to_response () of which can render the template specified in template_name with the context passed to it as a parameter.
Let's write our admixture, which will return JSON instead of HTML:
class JsonResponseMixin(object): def render_to_reponse(self, context): return http.HttpResponse(self.convert_context_to_json(context), content_type='application/json') def convert_context_to_json(self, context, extract_from_queryset=None): pass
The context conversion function in JSON needs to be written already realizing that this context is itself, therefore it is not implemented.
Now we will write a class that will be able to return both HTML and JSON:
class MixedView(View, JsonResponseMixin, TemplateResponseMixin): def get_context(self, request): pass def get(self, request, *args, **kwargs): context = self.get_context(request) if request.GET.get('format', 'html') == 'json' or self.template_name is None: return JsonResponseMixin.render_to_reponse(self, context) else: return TemplateResponseMixin.render_to_response(self, context)
The get function overrides the one that is declared in the View. Depending on the GET parameter, the context is rendered into one of two impurities, and returns JSON or HTML. In the first case, you need a function that determines the translation of this context in json. In the second case, you will need the name of the template in which this context is rendered. Any “view” is the heir of this one, should have two methods implementation: get_context (request) - which forms the context, and convert_context_to_json (context) - which translates it into JSON, and a specific template_name.
The implementation of the final view could be, for example, like this:
class PublisherView(MixedView): def get_context(self, request): context = dict() context['publishers'] = Publisher.objects.all() return context template_name = 'publisher_list.html' def convert_context_to_json(self, context): json_context = dict() json_context['publisher_names'] = [p.name for p in context['publishers']] return json.dumps(json_context, encoding='utf-8', ensure_ascii=False)
Conclusion
In this article, I tried to clarify what I lacked in the Django documentation - the ideas behind class-based views and how to use them. I hope the practice of such ubiquitous use of generic views and their subsequent expansion, when necessary, will seem useful to someone.
Thank you for your attention, I will be glad to any criticism.