Class Based Views (CBV) is one of my favourite things about Django. During my first Django projects (using Django 1.4 around 6 years ago) I was mainly using functional views — that’s what the tutorial recommended then anyway. However, slowly in my next projects I started reducing the amount of functional views and embracing CBVs, slowly understanding their usage and usefulness. Right now, I more or less only use CBVs for my views; even if sometimes it seems more work to use a CBV instead of a functional one I know that sometime in the future I’d be glad that I did it since I’ll want to re-use some view functionality and CBVs are more or less the only way to have DRY views in Django.

I’ve heard various rants about them, mainly that they are too complex and difficult to understand and use, however I believe that they are not really difficult when you start from the basics. Even if it is a little work to become comfortable with the CBV logic, when they are used properly they will greatly improve your Django experience so it is definitely worth it.

Notice that to properly understand CBVs you must have a good understanding of how Python’s (multiple) inheritance and MRO work. Yes, this is a rather complex and confusing thing but I’ll try to also explain this as good as I can to the first chapter of this article so if you follow along you shouldn’t have any problems.

This guide has four parts:

I’ve implemented an accompanying project to this article which you can find at https://github.com/spapas/cbv-tutorial. This project has two separate parts. One that is the implementation of the Custom Class Based View variant to see how it is working and the other is the application that contains the usage of the various CBV use cases.

A gentle introduction to CBVs

In this part of the guide we’ll do a gentle introduction to how CBVs work by implementing our own class based views variant - along with it we’ll introduce and try to understand some concepts of python (multiple) inheritance and how it applies to CBVs.

Before continuing, let’s talk about the concept of the “view” in Django: Django is considered an MVT (Model View Template) framework - the View as conceived by Django is not the same as the MVC-View. A Django View is more or less a way to define the data that the Template (which is cloased to the MVC-View) will display, so the Django View (with the help of the Django Framework) is similar to the MVC-Controller.

In any case, traditionally a view in Django is a normal python function that takes a single parameter, the request object and must return a response object (notice that if the view uses request parameters for example the id of an object to be edited they will also be passed to the function). The responsibility of the view function is to properly parse the request parameters and construct the response object - as can be understood there is a lot of work that need to be done for each view (for example check if the method is GET or POST, if the user has access to that page, retrieve objects from the database, crate a context dict and pass it to the template to be rendered etc).

Now, since functional views are simple python functions it is not easy to override, reuse or extend their behaviour. There are more or less two methods for this: Use function decorators or pass extra parameters when adding the view to your urls. I’d like to point out here that there’s a third method for code-reuse: Extracting functionality to common and re-usable functions or classes that will be called from the functional views but this is not something specific to Django views but a general concept of good programming style which you should follow anyway.

The first one uses python decorators to create a functional view that wraps the initial one. The new view is called before the initial one, adds some functionality (for example check if the current user has access, modify request parameters etc), calls the initial one which will return a response object, modify the response if needed and then return that. This is how login_required works. Notice that by using decorators you can change things before and after the original view runs but you can’t do anything about the way the original view works.

For the second one (adding extra view parameters) you must write your function view in a way which allows it to be reused, for example instead of hard-coding the template name allow it to be passed as a parameter or instead of using a specific form class for a form make it configurable through a parameter. Then, when you add this function to your urls you will pass different parameters depending on how you want to configure your view. Using this method you can override the original function behaviour however there’s a limit to the number of parameters you can allow your function views to have and notice that these function views cannot be further overridden. The login authentication view (which is now deprecated in favour of a CBV one) is using this technique, for example you can pass it the template name that will be used, a custom authentication form etc.

It should be obvious that both these methods have severe limitations and do not allow you to be as DRY as you should be. When using the wrapped views you can’t actually change the functionality of the original view (since that original function needs to be called) but only do things before and after calling it. Also, using the parameters will lead to spaghetti code with multiple if / else conditions in order to take into account the various cases that may arise. All the above lead to very reduced re-usability and DRYness of functional views - usually the best thing you can do is to gather the common things in external normal python functions (not view functions) that could be re-used from other functional views as already discussed.

