'How to deal with model changes in Django Rest Framework?

This is not a question for a particular use case, but for something I noticed in my experience doing APIs, specifically with using Django and Django Rest Framework.

Months ago I had a problem with the API I maintain for a client's project.

Let's say we have the following model:

class Person:
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

Then, the corresponding serializer:

class PersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = '__all__'

Of course, its corresponding ViewSet and route pointing to it:

http://localhost:8000/api/v1/persons/

Note this is the 1st version of my API.

Everything OK at this point, right?

Now, my client asked that we need to receive person's fullname instead of first and last name separately...

As supposed, I'll have to change my model to be:

class Person:
    full_name = models.CharField(max_length=200)

There are 3 different clients (mobile apps) using this version of the API. Obviously I don't want to change my API, instead I will want to put the new approach in a new version.

BUT the 1st version's Serializer is coupled with the Model, so at this point the 1st version of the API already changed

What I expect to read in answers below is how you guys deal with this problem in Django and what is the way I should take for my next projects using the same stack.

Edit: My question's objetive is to understand if is better to decouple API from Models. I've put a very very basic example, but there are cases where things get much more complicated. For example I needed to modify a M2M relation to use the through option in order to add more fields to the intermediate table.



Solution 1:[1]

It's type of question that could been flagged as "Recommend smth", but whatever.

First of all you need to extend your model with full_name field. If you need to be able to write to model provided full_name from your api then you need to extend with field otherwise you can deal property

@property
def full_name(self):
    return '{} {}'.format(self.first_name, self.last_name)

Then you can also include field in serializer

class PersonSerializer(serializers.ModelSerializer):
    full_name = serializers.CharField()  # if you have property
    class Meta:
        model = Person
        fields = '__all__'

Since you class field name and serializer field name would match you don't need to worry.

So having additional field won't break your clients and satisfy your big client.

Solution 2:[2]

It's been a while since you asked this, but it's an interesting question I've been contemplating on recently with the different versions of the OCPI protocol. What you describe here is basically one of the trickier situations, where a data structure needs to be refactored while retaining existing endpoints.

Copying shamelessly parts of the response from @vishes_shell, I would suggest you hide your changing data, provide properties to access it, and split your corresponding serializers and endpoints into two distinct versions.

@property
def full_name(self):
    return self._full_name or '{} {}'.format(self._first_name, self._last_name)

@property
def first_name(self):
   return self._first_name or self.parse_first_name(self._full_name)

@property
def last_name(self):
   return self._last_name or self.parse_last_name(self._full_name)


class PersonV1Serializer(serializers.Serializer):
    first_name = serializers.CharField(max_length=100)
    last_name = serializers.CharField(max_length=100)


class PersonV2Serializer(serializers.Serializer):
    full_name = serializers.CharField(max_length=200)

Create separate views:

http://localhost:8000/api/v1/persons/
http://localhost:8000/api/v2/persons/

This way you have your original and new endpoints working while using shared data. If you wish, you can migrate the data for first_name and last_name into full_name, and use a simpler logic for v1. That might be good for performance if you assume all clients are migrating eventually to v2, but I don't see a big difference here.

You can also find multiple different schemes for endpoint versioning in Django Rest at https://www.django-rest-framework.org/api-guide/versioning/.

Please note that self.parse_first_name() and self.parse_last_name() (which I leave as an exercise for the reader) need to be able to handle missing names. The ModelSerializer is no longer used, since it cannot determine the data types for properties, so we need to provide them explicitly.

Hope this helps all who are battling with API versioning.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 vishes_shell
Solution 2 Erkki Tapola