Browsing all posts tagged django

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.

I ran into a problem at work today with a custom template tag I've written in a Django project. The tag works as follows:

{% if_has_team_role team "role_name_to_check" %}
<!-- block of HTML to be included if true goes here -->
<!-- otherwise, all of this is skipped -->
{% endif_has_team_role %}

I'm using a custom tag here, rather than a simple conditional, because the underlying check is more complicated than should be expressed at the template layer of my code. The problem came when I nested other conditionals in this block:

{% if_has_team_role team "role_name_to_check" %}
  {% if some_other_condition %}
    <!-- a nested element -->
  {% endif %}
{% endif_has_team_role %}

This setup was throwing an error. While searching for a solution to this issue, I stumbled upon this StackOverflow question. Reading the question, it matched the very problem I was having. At the end of the question, I noted that I was the original asker, 5 years ago; ha!

The solution I had accepted back when I asked this was, at best, a workaround. It turns out that a simple typo in the code was to blame, and fixing that typo solves the problem. It's a nice feeling to answer your own question, even if it takes five years to do it.