I disagree with the idea of not having a service layer at all, because in practice, changing your data model in some way is actually pretty common, even if the API remains the same so the data coming in/out isn't changing. I've had to split models into multiple parts to add audit history on certain fields, create notifications on object creation or modification (possible but hard to follow and a visibility problem when using signals), change the normalisation on fields, etc. etc. and at this point, turning to a service layer is much more appropriate than putting methods on the model since often the data is split across multiple database tables. I also would advocate this in projects where it's Django + DRF since you generally want exactly same logic to be run through whether submitting via a form or submitting via an API.
I've also found that it's good practice to impose boundaries between apps so that they are not strongly coupled together but communicate through an interface. That way, if you need to split an app out into its own project at some point, the migration is significantly easier from a code perspective, since the implementation of the interface function is all that needs changing on the consumer side.
It sounds to me like you could use model managers and just call it a service layer.
>in practice, changing your data model in some way is actually pretty common
I find this to be very true on some projects and very not true on others.
Where it is true the abstraction would be beneficial but if you assume all projects have this problem and therefore jam the abstraction into a project that doesn't have it you're just creating overhead.
ModelManagers are directly coupled to one model though. You mix concerns if you need to reference other models in them.
For e.g. say you have a User model and a corresponding “Profile” model. If you need to create the profile object when the user is created, it doesn’t really feel appropriate to put that in a UserModelManager directly coupled to the User object. This is the example given from the Hacksoft Django style guide to advocate creating a service layer.
In terms of the abstraction, I totally agree and tend to be pretty pragmatic about this sort of thing.
i don't see anything wrong with it if there are relationships between Profile and User, but if you feel bad about abusing a manager, you could just do a classmethod, or a separate function all together.
Over time, though, this approach leads to massive model/manager files with dozens of groupable methods. Sometimes a service is a useful abstraction as a way to encourage placing related functionality in a dedicated file. The one-minute learning curve for telling new team members what that services directory does, may save hours that would have otherwise been necessary to grok a thousand-line model file.
I've also found that it's good practice to impose boundaries between apps so that they are not strongly coupled together but communicate through an interface. That way, if you need to split an app out into its own project at some point, the migration is significantly easier from a code perspective, since the implementation of the interface function is all that needs changing on the consumer side.