* Models being passed around everywhere, queries happening everywhere. I prefer having a dedicated service/selector layer to do those things. Then convert to pydantic objects or something that's passed around further.
* Corollary, but adding stuff to querymanagers quickly goes out of control. Sure, it's nice to reuse MyModel.objects.annotate_something().annotate_something_else().... but it can quickly become unwieldy and even wrong with exploding joins. And it promotes doing queries in places they shouldn't happen.
* It's veeery easy to make spaghetti. Very easy to query across boundaries, into other apps. Fine on smaller projects, but in huge codebases it quickly makes things hard to control, especially since it's all stringly typed. If I want to modify my model, it's hard to know if someone else have done a query where they did theirmodel__some_relation__another_relation__mymodel__some_field. Blows up in production.
* For some reason it's very common in Django/python projects to have types.py, models.py, selectors.py, views.py, services.py etc. And then each of those end up with lots of unrelated things in the same python file, while related stuff is spread over many files. Django apps doesn't really solve this cleanly either.
https://adsharma.github.io/django-fquery/
Your models can be plain old python data classes, declaratively mapped to Django primitives.
[1] https://www.doctrine-project.org/projects/doctrine-collectio... (for collections but you can use them for queries too)
[2] https://www.doctrine-project.org/projects/doctrine-orm/en/3.... / https://www.doctrine-project.org/projects/doctrine-orm/en/3....
And, rather like the petrol engine, it turns out while it sucks, everything else is massively worse in some vitally important way.
Never in my life did I have a problem with lazy loading causing unbearable performance until I joined a Python Django team. I really tried to find sympathy for the "dynamically typed" folks (please spare me saying Python is technically statically typed), but coming from writing apps and backends in Java, Swift, C#, Objective-C and PHP, Python with Django was the worst experience bar none.
I worked on the project for 10 months, could at least refactor the project to something semi-sane where obvious mistakes (which would not be possible in other languages) could not happen. Then along comes a "good Python dev" and threw it all out of the window and start doing SQL queries all over the place (typically 3-6 lines long), remove the domain objects and cause the same problems I started with to begin with. But his approach was saying that the other developers were "not good enough".
Yeah, have fun with schema changes going forward. Good riddance.
I've seen the horrors Java devs start doing on a Python project when trying to "fix" things, where by "fix" they mean use patterns they had to in previous gigs.
It's a different world.
In Django you'd typically use simple classes (models or forms, or even dataclasses nowadays) more than dicts everywhere; n+1 is trivially avoidable (as another sibling comment points out, and you also have multiple packages that autodetect such cases if you've missed them).
Python in general has a more "consenting adults" than "defensive programming" attitude (which doesn't mean exessive coupling or spaghetti, but the approach is different from the Java or C# mindset).
There's no one THE correct style of programming.
No, it doesn't. It's fair to criticize the consequences of this approach to coding.
select_related, prefetch_related. n+1 problems be gone.
We did do that and that's why our queries ended up being several lines long. But if you missed just one model? You openly walk a knife again.
It's a mess and it only gets longer and longer. I ended the project with having some proper aggregates, only for that to be thrown out of the window by the guy after me.
https://docs.djangoproject.com/en/dev/releases/6.1/#model-fi...
That's the great thing about Django, it's been around so long and the quality bar is so high that eventually all the major rough edges get sanded away usually in a really well considered manner.
Which is... perfectly normal for non-trivial needs.
> I ended the project with having some proper aggregates, only for that to be thrown out of the window by the guy after me.
How is that a Django problem though ? Sounds like a skill issue on your successor.
I get what you say, there's plenty of debates about ActiveRecord vs. AnythingElse, but in the end this one has its use and obviously has served many of us just fine. Different strokes... you know the drill.
Then it got to where I had to make a reflective function that I use like Model.objects.defer(*all_fields_except(Model, ['field1', 'field2'])), and then add another all_fields_except() for every select_related and prefetch_related.
Even save() by default re-writes every single field. You have to use save(update_fields=['field1']) instead.
It can take a while to wrap your head around what fields get used in aggregates and the like, but when working with big models with like 65 fields and juggling a bunch of stuff, not having to futz with serialization/deserialization and "just" expressing your problem in the dumb way is nice.
I want to say this all comes back to bite you in the end but honestly it's more just having wide tables that comes to bite you. A service layer wouldn't really save you. Meanwhile you save yourself a bunch of tedium in the mean time
And if you can't make the ORM make the SQL query you want, you can just write it as a SQL query, like this godawful monstrosity:
x = Site.objects.raw("select id, name, lat, lon, 111.045*degrees(acos(cos( \
radians(latpoint))*cos(radians(lat)) \
*cos(radians(lngpoint)-radians(lon)) \
+sin(radians(latpoint))*sin(radians(lat)))) \
as distance from sites_site join \
(select %s as latpoint, %s as lngpoint) as p on 1=1 \
order by distance limit 5", [float(lat), float(lon)])
... which calculates the Haversine distance from where you are now to the five nearest points.I am in roughly equal parts proud of and horrified by this creation.
for function calls, look at django.db.models.functions, you can find a bunch of stuff in there or create custom ones super easily (like "two lines of codes" easily)
I mean you have a thing that works in theory so it's a bit of navel gazing, though.
I was really just going to try and wrap it in a function to stick in the model so I can say something like Site.objects.get(id=thing).distance_from(lat,long) in.
But, now the bit that I was calling that from is actually being done from a websocket handler, and that's written in Go because Django and websockets seems very complex.
How would you have approached it?
Kinda took until Facebook showing off Hacklang in 2014 for people to believe in getting more canonical programming features into PHP and make it more performant. So it would have been a good decision if one could predict 10 years into the future, but nobody can.
> Models being passed around everywhere, queries happening everywhere.
No, as a developer you still need to be 100% aware of the underlying queries and potential performance issues. No excuse for N+1 problems. ORM is not an excuse to be lazy, but I admit it will probably catch quite a few developers.
Those same developers would probably make a mess out of any other framework or technology though.
It definitely takes a bit of discipline. The key layers are somewhat easy to manage—middleware, context processors, views, template tags—but I’ve seen some hairy lasagne further obscuring where the queries happen on top of that. A well-documented abstraction can be useful, but if it is possible to keep it simple and obvious then that’s the way to go.
(Third-party dependencies can further complicate things, but at least you can expect a library using ORM to be in the installed apps list.)
It's highly productive if you do it right.
Every time ORMs get discussed, it seems to be dominated by people who are like "but my app has 5 billion concurrent users doing 2 million requests per second and if there's an extra 5ms on my requests, it will all explode!" and can't comprehend that not everyone is building the same kind of systems all the time. Great, maybe an ORM isn't appropriate for your situation.
that means you can mold it to fit your use case easily - don't like the ORM - you can plug SQLalchemy and use a different 'architecture'. + you can use multiple different databases if you think that's the right path. in Django there's no 'the rails way' - you choose your own path.
Django-admin by itself saves so much work specially If you're doing B2B stuff & you gotta onboard users.
I guess Django is not the best thing, but not the worst thing either. so a perfect middle ground.
The Rails Way is a response to people changing too much Rails and making it super custom.
In the last 4 years alone I touched codebases ranging from Rails Way to Rails and everything dry.rb, to Rails and Grape and Sorbet, to Rails and service objects everywhere and a lot more combinations.
whereas Django though it also provides almost similar abstractions in terms of CRUD - it doesn't assume your app will only do CRUD stuff - hence the flexibility e.g there's many apps where you only have a handful of endpoints that are user accessible maybe at most 15, then of course management of users via Django-admin but everything else is non CRUD e.g calling other systems
[0]: https://jeromedalbert.com/how-dhh-organizes-his-rails-contro...
This article is very useful.
I use DRF but not serializers and write validations by hand because it is too abstract for me.
My views just call services and return the result.
I've been meaning to do my own Django post, on some other bits we take for granted - I should do it.
People should be using Django, the best parts are so useful you don't notice them until you switch platforms and implement them badly.
Every app that used a more narrow solution ultimately ends up implementing parts of Django badly.
The best way to solve this from Djangos side would be to have official ways of:
- Using the ORM outside of Django - Doing single file Django apps
Both of these have various 3rd party solutions, which shows demand.
In the past other bits of Django have been split off by 3rd parties but those two are the places to start.
Django is hands down one of my favorite frameworks ever created, and the only one I still reach for in some contexts. For a lot of projects I use it to drive database migrations, and stand up an easy admin portal for others to use - then anything else is driven by an API layer written in (e.g) Rust.
I haven't had to care about Django's performance in years but still get to reap some of the benefits.
Just read from the database and treat Django as a DB builder/migrator/inspector.
But I also remember, that some non-standard requirements were really difficult to implement or get around.
Having that in mind, the next project was entirely in Pylons. All was good, until we were asked to add Unicode support.
Since them I'm on Rails.
How long ago was this? I feel like unicode has kind of been a solved problem in Python since python3 came out. In the python2 days it was, indeed, miserable.
DjangoCon 2008 Keynote: Cal Henderson
This is where asking an LLM for an example is very useful, but ideally, I should be able to find the available fields and their explanation and when to use them at a glance in the docs.
In general the docs are good, just examples could be better.
I generally end up with a few core apps with the main data objects that a lot of other "parallel" ones depend on, a bit "star shaped". And then a few "aggregator" apps that cover functionality that needs to work across multiple of these domains. I see it all as an extension of how you think about your data model.
I do what I must in the atomic context, and trigger celery tasks for everything else.