• 12 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 8/25/21

Manipulate Models by Overriding Model Methods

Log in or subscribe for free to enjoy all this course has to offer!

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 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

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()  and  delete()  methods.

Now that you can manipulate models using methods, let’s ensure data is not lost when migrating to new database schemas.

Example of certificate of achievement
Example of certificate of achievement