Where to put Django Business Logic
Video: Django: Where to put business logic
Notes on abstraction¶
- If you're putting something the database, use as little abstraction as possible
- no DRF modelviewsets
- getting something out of database
- use as much abstraction as needed
Boxes that Django gives us
- Models
- Views/APIs
- Templates
- Forms / Serializers
- Tasks
Models¶
- small @property
- has_started
- has_finished
- validation logic with
clean
Why business logic doesn't belong in Models¶
- fat models aren't maintainable
- God object
Testing Models¶
- custom validations or properties
- tests don't hit db -> fast tests 🏃
Custom Model Managers and QuerySets¶
Django Style guide: business logic should not live in custom managers or query sets
When to use custom model managers and querysets?¶
- like model properties
- use this for simple stuff
- there might be more complex logic like 3rd party API calls
- where you should move the logic to services.py
Custom Model Manager example¶
Custom Model Manager & Model QuerySet
get_queryset
is like Post.objects.all()
QuerySet Manager example¶
Implement your own get_queryset
so that you can
Views & APIs¶
Django Rest Framework (DRF)
need to put it in the serializer
model view set???
Serializers¶
- Python/ORM objects <--> JSON
Why business logic doesn't belong to serializer¶
Adding 1 property --> breaking 5 other APIs because they all use the serializer
Services¶
What is a service¶
- simple, type annotated function
- speaks the domain language
- handles
- permissions
- cross-cutting concerns
- calls other services/tasks
- works mainly with models
selectors.py¶
- business logic around fetching
- not always necessary handle permissions, filtering, ...
Rule of thumb¶
- if you get the N + 1 problem
- If your model property starts doing queries on the model's relations
- which can't be solved with
select_related
orprefetch_related
- it should be a selector
example
class Lecture(models.Model):
@property
def not_present_students(self):
present_ids = self.present_students.values_list('id', flat=True)
return self.course.students.exclude(id__in=present_ids)
Testing services¶
- where the business logic is
- hit the db
- mocking
Services Examples¶
from .selectors import get_courses
class CourseListApi(SomeAuthenticationMixin, APIView):
class OutputSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = ('id', 'name', start_date', 'end_date')
def get(self, request):
courses = get_courses()
data = self.OutputSerializer(courses, many=True)
return Response(data)
from .services import create_course
class CourseCreateApi(SomeAuthMixin, APIView):
class CreateInputSerializer(serializers.Serializer):
name = serializer.CharField()
start_date = serializers.DateField()
end_date = serializers.DateField()
def post(self, request):
serializer = self.CreateInputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
create_course(**serializer.validated_data)
return Response(status=status.HTTP_201_CREATED)
class UpdateInputSerializer(serializers.Serializer):
name = serializer.CharField(required=False)
start_date = serializers.DateField(required=False)
end_date = serializers.DateField(required=False)
Why nested Serializers in the API?¶
We don't want to reuse serializers, especially for InputSerializers
- output serializer is okay sometimes
Editing a base serializer will break a ton of APIs - need to reuse serializers with great care
Last update:
2023-02-03