Monday, July 1, 2013

Custom Managers in Django

I had to add a bit of functionality to the Old Dog Haven "Walk For Old Dogs" registration and sponsorship site today (feel free to sponsor me!), specifically a way for the good folks running the event to see an aggregate total of the sponsorship dollars by registrant.

Here's the Django models for Registrant and Sponsor:
Pretty straight-forward -- main thing to notice is that Sponsor has a foreign key relationship with Registrant as opposed to the other way around. And yes, in its current incarnation sponsors can only sponsor one registrant; if they want to sponsor multiple registrants they simply create another sponsor record. (It's agile baby -- release early and often! Multiple sponsorships can happen next year!)

While calculating the total sponsorship dollars by each registrant could be handled via the Django ORM, I decided to use the opportunity to execute SQL directly in a function in a custom manager.

You can read more about custom managers in the Django docs, but the short version is that when you call something like Foo.objects.all() the objects bit refers to the model's Manager class, and it has all sorts of default functionality built in allowing you to easily interact with your model.

Referring back to the Sponsor and Registrant model code above, notice that since we're using a custom model manager, in the Sponsor model I added the line objects = SponsorManager() to override the default model manager with the custom one for which we'll see the code below.

As with most things in Django, model managers are easily extended and overridden, and in this case I wanted to add a new function to the Sponsor manager to return a list of registrants with a sum of their sponsorship amounts. This was as simple as adding a SponsorManager model that extends model.Manager and adding a function called with_sponsorship_totals to return the information I need.
Again, pretty straight-forward. SponsorManager extends models.Manager, we add the code to run the SQL query we want to execute, loop over the results and add instances of the Registrant model to the results list, and return the results.

One of the other great things I should point out about Python is the ability to add fields to classes on the fly. The Registrant class doesn't have a total_sponsorship_amount or team_name field defined, but I can add those as I loop over my results so they're available in the list returned by the custom manager function.

Another approach to solving this problem might be to add a function to the Registrant model itself that would calculate the total sponsorship amount for each registrant, but I opted to create a custom manager in this case to experiment a little, and I also thought it taking this approach would be more efficient than calculating individually for each registrant.

As you can see from this basic example, creating a custom manager for your Django models is an extremely simple, powerful way to add functionality to your application and is nothing to shy away from. By creating fat models and extending model managers where needed you can keep the business logic where it belongs and avoid a lot of unnecessary complexity in views and other areas of your application.

No comments: