So, what’s the problem here?
Well, we’ve ended up splitting our logic across both the model code and the view code, breaking the encapsulation that the model class should provide.
Here’s some scenarios we could easily run into:
status='signedup'
, but does not notice that they should also be setting the .signup_date
field.'signedup'
state. They do so in the Django shell, forgetting to set .signup_date
.expiry_date
field is added. The field value is properly set in the expire_trials
management command, but the team fail to notice that it should also be set to None
in the signup
view.In each case we end up with an inconsistent data state.
In our trivial example these kinds of errors would be reasonably easy to notice and avoid. However, in a large codebase they become increasingly difficult to spot and the risks increase.
Beyond “Fat models, thin views”The standard advice here is to “Use fat models, and thin views”. That’s entirely correct, but it’s also a little bit ill-defined. What constitutes a ‘fat model’? How much logic is okay in view code? Does the ‘fat models’ convention still hold if we split business logic into nicely defined utility functions?
I would rephrase this more strictly:
Never write to a model field or call save()
directly. Always use model methods and manager methods for state changing operations.
This is a simple, unambiguous convention, that’s easily enforceable at the point of code review.
Doing so allows you to properly encapsulate your model instances, and allows you to impose strict application level constraints on which state changes are permitted.
It follows that this rule also implies:
If your team follows this rule and you adhere to it as a point of policy, you will be able to reason more confidently about your possible data states and valid state changes.
This isn’t about writing boilerplate setter
properties for each field in the model, but rather about writing methods that encapsulate the point of interaction with the database layer. View code can still inspect any field on the model and perform logic based on that, but it should not modify that data directly.
We’re ensuring that there is a layer at which we can enforce application-level integrity constraints that exist on top of the integrity constraints that the database provides for us.
Let’s return to our example code, writing the state-changing logic entirely in the model and model manager classes.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4