5.11. Models Method Property

  • @property is a Python decorator that allows you to define a method that behaves like an attribute

  • This fields are not stored in the database

  • They are calculated when accessed

  • They are read-only (can't be set)

  • They can be used in the Django Admin

>>> #
... from datetime import date
... from django.db import models
... from django.utils.translation import gettext_lazy as _
... from shop.models import Address, Email
...
...
... class Customer(models.Model):
...     firstname = models.CharField(verbose_name=_('First Name'), max_length=100, null=False, blank=False)
...     lastname = models.CharField(verbose_name=_('Last Name'), max_length=100, null=False, blank=False, db_index=True)
...
...     @property
...     def age(self, year=365.25):
...         days = (date.today() - self.birthdate).days
...         return int(days / year)
...
...     @property
...     def emails(self):
...         return Email.objects.filter(customer=self)
...
...     @property
...     def addresses(self):
...         return Address.objects.filter(customer=self)
...
...     @property
...     def changelog(self):
...         from django.contrib.contenttypes.models import ContentType
...         from django.contrib.admin.models import LogEntry
...         model = self.__class__.__name__.lower()
...         app_label = self.__class__._meta.app_label
...         ct = ContentType.objects.get(app_label=app_label, model=model)
...         return LogEntry.objects.filter(content_type=ct, object_id=self.pk)

... def __str__(self): ... return f'{self.firstname} {self.lastname}' ... ... class Meta: ... verbose_name = _('Customer') ... verbose_name_plural = _('Customers')

5.11.1. Assignments

# TODO: Create Tests
# doctest: +SKIP_FILE

# %% About
# - Name: Django Model Property
# - Difficulty: easy
# - Lines: 4
# - Minutes: 5

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% English
# 0. Use `myproject.myapp`
# 1. Modify model `Person`
# 2. Add property fullname that returns `f'{self.first_name} {self.last_name}'`
# 3. Use `@property` decorator
# 4. Run `makemigrations`
# 5. Run `migrate`

# %% Polish
# 0. Użyj `myproject.myapp`
# 1. Stwórz model `Person`
# 2. Dodaj właściwość fullname, która zwraca `f'{self.first_name} {self.last_name}'`
# 3. Użyj dekoratora `@property`
# 4. Uruchom `makemigrations`
# 5. Uruchom `migrate`

# %% Expected

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 12), \
'Python has an is invalid version; expected: `3.12` or newer.'
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`

# %% Imports
from django.db import models
from django.utils.translation import gettext_lazy as _

# %% Types
Group: type[models.Model]

# %% Data

# %% Result
class Person(models.Model):
    firstname = models.CharField(verbose_name=_('Firstname'), max_length=30, default=None, null=True, blank=True)
    lastname = models.CharField(verbose_name=_('Lastname'), max_length=30, null=False, blank=False, db_index=True)


# TODO: Create Tests
# doctest: +SKIP_FILE

# %% About
# - Name: Django Model Property
# - Difficulty: easy
# - Lines: 4
# - Minutes: 5

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% English
# 0. Use `myproject.myapp`
# 1. Modify model `Person`
# 2. Add property `age` that returns age in years based on `self.birthdate`
# 3. Mind, that field `birthdate` can be `NULL`, then return `None`
# 4. Use `@property` decorator
# 5. Run `makemigrations`
# 6. Run `migrate`

# %% Polish
# 0. Użyj `myproject.myapp`
# 1. Stwórz model `Person`
# 2. Dodaj property `age`, która zwraca wiek w latach na podstawie `self.birthdate`
# 3. Pamiętaj, że pole `birthdate` może być `NULL`, wtedy zwróć `None`
# 4. Użyj dekoratora `@property`
# 5. Uruchom `makemigrations`
# 6. Uruchom `migrate`

# %% Expected

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 12), \
'Python has an is invalid version; expected: `3.12` or newer.'
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`

# %% Imports
from django.db import models
from django.utils.translation import gettext_lazy as _

# %% Types
Group: type[models.Model]

# %% Data

# %% Result
class Person(models.Model):
    birthdate = models.DateField(verbose_name=_('Birthdate'), null=True, blank=True, default=None)