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 ListView
for 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.