Class based views solve the above problem of non-DRY-ness by using the well known concept of OO inheritance: The view is defined from a class which has methods for implementing the view functionality - you inherit from that class and override the parts you want so the inherited class based view will use the overridden methods instead of the original ones. You can also create re-usable classes (mixins) that offer a specific functionality to your class based view by implementing some of the methods of the original class. Each one of your class based views can inherit its functionality from multiple mixins thus allowing you to define a single class for each thing you need and re-using it everywhere. Notice of course that this is possible only if the CBVs are properly implemented to allow overriding their functionality. We’ll see how this is possible in the next section.

Hand-made CBVs To make things more clear we’ll start implementing our own class based views hierarchy. Here’s a rather naive first try: class CustomClassView : context = [] header = '' def __init__ ( self , ** kwargs ): self . kwargs = kwargs for ( k , v ) in kwargs . items (): setattr ( self , k , v ) def render ( self ): return """ <html> <body> <h1>{header}</h1> {body} </body> </html> """ . format ( header = self . header , body = '<br />' . join ( self . context ), ) @classmethod def as_view ( cls , * args , ** kwargs ): def view ( request , ): instance = cls ( ** kwargs ) return HttpResponse ( instance . render ()) return view This class can be used to render a simple HTML template with a custom header and a list of items in the body (named context ). There are two things to notice here: The __init__ method (which will be called as the object’s constructor) will assign all the keyword arguments ( kwargs ) it receives as instance attributes (for example CustomClassView(header='hello') will create an instance with 'hello' as its header attribute). The as_view is a class method (i.e it can be called directly on the class without the need to instantiate an object for example you can call CustomClassView.as_view() ) that defines and returns a traditional functional view (named view ) that will be used to actually serve the view. The returned functional view is very simple - it just instantiates a new instance (object) of CustomClassView passing the kwargs it got in the constructor and then returns a normal HttpResponse with the instance’s render() result. This render() method will just output some html using the instance’s header and context to fill it. Notice that the instance of the CustomClassView inside the as_view class method is not created using CustomClassView(**kwargs) but using cls(**kwargs) - cls is the name of the class that as_view was called on and is actually passed as a parameter for class methods (in a similar manner to how self is passed to instance methods). This is important to instantiate an object instance of the proper class. For example, if you created a class that inherited from CustomClassView and called its as_view method then when you use the cls parameter to instantiate the object it will correctly create an object of the inherited class and not the base one (if on the other hand you had used CustomClassView(**kwargs) to instantiate the instance then the as_view method of the inheriting classes would instantiate instances of CustomClassView so inheritance wouldn’t really work!). To add the above class method in your urls, just use its as_view() as you’d normally use a functional view: from django.conf.urls import include , url from . import views urlpatterns = [ url ( r '^ccv-empty/$' , views . CustomClassView . as_view (), name = 'ccv-empty' ), # ... other urls ] This doesn’t actually render anything since both header and context are empty on the created instance — remember that as_view returns a functional view that instantiates a CustomClassView object and returns an HttpResponse filling it with the object’s render() results. To add some output we can either create another class that inherits from CustomClassView or initialize the attributes from the constructor of the class (using the kwargs functionality described above). The inherited class can just override the values of the attributes: class InheritsCustomClassView ( CustomClassView , ): header = "Hi" context = [ 'test' , 'test2' ] And then just add the inherited class to your urls as before: url ( r '^ccv-inherits/$' , views . InheritsCustomClassView . as_view (), name = 'ccv-inherits' ), The as_view() method will create an instance of InheritsCustomClassView that has the values configured in the class as attributes and return its render() output as response. The other way to configure the attributes of the class is to pass them to the as_view class method (which in turn will pass them to the instances constructor which will set the attributes in the instance). Here’s an example: url ( r '^ccv-with-values/$' , views . CustomClassView . as_view ( header = 'Hello' , context = [ 'hello' , 'world' , ], footer = 'Bye' , ), name = 'ccv-with-values' ), The above will create a CustomClassView instance with the provided values as its attributes. Although this method of configuration is used in normal django CBVs (for example setting the template_name in a TemplateView ) I recommend you avoid using it because passing parameters to the as_view method pollutes the urls.py with configuration that (at least in my opinion) should not be there (and there’s no reason to have to take a look at both your urls.py and your views.py to understand the behavior of your views) and also, even for very simple views I know that after some time I’ll need to add some functionality that cannot be implemented by passing the parameters so I prefer to bite the bullet and define all my views as inherited classes so it will be easy for me to further customize them later (we’ll see how this is done in a second). Thus, even if you have In any case, I won’t discuss passing parameters to the as_view method any more, so from now on any class based views I define will be added to urls py using ClassName.as_view() without any parameters to the as_view() class method.

Is this really DRY ? Let’s now suppose that we wanted to allow our class based view to print something on the header even if no header is provided when you configure it. The only way to do it would be to re-define the render method like this: def render ( self ): header = self . header if self . header else "DEFAULT HEADER" return """ <html> <body> <h1>{header}</h1> {body} </body> </html> """ . format ( header = header , body = '<br />' . join ( self . context ), ) This is definitely not the DRY way to do it because you would need to re-define the whole render method. Think what would happen if you wanted to print "ANOTHER DEFAULT HEADER" as a default header for some other view - once again re-defining render ! In fact, the above CustomClassView is naively implemented because it does not allow proper customization through inheritance. The same problems for the header arise also when you need modify the body; for example, if you wanted to add an index number before displaying the items of the list then you’d need to again re-implement the whole render method. If that was our only option then we could just stick to functional views. However, we can do much better if we define the class based view in such a way that allows inherited classes to override methods that define specific parts of the functionality. To do this the class-based-view must be properly implemented so each part of its functionality is implemented by a different method. Here’s how we could improve the CustomClassView to make it more DRY: class BetterCustomClassView ( CustomClassView , ): def get_header ( self , ): print ( "Better Custom Class View" ) return self . header if self . header else "" def get_context ( self , ): return self . context if self . context else [] def render_context ( self ): context = self . get_context () if context : return '<br />' . join ( context ) return "" def render ( self ): return """ <html> <body> <h1>{header}</h1> {body} </body> </html> """ . format ( header = self . get_header (), body = self . render_context (), ) So what happens here? First of all we inherit from CustomClassView to keep the as_view method which doesn’t need changing. Beyond this, the render uses methods ( get_header and render_context ) to retrieve the values from the header and the body - this means that we could re-define these methods to an inherited class in order to override what these methods will return. Beyond get_header and render_contex I’ve added a get_context method that is used by render_context to make this CBV even more re-usable. For example I may need to configure the context (add/remove items from the context i.e have a CBV that adds a last item with the number of list items to the list to be displayed). Of course this could be done from render_context but this means that I would need to define my new functionality (modifying the context items) and re-defining the context list formatting. It is much better (in my opinion always) to keep properly separated these things. Now, the above is a first try that I created to mainly fulfil my requirement of having a default header and some more examples I will discuss later (and keep everything simple enough). You could extract more functionality as methods-for-overriding, for example the render method could be written like this: def render ( self ): return self . get_template () . format ( header = self . get_header (), body = self . render_context (), ) and add a get_template method that will return the actual html template. There’s no hard rules here on what functionality should be extracted to a method (so it could be overridden) however I recommend to follow the YAGNI rule (i.e implement everything as normal and when you see that some functionality needs to be overridden then refactor your code to extract it to a separate method). Let’s see an example of adding the default header functionality by overriding get_header : class DefaultHeaderBetterCustomClassView ( BetterCustomClassView , ): def get_header ( self , ): return self . header if self . header else "DEFAULT HEADER" Classes inheriting from DefaultHeaderBetterCustomClassView can choose to not actually define a header attribute so "DEFAULT HEADER" will be printed instead. Keep in mind that for DefaultHeaderBetterCustomClassView to be actually useful you’ll need to have more than one classes that need this default-header functionality (or else you could just set the header attribute of your class to "DEFAULT HEADER" - this is not user generated input, this is your source code!).

Re-using view functionality We have come now to a crucial point in this chapter, so please stick with me. Let’s say that you have more than one class based views that contain a header attribute. You want to include the default header functionality on all of them so that if any view instantiated from these class based views doesn’t define a header the default string will be output (I know that this may be a rather trivial example but I want to keep everything simple to make following easy - instead of the default header the functionality you want to override may be adding stuff to the context or filtering the objects you’ll retrieve from the database). To re-use this default header functionality from multiple classes you have two options: Either inherit all classes that need this functionality from DefaultHeaderBetterCustomClassView or extract the custom get_header method to a mixin and inherit from the mixin. A mixin is a class not related to the class based view hierarchy we are using - the mixin inherits from object (or from another mixin) and just defines the methods and attributes that need to be overridden. When the mixin is mixed with the ancestors of a class its functionality will be used by that class (we’ll see how shortly). So the mixin will only define get_header and not all other methods like render , get_context etc. Using the DefaultHeaderBetterCustomClassView is enough for some cases but for the general case of re-using the functionality you’ll need to create the mixin. Let’s see why: Suppose that you have a base class that renders the header and context as JSON instead of the HTML template, something like this: class JsonCustomClassView : def get_header ( self , ): return self . header if self . header else "" def get_context ( self , ): return self . context if self . context else [] @classmethod def as_view ( cls , * args , ** kwargs ): def view ( request , ): instance = cls ( ** kwargs ) return HttpResponse ( json . dumps ({ 'header' : instance . get_header (), 'context' : instance . get_context (), })) return view Notice that this class does not inherit from our previous hierarchy (i.e does not inherit from BetterCustomClassView) but from object since it provides its own as_view method. How could we re-use default header functionality in this class (without having to re-implement it)? One solution would be to create a class that inherits from both JsonCustomClassView and DefaultHeaderBetterCustomClassView using something like # OPTION 1 class DefaultHeaderJsonCustomClassView ( DefaultHeaderBetterCustomClassView , JsonCustomClassView ): pass # OR # OPTION 2 class JsonDefaultHeaderCustomClassView ( JsonCustomClassView , DefaultHeaderBetterCustomClassView ): pass What will happen here? Notice that the methods get_header and as_view exist in both ancestor classes! So which one will be used in each case? Actually, there’s a (rather complex) rule for that called MRO (Method Resolution Order). The MRO is also what can be used to know which get_header and as_view will be used in each case in the previous example.

Interlude: An MRO primer What is MRO? For every class that Python sees, it tries to create a list (MRO list) of ancestor classes containing that class as the first element and its ancestors in a specific order I’ll discuss in the next paragraph. When a method of an object of that specific class needs to be called, then the method will be searched in the MRO list (from the first element of the MRO list i.e. starting with the class itself) - when a class is found in the list that defines the method then that method instance (i.e. the method defined in this class) will be called and the search will stop (careful readers: I haven’t yet talked about super so please be patient). Now, how is the MRO list created? As I explained, the first element is the class itself. The second element is the MRO of the leftmost ancestor of that object (so MRO will run recursively on each ancestor), the third element will be the MRO of the ancestor right next to the leftmost ancestor etc. There is one extra and important rule: When a class is found multiple times in the MRO list (for example if some elements have a common ancestor) then only the last occurrence in the list will be kept - so each class will exist only once in the MRO list. The above rule implies that the rightmost element in every MRO list will always be object - please make sure you understand why before continuing. Thus, the MRO list for DefaultHeaderJsonCustomClassView defined in the previous section is (remember, start with the class to the left and add the MRO of each of its ancestors starting from the leftmost one): [DefaultHeaderJsonCustomClassView, DefaultHeaderBetterCustomClassView, BetterCustomClassView, CustomClassView, JsonCustomClassView, object] , while for JsonDefaultHeaderCustomClassView is [JsonDefaultHeaderCustomClassView, JsonCustomClassView, DefaultHeaderBetterCustomClassView, BetterCustomClassView, CustomClassView, object] . What this means is that for DefaultHeaderJsonCustomClassView the CustomClassView.as_view() and DefaultHeaderBetterCustomClassView.get_header() will be used (thus we will not get the JSON output) and for JsonDefaultHeaderCustomClassView the JsonCustomClassView.as_view() and JsonCustomClassView.get_header() will be used (so we won’t get the default header functionality) - i.e none of those two options will result to the desired behaviour. Let’s try an example that has the same base class twice in the hierarchy (actually the previous examples also had a class twice in the hierarchy - object but let’s be more explicit). For this, we’ll create a DefaultContextBetterCustomClassView that returns a default context if the context is empty (similar to the default header functionality). class DefaultContextBetterCustomClassView ( BetterCustomClassView , ): def get_context ( self , ): return self . context if self . context else [ "DEFAULT CONTEXT" ] Now we’ll create a class that inherits from both DefaultHeaderBetterCustomClassView and DefaultContextBetterCustomClassView : class DefaultHeaderContextCustomClassView ( DefaultHeaderBetterCustomClassView , DefaultContextBetterCustomClassView ): pass Let’s do the MRO for the DefaultHeaderContextCustomClassView class: Initially, the MRO will be the following: Starting with the initial class 1. DefaultHeaderContextCustomClassView Follows the leftmost class (DefaultHeaderBetterCustomClassView) MRO 2. DefaultHeaderBetterCustomClassView, 3. BetterCustomClassView, 4. CustomClassView, 5. object And finally the next class (DefaultContextBetterCustomClassView) MRO 6. DefaultContextBetterCustomClassView, 7. BetterCustomClassView, 8. CustomClassView, 9. object Notice that classes BetterCustomClassView , CustomClassView and object are repeated two times (on place 3,4,5 and 7,8,9) thus only their last (rightmost) occurrences will be kept in the list. So the resulting MRO is the following (3,4,5 are removed): [DefaultHeaderContextCustomClassView, DefaultHeaderBetterCustomClassView, DefaultContextBetterCustomClassView, BetterCustomClassView, CustomClassView, object] One funny thing here is that the DefaultHeaderContextCustomClassView will actually work properly because the get_header will be found in DefaultHeaderBetterCustomClassView and the get_context will be found in DefaultContextBetterCustomClassView so this result to the correct functionality. Yes it does work but at what cost? Do you really want to do the mental exercise of finding out the MRO for each class you define to see which method will be actually used? Also, what would happen if the DefaultHeaderContextCustomClassView class also had a get_context method defined (hint: that get_context would be used and the get_context of DefaultContextBetterCustomClassView would be ignored). Before finishing this interlude, I’d like to make a confession: The Python MRO algorithm is not as simple as than the procedure I described. It uses an algorithm called C3 linearization which seems way too complex to start explaining or understanding if you not a CS student. What you’ll need to remember is that the procedure I described works fine in normal cases when you don’t try to do something stupid. Here’s a post that explains the theory more. However if you follow along my recommendations below you won’t have any problems with MRO, actually you won’t really need to use the MRO that much to understand the method calling hierarchy.

Using mixins for code-reuse The above explanation of MRO should convince you that you should avoid mixing hierarchies of classes - if you are not convinced then wait until I introduce super() in the next section and I guarantee that you’ll be! So, that’s why I propose implementing common functionality that needs to be re-used between classes only with mixins (hint: that’s also what Django does). Each re-usable functionality will be implemented in its own mixin; class views that need to implement that functionality will just inherit from the mixin along with the base class view. Each one of the view classes you define should inherit from one and only one other class view and any number of mixins you want. Make sure that the view class is rightmost in the ancestors list and the mixins are to the left of it (so that they will properly override its behaviour; remember that the methods of the ancestors to the left are searched first in the MRO list — and the methods of the defined class have of course the highest priority since it goes first in the MRO list). Let’s try implementing the proposed mixins for a default header and context: class DefaultHeaderMixin : def get_header ( self , ): return self . header if self . header else "DEFAULT HEADER" class DefaultContextMixin : def get_context ( self , ): return self . context if self . context else [ "DEFAULT CONTEXT" ] and all the proposed use cases using the base class view and the mixins: class DefaultHeaderMixinBetterCustomClassView ( mixins . DefaultHeaderMixin , BetterCustomClassView ): pass class DefaultContextMixinBetterCustomClassView ( mixins . DefaultContextMixin , BetterCustomClassView ): pass class DefaultHeaderContextMixinBetterCustomClassView ( mixins . DefaultHeaderMixin , mixins . DefaultContextMixin , BetterCustomClassView ): pass class JsonDefaultHeaderMixinCustomClassView ( mixins . DefaultHeaderMixin , JsonCustomClassView ): pass I believe that the above definitions are self-documented and it is very easy to know which method of the resulting class will be called each time: Start from the main class and if the method is not found there continue from left to right to the ancestor list; since the mixins do only one thing and do it well you’ll know what each class does simply by looking at its definition.

The super situation The final (and most complex) thing and extension I’d like to discuss for our custom class based views is the case where you want to use the functionality of more than one mixins for the same thing. For example, let’s suppose that we had a mixin that added some data to the context and a different mixing that added some different data to the context. Both would use the get_context method and you’d like to have the context data of both of them to your context. But this is not possible using the implementations above because when a get_context is found in the MRO list it will be called and the MRO search will finish there! So how could we add the functionality of both these mixins to a class based view? This is the same problem as if we wanted to inherit from a mixin (or a class view) and override one of its methods but also call its parent (overridden) method for example to get its output and use it as the base of the output for the overridden method. Both these situations (re-use functionality of two mixins with the same method or re-use functionality from a parent method you override) are the same because what stays in the end is the MRO list. For example say we we had the following base class class V:pass and we wanted to override it either using mixins or by using normal inheritance. When using mixins for example like this: class M1:pass class M2:pass class MIXIN(M2, M1, V):pass we’ll have the following MRO: # MIXIN.mro() # [MIXIN, M2, M1, V, object, ] while when using inheritance like this: class M1V(V):pass class M2M1V(M1V):pass class INHERITANCE(M2M1V):pass we’ll have the following MRO: # INHERITANCE .mro() # [ INHERITANCE , M2M1V , M1V , V, object ] As we can see in both cases the base class V is the last one (just next to object) and between this class and the one that needs the functionality ( MIXIN in the first case and INHERITANCE in the second case) there are the classes that will define the extra functionality that needs to be re-used: M2 and M1 (start from left to right) in the first case and M2M1V and M1V (follow the inheritance hierarchy) in the second case. So in both cases when calling a method they will be searched the same way using the MRO list and when the method is found it will be executed and the search will stop. But what if we needed to re-use some method from V (or from some other ancestor) and a class on the left of the MRO list has the same method? The answer, as you should have guessed by now if you have some Python knowledge is super() . The super method can be used by a class method to call a method of its ancestors respecting the MRO. Thus, running super().x() from a method instance will try to find method x() on the MRO ancestors of this instance even if the instance defines the “x()“ method i.e it will not search the first element of the MRO list. Notice that if the x() method does not exist in the headless-MRO chain you’ll get an attribute error. So, usually, you’ll can super().x() from inside the x() method to call your parent’s (as specified by the MRO list) same-named method and retrieve its output. Let’s take a closer look at how super() works using a simple example. For this, we’ll define a method calld x() on all classes of the previous example: class V : def x ( self ): print ( "From V" ) class M1 : def x ( self ): super () . x () print ( "From M1" ) class M2 : def x ( self ): super () . x () print ( "From M2" ) class MIXIN ( M2 , M1 , V ): def x ( self ): super () . x () print ( "From MIXIN" ) class M1V ( V ): def x ( self ): super () . x () print ( "From M1V" ) class M2M1V ( M1V ): def x ( self ): super () . x () print ( "From M2M1V" ) class INHERITANCE ( M2M1V ): def x ( self ): super () . x () print ( "From INHERITANCE" ) print ( "MIXIN OUTPUT" ) MIXIN () . x () print ( "INHERITANCE OUTPUT" ) INHERITANCE () . x () Here’s the output: MIXIN OUTPUT From V From M1 From M2 From MIXIN INHERITANCE OUTPUT From V From M1V From M2M1V From INHERITANCE Notice when each message is printed: Because x() first calls its super() method and then it prints the message in both cases first the From V message is printed from the base class and then from the following classes in the hierarchy (as per the MRO) ending with the class of the instance (either MIXIN or INHERITANCE ). Also the print order is the same in both cases as we’ve already explained. Please make sure you understand why the output is like this before continuing.

Using super in our hierarchy Using super and mixins it is easy to mix and match functionality to create new classes. Of course, super can be used without mixins when overriding a method from a class you inherit from and want to also call your ancestor’s method. Here’s how we could add a prefix to the header: class HeaderPrefixMixin : def get_header ( self , ): return "PREFIX: " + super () . get_header () and here’s how it could be used: class HeaderPrefixBetterCustomClassView ( mixins . HeaderPrefixMixin , BetterCustomClassView ): header = 'Hello!' This will retrieve the header from the ancestor and properly print the header displaying both PREFIX and Hello. What if we wanted to re-use the default header mixin? First let’s change DefaultHeaderMixin to properly use super() : class DefaultHeaderSuperMixin : def get_header ( self , ): return super () . get_header () if super () . get_header () else "DEFAULT HEADER" class HeaderPrefixDefaultBetterCustomClassView ( mixins . HeaderPrefixMixin , mixins . DefaultHeaderSuperMixin , BetterCustomClassView ): pass Notice the order of the ancestor classes. The get_header() of HeaderPrefixMixin will be called which will call the get_header() of DefaultHeaderSuperMixin (which will call the get_header() of BetterCustomClassView returning None ). So the result will be "PREFIX: DEFAULT HEADER" . However if instead we had defined this class like class HeaderPrefixDefaultBetterCustomClassView ( mixins . DefaultHeaderSuperMixin , mixins . HeaderPrefixMixin , BetterCustomClassView ): pass the result would be "PREFIX: " (DEFAULT HEADER won’t be printed). Can you understand why? One thing to keep in mind is that most probably you’ll need to call super() and return its output when you override a method. Even if you think that you don’t need to call it for this view or mixin, you may need it later from some other view or mixin that inherits from this view. Also notice that super() may not return anything but may have some side-effects in your class (for example set a self attribute) which you won’t get if you don’t call it! For another example of super, let’s define a couple of mixins that add things to the context: class ExtraContext1Mixin : def get_context ( self , ): ctx = super () . get_context () ctx . append ( 'data1' ) return ctx class ExtraContext2Mixin : def get_context ( self , ): ctx = super () . get_context () ctx . insert ( 0 , 'data2' ) return ctx The first one retrieves the ancestor context list and appends 'data1' to the it while the second one will insert 'data2' to the start of the list. To use these mixins just add them to the ancestor list of your class hierarchy as usually. One interesting thing to notice here is that because of how get_context is defined we’ll get the same output no matter the order of the mixins in the hierarchy since ExtraContext1Mixin will append data1 to the end of the context list and the ExtraContext2Mixin will insert data2 to the start of the context list. class ExtraContext12BetterCustomClassView ( mixins . ExtraContext1Mixin , mixins . ExtraContext2Mixin , BetterCustomClassView ): pass class ExtraContext21BetterCustomClassView ( mixins . ExtraContext2Mixin , mixins . ExtraContext1Mixin , BetterCustomClassView ): pass If instead both of these mixins appended the item to the end of the list, then the output would be different depending on the ancestor order. Of course, since we’ve already defined HeaderPrefixMixin and DefaultHeaderSuperMixin nothing stops us from using all those mixins together! class AllTogetherNowBetterCustomClassView ( mixins . HeaderPrefixMixin , mixins . DefaultHeaderSuperMixin , mixins . ExtraContext1Mixin , mixins . ExtraContext2Mixin , BetterCustomClassView ): pass This will have the desired behaviour of adding a prefix to the header, having a default header if not one was defined and adding the extra context from both mixins!