0

When developing a REST API using django and django rest framework, it seems to me that there's a step missing at the very end of this chain, especially if you'll design a python client of that API:

  • define the model of your resources in django
  • use DRF's serializers to send your resource over HTTP
  • missing: deserialize your resource back in a python model

Let me give a simple example. I declare the following django model with a bit of business logic:

class Numberplate(models.Model):
    number = models.CharField(max_length=128)
    country_code = models.CharField(max_length=3)

    def __repr__(self):
        return f"{self.number} ({self.country_code})"

DRF is super powerful when it comes to building the API around that model. However, if I call that API from a python client, it seems that the best I can get as a response from that API will be a simple json without all the logic I might have written in my model (in this case, the __repr__ method), and I'll have to navigate through it as such.

Continuing my example, here is the difference with and without a proper deserializer:

# the best we can do without rewriting code:
>> print(api.get(id=3))
{"numberplate": "123ABC", "country_code": "BE"}

# instead of re-using my __repr__, which would be ideal:
>> print(api.get(id=3))
123ABC (BE)

Is there a clean way to stay DRY and write the logic only once?

I've thought about a couple of options which are not satisfactory in my opinion:

  • write a django-model-agnostic Numberplate class and make your model class inherit from both models.Model and that class
  • import your django models in your client (like this)
Florentin Hennecker
  • 1,974
  • 23
  • 37
  • The point of a rest api is that the request and response is the same regardless of the client and source. They are and should be agnostic of one another. How would a python object definition with methods even be sent over http? If you have some logic that is shared between api and client, then by all means, write a library that both use and extend (most like in option 1 in your question). But trying to send the object definition to a client is the wrong path here. – bryan60 Feb 05 '20 at 16:02
  • @bryan60 very good point. I'm not suggesting in any way that any python logic be sent over the network. However, in the case where you want both a REST API and a python API to some service, re-writing all the business logic and deserialization logic for the python client seems wasteful given that it could technically be derived from the server-side models. – Florentin Hennecker Feb 05 '20 at 16:07

1 Answers1

0

One option for you would be to use SerializerMethodField

class NumberplateSerializer(serializers.ModelSerializer):
    display = serializers.SerializerMethodField()

    class Meta:
        model = Nameplate
        fields = [..., 'display']

    def get_display(self, obj):
        return str(obj)
        # Or you can move your business logic here directly.
        # return f"{obj.number} ({obj.country_code})"

Now you should have:

>> print(api.get(id=3))
{"numberplate": "123ABC", "country_code": "BE", "display": "123ABC (BE)"}
schillingt
  • 13,493
  • 2
  • 32
  • 34
  • This is an interesting solution, but it only works if all of the logic you want to re-use is "property-like", e.g. your methods don't take any arguments. If I had a method `get_nth_digit(self, n)`, this would not work. – Florentin Hennecker Feb 05 '20 at 16:02
  • Yup, definitely. You'd have to pass some parameters in from the request to the serializer to allow for that to happen any way. – schillingt Feb 05 '20 at 16:08