Last Updated: February 25, 2016
·
1.463K
· saevarom

Manager order in Django models matters!

Sometimes I want to incorporate some kind of visibility control for models.

class MyModel(models.Model):
    # ... all the fields are here

    ACTIVE='active'
    INACTIVE='inactive'

    STATUS_CHOICES = (
        (ACTIVE, 'Active'),
        (INACTIVE, 'Inactive'),
    )

    status = models.CharField(max_length=255, choices=STATUS_CHOICES, db_index=True)

Often, this requirement is introduced after a lot of views and logic has been written so it is a real pain having to update many queries from MyModel.objects.all() to MyModel.objects.filter(status='active')

The solution is to create a Manager to handle this, but I still want to be able to reference instances with the status 'inactive' somehow, so I'll include a reference to the default Django Manager:

class MyManager(models.Manager):
    def get_query_set(self):
        return super(MyManager, self).get_query_set().filter(status=MyModel.ACTIVE)

class MyModel:
    # other stuff goes here

    unfiltered = models.Manager()
    objects = MyManager()

Here, I ran into a problem. Defining a simple ListViewfor this model will show all models, regardless of their status attribute!

class MyListView(ListView):
    model = MyModel

I start digging into the Django source code and I find that the ListView uses a _default_manager attribute to fetch the queryset. As it turns out, this _default_manager attribute is populated using the first manager defined in the model!

So, reversing the managers solves our problem with the ListView:

class MyModel:
    # other stuff goes here

    objects = MyManager()
    unfiltered = models.Manager()

There is still one problem, though. If you use the Django Admin app to administer your model, you will not see any models with the status of 'inactive'. The solution is simple, you simply override the `get_queryset' method in your admin class:

class MyModelAdmin(admin.ModelAdmin):
    def queryset (self, request):
        qs = MyModel.unfiltered.all()
        ordering = self.get_ordering(request)
        if ordering:
            qs = qs.order_by(*ordering)
        return qs

It is unfortunate that there is no hook in the ModelAdmin to handle the ordering part, but that's life.

So if you have a lot of code depending on using MyModel.objects.filter(), this is a relatively easy way to provide default filtering of your data without changing a lot of code.