Managers and Querysets for business logic

Yesterday we considers Models as the place for business logic as it avoids duplication, but the reality is the abstraction isn't quite right for every case. This leads us to custom Managers & Querysets, where we can add our own methods to perform the necessary logic and can be called from most places in a project.

Both are very similar abstractions for working the database via the Django ORM. As I understand it the main practical difference between the two is that QuerySets methods can be chained and Managers cannot (since they return QuerySets). Below is an example of this. Thanks to Mariusz for this example

# Manager
>>> Person.objects.experienced().with_extra_fields()
...
AttributeError: 'QuerySet' object has no attribute 'with_extra_fields'

# QueySet
>>> Person.objects.experienced().with_extra_fields().get().full_name
'Joe Doe'
>>> Person.objects.experienced().number_of_unique_names()
1
>>> Person.objects.number_of_unique_names()
2

Managers & QuerySets have a defined API which allows for returning multiple instances or a single instance, but they don't allow for working with a single instance as an input, unless you pass it in as a parameter. This breaks the chain as illustrated above though. One solution is to define similar functions on both a QuerySet and the Model, which does work, but does lead to some duplication which may even be harder to maintain in the long run.