I’ve worked for several years on projects that used Django now and I’ve seen many different implementations using this framework.

Historically, a lot of articles on the internet have been led you to use Fat Models, which makes you push most (if not all) of your business logic/rules into your Django Models. I strongly disagree with that and in this article, I will tell you why.

I will also give you 5 items for you to keep in mind when designing your models. First, let me tell you why I think fat models are a bad thing.

Fat models create a lot of dependencies overtime

Coupling those components like this forces you to change your code if any change happens on other models such as a method signature or a side effect that was added into it.

There’s no reason for that. Over time, it will create a tail of calls that you will never be able to track.

Fat models are hard to test

Keep your methods simple, avoid dependencies, and you should enjoy an easier life testing your stuff. Trust me, I’ve been there.

Fat models violate the Single Responsibility Principle (SRP)

Fat models make you lazy

Therefore, your class acts more like a facade than a model, doing all sort of things. Try to test it! it's a nightmare.

Here's what I consider good practices

Models handle their own data, and that’s it!

Add behaviors to models, not flows

Imagine you have a model called Customer.

from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=90)
active = models.BooleanField(default=True)
work_email = models.EmailField()
personal_email = models.EmailField(null=True, blank=True)

Now, let’s say you want to deactivate a particular customer. The way you do it is by adding behavior to that model.

from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=90)
active = models.BooleanField(default=True)
work_email = models.EmailField()
personal_email = models.EmailField(null=True, blank=True)
def deactivate(self):
self.active = False
self.save(update_fields=[‘active’])

Let’s suppose now, you want to validate if the work_email is different from personal_email. Here's how it'd look like.

from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=90)
active = models.BooleanField(default=True)
work_email = models.EmailField()
personal_email = models.EmailField(null=True, blank=True)
def deactivate(self):
self.active = False
self.save(update_fields=[‘active’])
def set_personal_email(self, email):
if self.work_email == email:
raise ValueError(
“Personal email and work email are equal"
)
self.personal_email = email
self.save(update_fields=[‘personal_email’])

By the way, I’m persisting in the state every time a behavior gets called on purpose. You don’t have to do it. You could literally call the save method from outside of the class. However, I’d argue that this way you can actually know exactly where you’re persisting the data and how it is happening.

Avoid changing the state directly

# Do not do this
customer.name = ‘John Smith’
customer.save()
# This is better
customer.set_name(‘John Smith’)

Avoid dependencies

def create_customer(self, name, work_email, personal_email=None):
customer = Customer.factory(
name=name,
work_email=work_email,
personal_email=personal_email
)
WelcomeEmailSender.send(customer.name, customer.work_email)
return customer

Make use of Model Managers

from django.db import models
class CustomerManager(models.Manager):
def find_by_email(self, email):
return self.filter(
models.Q(work_email=email) |
models.Q(personal_email=email)
)

def find(self, id):
return self.get(pk=id)

class Customer(models.Model):

objects = CustomerManager()

Conclusion

Just keep in mind though that embracing a framework does not mean that you can break software architecture principles. Aways seek for low coupling and high cohesion among your components.

These are my ideas based on the large Django projects I've worked on. I've seen the issues I pointed out here becoming really challenging from the architectural point of view, making the extension of the "current system" a really hard task.

You're free to disagree with me on this. In software architecture, there's no right answer to a problem, but rather options. However, you must live with the consequences of it.

CTO @ Flieber, Mentor @ Latitud, and Tech advisor. Building the next generation of supply-chain automation and helping companies to master the zero to one game!