
One design pattern commonly used in Django apps is called fat models - skinny views. The philosophy is that the logic of your views should be as simple as possible and that most of the so-called “heavy lifting” should be done by the models.
You can achieve this by writing custom methods for your models. It allows your views to be simpler and more readable. This means that you can easily reuse the logic on your method between different views or tasks.
What logic belongs in a model method?
If the code you are writing involves either manipulating a model or retrieving information from a model, it probably belongs as a model method.
On our project, we want to resize photos before they are saved. This will help avoid high storage costs when the number of photos grows.
As this method is manipulating the Photo model, it is a perfect candidate for a model method. If we were to put this logic solely in a view, we wouldn’t be able to reuse it anywhere else, and it would also complicate the view logic.
Let's use the Pillow library to resize the photo. You installed this library earlier as required when using ImageField . You can also use it to manipulate images.
To resize the image while keeping the original aspect ratio, use the thumbnail function. It will only resize the image if it is larger than the maximum size.
We will create a new model method on Photo , resize_image .
# blog/models.py
from PIL import Image
class Photo(models.Model):
...
IMAGE_MAX_SIZE = (800, 800)
def resize_image(self):
image = Image.open(self.image)
image.thumbnail(self.IMAGE_MAX_SIZE)
# save the resized image to the file system
# this is not the model save method!
image.save(self.image.path)Now let’s test this out in the Python shell. We’re grabbing an image we have already uploaded, so when you’re testing this out, you will have to upload an image through the site first.
>>> from blog.models import Photo
>>> photo = Photo.objects.first()
>>> print(f'size: {photo.image.size // 1024}KB')
size: 683KB
>>> photo.resize_image()
>>> print(f'size: {photo.image.size // 1024}KB')
size: 44KBIt looks like we reduced the photo size by over 15 times! Now that you have the ability to resize photos, let’s see how to make this happen automatically whenever a photo is uploaded.
save() Method on a ModelYou now have a handy method, resize_image , on the Photo model. You don’t want to have to manually call this method every time a new photo is uploaded!
Remember using the save() method to save models into the database? Well, you can override this method. In fact, you can override any of the built-in methods on models to customize them to your needs.
Let's override the save() method on the Photo class to ensure that all of the uploaded photos to the site are resized to specification.
Let’s see what this looks like. First, use the super() method to make sure that saving the object to the database still works.
class Photo(models.Model):
...
def save(self, *args, **kwargs):
super().save(*args, **kwargs)When overriding Django’s built-in models, it is always a good idea to take the arguments *args and **kwargs in your method and feed them to the super() method, even if the current specification doesn’t require them.
Why?
If the specification does change, and the model method starts taking new, different arguments, your custom method can handle it thanks to the generic *args and **kwargs .
Now just add the resize_image() method.
class Photo(models.Model):
...
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.resize_image() It’s that simple! Your images will now be automatically resized whenever the Photo.save() method is called.
Blog ModelWe now want to display the Blog post word count on the front end. To do this, calculate it from the content field on Blog .
Let’s say a senior developer has decided that it is too computationally intensive to calculate the word count for each individual post on the fly, so this information should be stored as a field on the Blog model. This field should then automatically update whenever changes are made to the Blog instance.
You will need to:
Create a new field word_count on the Blog model. You can set null=True so that you don’t have to provide a default value.
Create and run the migrations to enact this change.
Add a new method _get_word_count() to the Blog model that calculates the word count of the content field. The leading underscore indicates this is an internal method and the word_count attribute is the correct way of accessing the word count.
Update the save() method to set the word_count field to the result of _get_word_count() .
Once you’ve had a try, check your work against the solution in the GitHub repo.
Write chunks of code that manipulate or get data from models as model methods.
Where appropriate, store logic on the models (fat models) and not in the views (skinny views).
You can override built-in methods for models such as the save() and delete() methods.
Now that you can manipulate models using methods, let’s ensure data is not lost when migrating to new database schemas.