Write Your Own Model Methods to Resize Photos
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: 44KB
It 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.
Override the save()
Method on a Model
You 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.
Exercise: Write Custom Methods for the Blog
Model
We 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 theBlog
model. You can setnull=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 theBlog
model that calculates the word count of thecontent
field. The leading underscore indicates this is an internal method and theword_count
attribute is the correct way of accessing the word count.Update the
save()
method to set theword_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.
Let's Recap!
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()
anddelete()
methods.
Now that you can manipulate models using methods, let’s ensure data is not lost when migrating to new database schemas.