19

I created a class that subclasses ListView and two custom mixins which have implemented a get_context_data function. I wanted to override this function on the child class:

from django.views.generic import ListView

class ListSortedMixin(object):
    def get_context_data(self, **kwargs):
        print 'ListSortedMixin'
        return kwargs

class ListPaginatedMixin(object):
    def get_context_data(self, **kwargs):
        print 'ListPaginatedMixin'
        return kwargs

class MyListView(ListSortedMixin, ListPaginatedMixin, ListView):
  def get_context_data(self, **context):
    super(ListSortedMixin,self).get_context_data(**context)
    super(ListPaginatedMixin,self).get_context_data(**context)
    return context

When I execute MyListView it only prints "ListSortedMixin". For some reason python is executing ListSortedMixin.get_context_data in place of MyListView.get_context_data. Why?

If I change the inheritance order to ListPaginatedMixin, ListSortedMixin, ListView, ListPaginatedMixin.get_context_data is executed.

How can I override the get_context_data function?

Jorge Barata
  • 2,227
  • 1
  • 20
  • 26

3 Answers3

25

This is an old question, but I believe the answer is incorrect. There is a mistake in your code. It should read:

class MyListView(ListSortedMixin, ListPaginatedMixin, ListView):
    def get_context_data(self, **context):
        super(MyListView,self).get_context_data(**context)
        return context

The order in which the get_context_data will be called follows the same order as specified in the declaration of MyListView. Notice the argument of super is MyListView and not the super classes.

UPDATE:

I missed that your mixins don't call super. They should. Yes, even if they inherit from object, because super calls the next method in the MRO, not necessarily the parent of class it is in.

from django.views.generic import ListView

class ListSortedMixin(object):
    def get_context_data(self, **kwargs):
        print 'ListSortedMixin'
        return super(ListSortedMixin,self).get_context_data(**context)

class ListPaginatedMixin(object):
    def get_context_data(self, **kwargs):
        print 'ListPaginatedMixin'
        return super(ListPaginatedMixin,self).get_context_data(**context)

class MyListView(ListSortedMixin, ListPaginatedMixin, ListView):
    def get_context_data(self, **context):
        return super(MyListView,self).get_context_data(**context)

For MyListView the MRO is then:

  1. MyListView
  2. ListSortedMixin
  3. ListPaginatedMixin
  4. ListView
  5. Whatever is above ListView ... n. object

Calling them one by one may work, but is not how it was intended to be used.

UPDATE 2

Copy and paste example to prove my point.

class Parent(object):
    def get_context_data(self, **kwargs):
        print 'Parent'

class ListSortedMixin(object):
    def get_context_data(self, **kwargs):
        print 'ListSortedMixin'
        return super(ListSortedMixin,self).get_context_data(**kwargs)

class ListPaginatedMixin(object):
    def get_context_data(self, **kwargs):
        print 'ListPaginatedMixin'
        return super(ListPaginatedMixin,self).get_context_data(**kwargs)

class MyListView(ListSortedMixin, ListPaginatedMixin, Parent):
    def get_context_data(self, **kwargs):
        return super(MyListView,self).get_context_data(**kwargs)


m = MyListView()
m.get_context_data(l='l')
Daniel Palm
  • 404
  • 4
  • 7
  • In this case `super(MyListView,self).get_context_data(**context)` is the same as `ListSortedMixin.get_context_data(self, **context)`. I think that [the previous answer](http://stackoverflow.com/a/9939867/959819) is correct: I need to call the parents functions one by one. – Jorge Barata Jul 23 '13 at 16:07
  • The problem is that your mixins don't call super. Even if mixins inherit from `object` they should call super. Super delegates to the next object in the MRO (method resolution order), which depends on the order in which they were are specified in the declaration of MyListView. I will update my answer above to make it more clear. – Daniel Palm Jul 24 '13 at 18:40
  • Exactly. Therefore the called method in your example is only `ListSortedMixin`. I need to call manually all the parents functions. – Jorge Barata Jul 25 '13 at 16:45
  • `super` also calls siblings where necessary. Copy and paste the updated code (UPDATE 2) in your favourite Python shell and convince yourself of the truth. – Daniel Palm Jul 26 '13 at 08:05
10

If what you're trying to do is to call overwritten methods in fixed order. Use this syntax:

class MyListView(ListSortedMixin, ListPaginatedMixin, ListView):
  def get_context_data(self, **context):
    ListSortedMixin.get_context_data(self, **context)
    ListPaginatedMixin.get_context_data(self, **context)
    return context

Super won't work in this case. See the manual for super(type[, object]):

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped.

There are two typical use cases for super. In a class hierarchy with single inheritance, super can be used to refer to parent classes without naming them explicitly, thus making the code more maintainable. This use closely parallels the use of super in other programming languages.

The second use case is to support cooperative multiple inheritance in a dynamic execution environment. This use case is unique to Python and is not found in statically compiled languages or languages that only support single inheritance. This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method. Good design dictates that this method have the same calling signature in every case (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime).

So argument of super is the class whose parent or sibling class proxy you want to get. super(ListSortedMixin,self).get_context_data(**context) won't necessarily call get_context_data of ListSortedMixin. It depends on the method resolution order (MRO), which you can get using print MyListView.__mro__

So super() will call get_context_data of parent or sibling. The order of execution adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime.

Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162
1

I find that most people who are newer to Python and are on here looking for answers are going to be using Python 3 and not 2, so here is a small update.

class Parent(object):
    def get_context_data(self, **kwargs):
        print('Parent')

class ListSortedMixin(object):
    def get_context_data(self, **kwargs):
        print('ListSortedMixin')
        return super().get_context_data(**kwargs)

class ListPaginatedMixin(object):
    def get_context_data(self, **kwargs):
        print('ListPaginatedMixin')
        return super().get_context_data(**kwargs)

class MyListView(ListSortedMixin, ListPaginatedMixin, Parent):
    def get_context_data(self, **kwargs):
        return super().get_context_data(**kwargs)


m = MyListView()
m.get_context_data(l='l')
Nate Hawk
  • 41
  • 3