Model Methods

Django’s Model class comes with many built-in methods. We have already used some of them—save(), delete(), __str__() and others. Where manager methods add table-level functionality to Django’s models, model methods add row-level functions that act on individual instances of the model.

There are two common cases where you want to play with model methods:

  1. When you want to add business logic to the model by adding custom model methods; and
  2. When you want to override the default behavior of a built-in model method.

Custom Model Methods

As always, it’s far easier to understand how custom model methods work by writing a couple, so let’s modify our Event class (changes in bold):

  1. # myclub_root\events\models.py
  2. # ...
  3. 1 class Event(models.Model):
  4. 2 name = models.CharField('Event Name', max_length=120)
  5. 3 event_date = models.DateTimeField('Event Date')
  6. 4 venue = models.ForeignKey(Venue, blank=True, null=True, on_delete=models.CASCADE)
  7. 5 manager = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
  8. 6 attendees = models.ManyToManyField(MyClubUser, blank=True)
  9. 7 description = models.TextField(blank=True)
  10. 8 events = EventManager()
  11. 9
  12. 10 def event_timing(self, date):
  13. 11 if self.event_date > date:
  14. 12 return "Event is after this date"
  15. 13 elif self.event_date == date:
  16. 14 return "Event is on the same day"
  17. 15 else:
  18. 16 return "Event is before this date"
  19. 17
  20. 18 @property
  21. 19 def name_slug(self):
  22. 20 return self.name.lower().replace(' ','-')
  23. 21
  24. 22 def __str__(self):
  25. 23 return self.name

Let’s have a look at what’s happening with this new code:

  • In line 10 I have added a new method called event_timing. This is a straightforward method that compares the event date to the date passed to the method. It returns a message stating whether the event occurs before, on or after the date.
  • In line 19 I have added another custom method that returns a slugified event name. The @property decorator on line 18 allows us to access the method directly, like an attribute. Without the @property, you would have to use a method call (name_slug()).

Let’s test these new methods out in the Django interactive interpreter. Don’t forget to save the model before you start!

First, the name_slug method:

  1. >>> from events.models import Event
  2. >>> events = Event.events.all()
  3. >>> for event in events:
  4. ... print(event.name_slug)
  5. ...
  6. test-event
  7. club-presentation---juniors
  8. club-presentation---seniors
  9. gala-day

This should be easy to follow. Notice how the @property decorator allows us to access the method directly like it was an attribute. I.e., event.name_slug instead of event.name_slug().

Now, to test the event_timing method (assuming you have an event named “Gala Day”):

  1. >>> from datetime import datetime, timezone
  2. >>> e = Event.events.get(name="Gala Day")
  3. >>> e.event_timing(datetime.now(timezone.utc))
  4. 'Event is befor this date'
  5. >>>

Too easy.

Date and Time in Django

Remember, Django uses timezone aware dates, so if you are making date comparisons like this in any of your code, not just in class methods, you can’t use datetime.now() without timezone information as Django will throw a TypeError: can't compare offset-naive and offset-aware datetimes. To avoid this error, you must provide timezone information with your dates.

Overriding Default Model Methods

It’s common to want to override built-in model methods like save() and delete() to add business logic to default database behavior.

To override a built-in model method, you define a new method with the same name. For example, let’s override the Event model’s default save() method to assign management of the event to a staff member (changes in bold):

  1. # myclub_root\events\models.py
  2. # ...
  3. 1 class Event(models.Model):
  4. 2 name = models.CharField('Event Name', max_length=120)
  5. 3 event_date = models.DateTimeField('Event Date')
  6. 4 venue = models.ForeignKey(Venue, blank=True, null=True, on_delete=models.CASCADE)
  7. 5 manager = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
  8. 6 attendees = models.ManyToManyField(MyClubUser, blank=True)
  9. 7 description = models.TextField(blank=True)
  10. 8 events = EventManager()
  11. 9
  12. 10 def save(self, *args, **kwargs):
  13. 11 self.manager = User.objects.get(username='admin') # User 'admin' must exist
  14. 12 super(Event, self).save(*args, **kwargs)
  15. # ...

The new save() method starts on line 10. In the overridden save() method, we’re first assigning the staff member with the username “admin” to the manager field of the model instance (line 11). This code assumes you have named your admin user ‘admin’. If not, you will have to change this code.

Then we call the default save() method with the super() function to save the model instance to the database (line 12).

Once you save your models.py file, you can test out the overridden model method in the Django interactive shell (Remember, the username you entered on line 11 has to exist in the database for the test to work):

  1. >>> from events.models import Event
  2. >>> from events.models import Venue
  3. >>> from datetime import datetime, timezone
  4. >>> v = Venue.venues.get(id=1)
  5. >>> e = Event.events.create(name='New Event', event_date=datetime.now(timezone.utc), venue=v)

Once the new record is created, you can test to see if your override worked by checking the manager field of the Event object:

  1. >>> e.manager
  2. <User: admin>
  3. >>>