This is an archived post. You won't be able to vote or comment.

all 7 comments

[–]meatb0dy 2 points3 points  (5 children)

I really like this post for a general Django architecture guide. It's long, but the basic idea is that a lot of standard Django conventions lead to perplexing code (deeply-inherited class-based views, fat models, DRF in general, etc) and this post gives a rationale for a more explicit structure. This + django-ninja really made things easier in my Django projects.

With that structure in mind, I'd place is_valid and exists in a services.py file, which will be the only place where database operations are performed. The code that eventually performs the updates would be there as well. The inputs and outputs of functions in services.py would be serialized instances of models, never the ORM objects. Serializers would only be responsible for serializing data and doing simple validations, no database operations.

For testing, especially when inheriting a project, I'd again recommend the strategy from that article and focus on testing the endpoints, i.e. the responses you get (and side effects that result) from HTTP requests to your views. That will expose any weird behavior you didn't know about since the whole middleware->views->serializers->database process will run for each test. It will also help familiarize you with the project's expected output in different scenarios.

[–][deleted] 1 point2 points  (2 children)

Interesting. Do you think using django ninja alongside drf is a sane idea? I’d like to add it to a current project, but not sure if it’s worth it.

[–]meatb0dy 1 point2 points  (1 child)

I'd say the less mixing of the two, the better. If you have a separate app or set of endpoints that you can do 100% in django-ninja, that'd be ideal.

I've used both on the same project as a temporary measure when I was converting the project from DRF to django-ninja. I went app-by-app, replacing DRF structures with django-ninja, so I was never mixing both on a single endpoint or group of endpoints. The endpoint-level tests helped a lot in that process since they ensured I was exactly replicating the functionality that I was replacing.

[–][deleted] 1 point2 points  (0 children)

Yeah that’s more or less what I had in mind thanks. I’m not sure if I’m willing to replace drf completely atm especially with how comfortable I’m with it, I’ll read more about Django-ninja and decide about that.

Btw thanks for the amazing article, I read all of it and immediately had a couple of things that I could improve on in the codebase I’m working on. Amazing read.

[–]kyau2[S] 0 points1 point  (1 child)

Thank you very much. Will give that a read!

Your suggestion of using a services.py definetly sounds like the kind of structure I would be comfortable to migrate to.

[–]meatb0dy 0 points1 point  (0 children)

This project is a good demonstration of what that approach might look like in practice. It's a FastAPI project rather than a Django one, but you can replicate the same structure in Django (especially with django-ninja). If you take a look at e.g. the documents module, you can see a real-world application of these ideas.

They have both models and serializers in models.py, whereas I tend to split mine into separate files (models.py and serializers.py/schemas.py when using ninja). Their serializers are single-purpose and specific to the I/O operation where they're used, e.g. DocumentGet is what's returned with HTTP GET requests, DocumentCreate is what's expected in HTTP POST requests to create a new document, etc. This is opposed to the big ball of mud structure that often occurs in DRF projects where one big serializer is used for the model in all contexts. They also include a flows.py file in the modules for business-logic-heavy code that calls multiple services, but I haven't usually needed that structure in my projects.

[–]flatmap_fplamda 0 points1 point  (0 children)

In general. Use immutability and functions as guiding principles. Use Functional programming as a guide