Django REST Framework (DRF) is, on the surface, a neat piece of software. It provides web interactivity (for free!) to your REST interfaces, and can make generating those interfaces pretty quick to do. It has baked in authentication and permission handling. With just a few lines of code you're up and running. Or are you?
As you dig deeper into their tutorial, you'll find that this framework is abstraction layer on top of abstraction layer. Using naked Django style views, I could easily write a listing routine for a specific model in my application. Let's take an example model (all code in this post will omit imports, for brevity):
class Person(models.Model):
email = models.CharField(max_length=60, unique=True)
first_name = models.CharField(max_length=60)
last_name = models.CharField(max_length=60)
display_name = models.CharField(blank=True, max_length=120)
manager = models.ForeignKey('self', related_name='direct_reports', on_delete=models.CASCADE)
def __str__(self):
return (self.display_name if self.display_name
else f"{self.first_name} {self.last_name}")
This model is simply a few key pieces of data on a person inside my application. A simple view to get the list of people known to my application might look like this:
class PersonList(View):
def get(self, request):
people = []
for x in Person.objects.select_related('manager').all():
obj = {
'email': x.email,
'first_name': x.first_name,
'last_name': x.last_name,
'display_name': str(x),
'manager': str(x.manager),
}
people.append(obj)
return JsonResponse({'people': people})
This, I would argue, is simple and easy to read. It may be a little verbose for me, the programmer, I admit. But if another programmer comes along behind me, they're fairly likely to understand exactly what's going on here; especially if they are a junior programmer. Maintenance of this code therefore becomes trivial.
Let's now look at a DRF example. First I need a serializer:
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
depth = 1
This looks good, but it doesn't handle the display_name
case correctly, because I want the str()
method output for that field, not the field value itself. The same goes for the manager
field. So I now have to write some field getters for both. Here's the updated serializer code:
class PersonSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField()
manager = serializers.SerializerMethodField()
class Meta:
model = Person
fields = '__all__'
depth = 1
def get_display_name(self, obj):
return str(obj)
def get_manager(self, obj):
return str(obj.manager)
Once my serializer is complete, I still need to set up the view that will be used to actually load the list:
class PersonList(generics.ListAPIView):
queryset = Person.objects.select_related('manager').all()
serializer_class = PersonSerializer
I'll admit, this is pretty lean code. You cannot convince me, however, that it's more maintainable. The junior programmer is going to come in and look at this and wonder:
- Why do only two fields in the serializer have get routines?
- What even is a SerializerMethodField?
- Why is the
depth
value set on this serializer?
- What does the ListAPIView actually return?
- Can I inject additional ancillary data into the response if necessary? If so, how?
DRF feels like it was designed by Java programmers (does anyone else get that vibe?). REST interfaces always have weird edge cases, and I'd much rather handle them in what I would consider the more pythonic way: simple, readble, naked views. After all, according to the Zen of Python:
- Simple is better than complex.
- Readability counts.