I disagree with the perspective on CBVs though. I've been programming Django since 0.96 and CBVs have made nearly everything easier and better for me.
People always say this but well-structured CBVs keep a generic interface that you'll be really glad that exists when you have 80 views spread across 10 apps.
Composing function-based views is a PITA and when you're building an API with a bunch of auth/serialization/cache extras being bolted on it's way easier to keep disciplined and ordered. It is _trivial_ to mess up the order of callers for these things inside function-based views.
I wrote an article on this. My goto strategy is to create a project/core/views.py,models.py,apps.py,tests.py
Link or it didn't happen :)
My experience has been opposite so I'd be interested in hearing your experiences if you are willing. I have been using the built in migrations since day 1 on a medium sized Django project with 350+ migrations and migration issues for our project have been exceedingly rare. edit: We have a small team of developers, so merge migrations are very rare for us, which might be a contributing factor.
if using graphene or strawberry you export sdl to json and use @graphql-codegen/typescript
Tiangolo of FastAPI fame is working on https://sqlmodel.tiangolo.com/ Which is pydantic models, SQL alchemy. Migrations are coming soon. This will probably be an excellent way to build APIs with db. Then you can generate a typescript client from the built in OpenAPI schema.
Your migration code uses the model classes, but the migrations are using a rehydrated version of the model that doesn't include any methods; another footgun. Basically you need to copy in any logic that you're using into your migration file, or else that migration's logic will change as your code is refactored. You might naively think that because `model.do_the_thing()` works now, the migration is somehow pickling a snapshot of your model class. It's not.
Because of the above, you should really squash migrations frequently, but it's a big pain to do so -- particularly if you have dependency cycles between apps. ("Just Say No to Apps" is my default advice for new Django developers. If you have a real need for making a bit of library code sharable between multiple different Django projects then you know enough to break this rule. Within a single monolithic project, apps just cause pain.) Squashing migrations quickly gets to some extremely gnarly and hard-to-work-with logic in the docs.
Moving models between apps isn't supported by the migration machinery; it's an involved and dangerous process. One idea here that might save Apps is if you manually remove the App Prefix from your "owned" / internal apps; if I have Customer and Ledger apps, I don't really need to namespace the tables; `user`, `information`, `ledger_entries` are fine table names instead of `customer_user`, `customer_information`, `ledger_ledger_entries`, a normal DB admin would not namespace the table names. You neeed the app namespacing to make it safe to use someone else's apps, but I think namespacing for your own apps inside a single repo is harmful.
I find the migration framework to be worth using, but it's definitely got some sharp edges.
I recently wanted to move a model between apps and ended up going the route of create new table, copy all rows over, delete old table. It was annoying, but the only way to make it work with regular migrations.
We ended up writing our own script[1] to squash migrations, and I'd love to know if there's a better way. We needed something that works for clean installs or existing installs that already have the current migrations installed - so it generates empty migrations which get applied on existing installs, and then they get replaced with real initial migrations on clean installs starting from a new release.
1. https://github.com/nyaruka/rapidpro/blob/main/tools/squash_m...
It's hard to get 100% right and thus our projects always have a few lazy foreign key relationships and inline imports to avoid cyclical imports... but I think our code is easier to manage because we try to model the dependency relationships by having things in separate apps.
> Also be wary of the formerly South, now built-in migrations stuff
Our experience from South to 4.x has been that the models/migrations system has matured significantly and is probably now the main selling point for Django for us.
The long answer is if two entities are updating each other you might benefit from shifting all update responsibilities to one of them. Or even to a third entity that knows about both and keeps those two isolated from each other.
Would that mediator be another app? Or should it be some module sitting in the project directory? (I'm not even sure Django would import something like that.)
Yes!
It is an app that might not even have any model classes. But it will contain business logic. And it will probably speak domain language, which is great.
If you're lucky, those two other apps will become pluggable. You will probably never replace them, but separation of concerns is always nice.
The downside of course is that you will have 3 apps instead of 1. That's the balance you have to maintain.
Thanks so much!
I usually prefer to resolve those too. If job is small enough, all the orchestration of jobs should happen through mediator (same for load). If it's not plausible, then job can emit signals which mediator subscribes to.
A good place to start is to give a more descriptive name to the mediator. Sure, it mediates between the two, but it probably does that to implement some business process. Can you name it after that process?
So, in a smaller app, when a request comes into job/views.py or load/views.py then we immediately start working with JobLoadMediator to handle business logic between the two? I was just going to focus on specific tasks between job and load. I'll probably look into signals; I haven't used those in several years and as I recall, it felt hacky.
> give a more descriptive name to the mediator
When a Job is deleted, it needs to delete associated Loads. And the states of the Loads will affect the state of the Job. That's the main cycle I'm looking at right now.
In my world, mediator.views and job.views would likely have different audiences.
mediator.views is for business domain (e.g. your API). Though name would not be mediator, it would be something domain-specific.
job.views could be for more low-level internal tooling (e.g. analytics/monitoring). Or it could be empty, if job is just dumb data object that doesn't require public API.
> When a Job is deleted, it needs to delete associated Loads. And the states of the Loads will affect the state of the Job
If you want to keep them apart, signals is the right (though not the easiest) answer. job owns (depends on) loads, and subscribes to loads signals. load doesn't know anything about job (ignore the fact that job is referenced in load.models as foreign key, that's just limitations of SQL DDL).
The easiest is a subtle dependency: load can access job through foreign key, job can access loads through related_name. Circular dependency is still there, but it is resolved at runtime. This will start to cause pain when application grows, but is fine at small scale.
Though again, I'd probably merge those apps: if you can't reason about load without job, and can't reason about job without load, there's very little benefit in keeping them separate. It might make you feel more organized, but the if the code is lying about your mental model, this is false organization.
That's very interesting. I'm mostly following the architecture from: https://phalt.github.io/django-api-domains/styleguide/
I knew this was going to be a large project, about 19 apps, supporting a trucking and inventory web/mobile app, and I wanted a sane/organized way to deal with all of the models and relations.
Do you know of some blog posts or books that go into the way you normally organize things?
> if you can't reason about load without job, and can't reason about job without load
There is a lot of interaction between all of the parts of the app, but particularly between Jobs, Loads, Inventory, Stages, and Drivers. In the future I might start with one app, and then add another if I absolutely have to.
Nobody wants to work with those. Also, a simple typo in those files can bring your whole system down and can make it hard to debug.
Apps, are a cheap (EXCEPT when you HAVE TO [but really, do you? Really?] move models between apps) way to keep YOU sane.
You can either decide that signals code will live in the app where the objects reside or the app of the target objects and that's it. A built-in and simple interface between objects.
It will also keep you sane.
As simple as possible, but no simpler. Django models are meant to deal not just with the data, but also with business logic. If `course.has_finished` is a property of the course, why would you want to have a separate function outside of the class?
> Do precomputation if you need the information in a template
If the precomputation is only needed in a template, you can (should, IMO) use template tags.
> Don't make separate apps in the same project (...) avoid dependency hell.
My current pattern here has been to create one "core" project where I represent all the internal models of the domain of the application, and "adapter" apps if I want to interface/integrate with anything from the external world. This makes it easier to extend or replace third-party tools.
> (Migrations) It's built around a fragile model.
I wouldn't call it fragile, quite the opposite. There are some annoying limitations for sure (I didn't find a reliable way to change the primary key of a model, except for creating a whole new model and migrating the data to it), but I think they are due to a matter of strong safety that the migration can only be done if it consistent.
My current preference is a functional core-imperative shell-style architecture where as much code lives in the functional core as possible. It's not very elegant with Django but it works fine. Cosmic Python (really accessible and fairly quick read if you have the time: https://www.cosmicpython.com/book/preface.html) has examples that are similar.
Refactor is not a dirty word. The problems you are describing seem to be more of the nature of having too many things concentrated at specific model classes, and that this model should be decomposed, broken down. This is not a Django-specific issue.
I think a lot of 'MVC-inspired' frameworks fail there, not just django. Rails... 'app/helpers' maybe? Laravel 'models' is it, and 'services' or a variation is something I see a lot of folks adding, but it's not an out of the box convention. I can't remember anything specific/explicit in the asp.net world either.
Because one should avoid passing Django models around. It leads to bad design. Have a selector or something that uses the ORM, but exposes some dataclass or pydantic model instead, and put the logic there.
But aside from that you are just adding another layer of abstraction that does not give any benefit when all your models are managed by Django already.
No longer should anyone in their module directly do something to a model and save it. They should always go through a service in the module owning that model, that makes sure everything is done correctly. So services.py and selectors.py works as a public API for the module, while the models are internal. Avoids having lots of other apps/modules depending on your app's internals.
> No longer should anyone in their module
> they should always go through a service
Weasel words and opinions-as-fact. Come back when you have a way to show that your approach gives any actual benefit.
"incomprehensible and slow". To whom? How slow? What about your approach makes it faster?
A sibling comment pointed one important aspect: Django Querysets are lazily evaluated, I find it really hard to believe that having a layer that constant marshalls and unmarshalls the data through pydantic can make anything faster than not having to fetch any data until you really need it.
What you're describing takes a selector, and maps it onto an object. Is it just that you want type hints or something?
where Order is a pydantic model, not a fat django model. Other modules shouldn't know about my internal database. All other modules should use functions from this selector.py, they aren't allowed to use the OrderModel themselves directly, only the pydantic class. Because otherwise you end up with spaghetti.
So you want an ORM, but you want it without a save method? Or presumably with a save method that can only be called under specific circumstances?
For example, if you do a query like Model.objects.filter(related_model__in=RelatedModel.objects.filter(...)) the ORM will only run a single query, silently converting the second one into a JOIN.
If you pass lists of "RelatedModel" however you would've had to first one one query to get that list (raising potential edge-cases with regards to atomicity and transactional isolation) and then pass the list of IDs to the outer query in an "WHERE related_model_id IN (...)", resulting in 2 queries in the end.
While I propose most often sending in a list of related IDs (premature optimization and all that), the function could just accept any iterable, and you from the outside could send in the lazy relatedmodel query.
At least never put your business logic in your views, forms or even templates.
I disagree with this. When trouble shooting or expanding code it is super convenient to import a model and have all of your methods on auto complete. Especially when you need the same functionality in a view, a cron job, a celery task, and an DRF end point.
If you want to keep it clean you can put all your methods in a mixin class and import it from another file.
Also be wary of the formerly South, now built-in migrations stuff.
Things are much better than they were in South. But yes be careful, rule of thumb is always move forward.
Don't use class-based views
Please. For the love of God, always use class based views for almost everything. Almost everything you need is a variant of one of the built in class based views, don't make me read your copy/pasted reimplementation of it.
This. 100%. Leverage as much pre-built stuff you can, especially with something as important as your HTTP layer. Whenever I run out of CRUD verbs for a model and I need to add a custom endpoint, I'll implement it in a separate APIView sublcass. Convention over configuration; write boring code.
Fat models think controllers is the suggested strategy. It works well in my opinion / experience, though I guess it could get out of hand on very large projects.
There is something that feels very unnatural and unintuitive about your course_has_finished(course) versus course.has_finished() example. This was one of the principles behind OOP, keeping your data and functions / methods together, though I know OOP isn't trendy these days. It's far more natural to have it as a method of course than some random function, stored who knows where. I worked on a system with this type of design, it was pretty bad.
One thing I would like to see for larger projects is the ability to easily split models into separate files - a bit more like Java does with one class per file. Maybe you can do this already.
Class based views mean that a lot of code is written and tested for you. I'll agree that your view does need to be somewhat "standard" in what it's doing (anything you would see in the admin, list, create, edit, detail, login, etc), so if you have something more complex multiple forms in one view, then thy don't give a great deal of advantage. In that situation I would still likely choose a basic View class over functional views, but more for consistency.
I am probably in agreement on separate apps.
Migrations are one of the best features of Django. I have just spent 4 years working on a a system without them and it was a shambles as you would expect. Everyone is scared to make database changes, so you get a ton of shitty application code to compensate for shitty database modelling. Tech debt in other words.
I can't think of any 3rd party apps where I have used the models directly, or if I did it was frictionless enough for me not to remember. So no real opinion on that one. 3rd party apps can be pretty hit and miss, especially if they get abandoned as you upgrade Django, so I would say use with caution, particularly for more obscure ones (just been revisiting django-celery-beat, that's been around for years so I doubt it's going away).
Always keep your models slim. Don't stuff template related stuff in there. You need to look at those models often, so compact is a win. course_has_finished(course) is not much longer than course.has_finished(), and will allow you to expand the functionality as time goes on. Do precomputation if you need the information in a template - that keeps your templates simpler and allows you to easily expand the complexity of the precomputation.
Don't use class-based views, at least not outside very specific niches like the Django admin. Class-based views will transform a simple, composeable call stack into a ball of inheritance mud. Inheritance is not a good code reuse tool.
Don't make separate apps in the same project, unless the project actually consists of several different, completely independent projects. You can make subdirectories without making apps, and thus avoid dependency hell.
Also be wary of the formerly South, now built-in migrations stuff. It's built around a fragile model (perfect history representation), so has lots of foot guns.
And be wary of 3rd party libraries with their own models. You can spend a lot of time trying to built bridges to those models compared to just doing what makes sense for your particular project. I think 3rd party libraries are perhaps best implemented without concrete models - duck-typing in Python let us do this. This includes Django itself, by the way. User profiles didn't become good until Django allowed you to define the user model yourself.