Knower
Creating custom validator for model field

Creating Custom Model Field Validator

Introduction

There should be some point when we need validation on data while maintaining service. For instance,

  1. Users should not include special characters or whitespace for their name field while creating account.
  2. Purchase date should not be earlier than today's date.
  3. Discount price should be less than the original price.

and so forth.

Examples

1. Lookbook season year

Lookbook is a collection data of recommended products by Knower admin team corresponding to each year's season, so as to strive for better UX by letting customers search a variety of products. Lookbook season code consists of 2 model fields, which are year and season (SS & FW).

As lookbook shows products collection for the future, year of lookbook should be future as well.

For this, I decided to pass Validator (opens in a new tab) for that field (opens in a new tab) with validators property.

Creating Custom Validator Function

Create validator function for generating lookbook season code.

models.py
Copy

class Lookbook(models.Model):
# ...
def validate_lookbook_year(year):
pass
year = models.PositiveSmallIntegerField(
verbose_name="연도",
help_text="최대 4자리 수",
)
# ...

Calculating year

From Python's built-in datetime (opens in a new tab) module, I extracted year (opens in a new tab) data for catching lookbook's year. Then, I let Django trigger ValidationError (opens in a new tab) when lookbook's year(year) is lesser than real time's year(this_year) (e.g. past).

models.py
Copy

from datetime import datetime
class Lookbook(models.Model):
# ...
def validate_lookbook_year(year):
this_year = datetime.now().year
if year < this_year:
raise ValidationError("룩북 연도는 현재보다 과거일 수 없습니다.")
year = models.PositiveSmallIntegerField(
verbose_name="연도",
help_text="최대 4자리 수",
)
# ...

Excluding Invalid Format of Year

Also, I triggered ValidationError when user provided extremely big unit of year (e.g. 12345).

models.py
Copy

from datetime import datetime
class Lookbook(models.Model):
# ...
def validate_lookbook_year(year):
this_year = datetime.now().year
if year < this_year:
raise ValidationError("룩북 연도는 현재보다 과거일 수 없습니다.")
if len(str(year)) >= 5:
raise ValidationError("잘못된 연도입니다.")
year = models.PositiveSmallIntegerField(
verbose_name="연도",
help_text="최대 4자리 수",
)
# ...

Setting Lookbook Year Cap

There was no need for showing future products for no less than 2 years. Therefore, I also put this logic into my validator function.

models.py
Copy

from datetime import datetime
class Lookbook(models.Model):
# ...
def validate_lookbook_year(year):
this_year = datetime.now().year
if year < this_year:
raise ValidationError("룩북 연도는 현재보다 과거일 수 없습니다.")
if len(str(year)) >= 5:
raise ValidationError("잘못된 연도입니다.")
if year > this_year + 1:
raise ValidationError(
"룩북 연도는 현재 연도보다 2년 이상 설정할 수 없습니다."
)
year = models.PositiveSmallIntegerField(
verbose_name="연도",
help_text="최대 4자리 수",
)
# ...

Adapting Validator

I adapted my custom validator for year field.

models.py
Copy

from datetime import datetime
class Lookbook(models.Model):
# ...
def validate_lookbook_year(year):
this_year = datetime.now().year
if year < this_year:
raise ValidationError("룩북 연도는 현재보다 과거일 수 없습니다.")
if len(str(year)) >= 5:
raise ValidationError("잘못된 연도입니다.")
if year > this_year + 1:
raise ValidationError(
"룩북 연도는 현재 연도보다 2년 이상 설정할 수 없습니다."
)
year = models.PositiveSmallIntegerField(
verbose_name="연도",
help_text="최대 4자리 수",
validators=[validate_lookbook_year],
)
# ...

Creating Custom Validator Function

Create validator function for generating lookbook season code.

Calculating year

From Python's built-in datetime (opens in a new tab) module, I extracted year (opens in a new tab) data for catching lookbook's year. Then, I let Django trigger ValidationError (opens in a new tab) when lookbook's year(year) is lesser than real time's year(this_year) (e.g. past).

Excluding Invalid Format of Year

Also, I triggered ValidationError when user provided extremely big unit of year (e.g. 12345).

Setting Lookbook Year Cap

There was no need for showing future products for no less than 2 years. Therefore, I also put this logic into my validator function.

Adapting Validator

I adapted my custom validator for year field.

models.py
CopyExpandClose

class Lookbook(models.Model):
# ...
def validate_lookbook_year(year):
pass
year = models.PositiveSmallIntegerField(
verbose_name="연도",
help_text="최대 4자리 수",
)
# ...

2. Discount Price of Product

Discount price is not required field, but its value should always be less than the original price.

Using clean() Method

In order to carry out validator on model perspective, that is when you want to carry out validation while creating new QuerySet, you may use clean() (opens in a new tab) with ease. First, I initialized clean() method.


class Product(models.Model):
# ...
def clean(self):
super().clean()

Setting Validator's Logic

Validator will work only when there is discount_price, and if discount_price is bigger or equal to the original price, I triggered ValidationError as well.


class Product(models.Model):
# ...
def clean(self):
if self.discount_price:
if self.discount_price >= self.price:
raise ValidationError("할인가가 원가보다 같거나 높을 수는 없습니다.")
super().clean()

Using clean() Method

In order to carry out validator on model perspective, that is when you want to carry out validation while creating new QuerySet, you may use clean() (opens in a new tab) with ease. First, I initialized clean() method.

Setting Validator's Logic

Validator will work only when there is discount_price, and if discount_price is bigger or equal to the original price, I triggered ValidationError as well.


class Product(models.Model):
# ...
def clean(self):
super().clean()