One of my first projects in my current role was to optimize a process that involved taking a hardware design and making predictions about it. We had some old Excel sheets that would keep track of old analyses but these didn’t really enforce any structure and lots of work had to be redone each time a new design was created.
Since I’m a web person my initial step was to build a Django app that maintained a database of predictions so we could create new designs and apply the predictions that didn’t change.
Decisions are expensive – try to never make the same one twice.
This worked out pretty well, Django’s ORM and migration management meant that new features could be added incredibly fast. I had never really been a fan of the template engine however, mainly because I’ve been too lazy to configure my IDE to handle them well.
To solve this issue, I separated Django from the frontend by creating a React app using MUI (looks plenty good enough for an internal tool) and linking them with NGINX so that a couple docker-compose scripts could create dev, staging, and production environments with one liners.
My Django views changes a little bit, returning JsonReponse models and data provided by .to_json() methods I created for each of my models. These worked out with my function-based views (also never a fan of class-based views) and everything was good for a while.
Django-REST-Framework
Every couple of months I would try to get comfortable with Django-REST-Framework but the focus on class-based views always kept me away. Now that I finally had a Django-based backend feeding a frontend it felt like a good time to revisit the tool.
In my initial forays I never realized the DRF offered an `api_view` decorator that could be applied to function-based views. This gave a nice, low-effort way to step into a project migration. I looked around a couple different guides but the best plan seemed to be from the DRF tutorial, so I tried it out and the following steps worked pretty well:
Phase 1:
- Install and activate DRF
- Add the api_view decorator to all my function-based views
- Replace HttpResponse and JsonResponse with DRF’s Response
- Replace request.body and request.POST with request.data
This was a pretty trivial exercise. The views all worked pretty much the same way – I had most of my business logic in my models already (phew). An immediate benefit was being able to include allowed HTTP verbs in the decorator; I could get rid of my catch all else statement that return an HTTP 405 and showed which methods were allowed. The relatively useless csrf_exempt decorators could also be removed (this is not a secure app by design).
This also ignores that all responses were json, eliminating the issue with HTML responses for different unhappy-path errors that would need to be caught on the frontend.
Phase 2:
- Replace my to_json() methods with serializers
A bit more tedious but very satisfying. I had already created “detail” and “list” style methods for the parent models and these mapped over to the DRF ModelSerializers pretty well. It’s much more fun typing out the field names once instead of {“description”: self.description…} over and over. This additionally helped on the create and update methods as well, removing dozens of lines of code that didn’t really add any value.
My only hiccup was I had been converting snake_case model fields to camelCase in my json output – apparently this can be applied easily with a third party extension but it was trivial to go to my interfaces on the frontend and change the object keys to snake_case.
Note: still having some issues with parent models not automatically pulling their children serializers out – it might be more due to how I structured my models but using DRF’s SerializerMethodField makes it trivial to apply some logic to pulling out whatever I want
Phase 3
- Consolidate function-based views to class-based
The big one. I’m actually still in the process, it’s been going smoothly but is a bit more work reading the docs for the weird corner cases that need to be addressed. I think having this API-first mentality has made it easier for my brain to wrap itself around the class-based approach. For some reason serializers seem to fit better than templates, especially since a lot of the operations boil down to “find this item(s), update if needed, and return”
The consolidation started pretty straightforward by extending the APIView class, and then defining the get(), post(), patch(), delete() methods for the list style and detail style views. These line up pretty well to the function-based views, so not much wrangling is needed here.
Once these are in place, it’s a nice, satisfying process of aligning each action to fit its DRF mixin class, which can then replace some of the http method-based methods.
Finally, these mixins and GenericAPIView classes can be consolidated down to the DRF generic classes, eliminating pretty much all the code except plugging the correct query set and serializer into the view. I still have quite a bit of modification in these classes to fit the existing structure but most of this is due to creating the project without thinking about this option. Overtime (and once I’ve got all my tests into idiomatic DRF) I can further structure and refactor the entire app to better fit this.
Conclusion
Overall it’s been a pretty fun experience learning Django REST Framework by migrating an existing project over piece-by-piece. It might have been faster to just start from scratch and import all my existing data but since this tool is used on a daily basis being able to slowly port the features over with no downtime was definitely satisfying. It also gave me a much better understanding of the inner workings of DRF which is what kept me away in the past. You can read about the differences between a serializer saving and a model saving but never really get it until you start breaking things.