You can define your own custom field that will hold all the opening hours per day. Here, we will use an OpeningHours class that will hold the opening time per day of type datetime.time which means entering an invalid time e.g. 12:61 would fail validation. Then, we will store it as a space-separated string to the database field opening_hours e.g.
12:00 15:00 18:00 18:30 09:00 12:00 08:45
All we need to do is to configure:
- Saving of the custom field to the database from an instance of type
OpeningHours to that space-separated string of times per day via get_prep_value()
- Retrieving the custom field from the database from that space-separated string to an instance of
OpeningHours via from_db_value()
models.py
from datetime import datetime
from django.core.exceptions import ValidationError
class OpeningHours:
FORMAT = '%H:%M'
def __init__(self, mon, tue, wed, thu, fri, sat, sun):
self.mon = datetime.strptime(mon, self.FORMAT).time()
self.tue = datetime.strptime(tue, self.FORMAT).time()
self.wed = datetime.strptime(wed, self.FORMAT).time()
self.thu = datetime.strptime(thu, self.FORMAT).time()
self.fri = datetime.strptime(fri, self.FORMAT).time()
self.sat = datetime.strptime(sat, self.FORMAT).time()
self.sun = datetime.strptime(sun, self.FORMAT).time()
def __str__(self):
return ' '.join(
dt_time.strftime(self.FORMAT)
for dt_time in (
self.mon,
self.tue,
self.wed,
self.thu,
self.fri,
self.sat,
self.sun,
)
)
class OpeningHoursField(models.CharField):
description = "Opening time per day"
def from_db_value(self, value, expression, connection):
return self.to_python(value)
def to_python(self, value):
if value is None or isinstance(value, OpeningHours):
return value
try:
obj = OpeningHours(*value.split())
except Exception:
raise ValidationError("Invalid opening time")
else:
return obj
def get_prep_value(self, value):
return str(value)
class Restaurant(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30)
city = models.CharField(max_length=15)
opening_hours = OpeningHoursField(max_length=100)
def __str__(self):
return f'{self.name}, {self.city}'
Creating a new Restaurant would require an OpeningHours object to be set to the custom opening_hours field:
>>> from my_app.models import *
>>> opening_hours = OpeningHours(mon="12:00", tue="15:00", wed="18:00", thu="18:30", fri="09:00", sat="12:00", sun="08:45")
>>> obj = Restaurant.objects.create(name="Burger King", city="London", opening_hours=opening_hours)
>>> obj.save()
>>> obj.__dict__
{'_state': <django.db.models.base.ModelState object at 0x7fe3c5b4cb50>, 'id': 1, 'name': 'Burger King', 'city': 'London', 'opening_hours': <my_app.models.OpeningHours object at 0x7fe3c5b4c1f0>}
Reading the opening_hours field is just like reading an ordinary OpeningHours object:
>>> print(obj.opening_hours)
12:00 15:00 18:00 18:30 09:00 12:00 08:45
>>> obj.opening_hours.mon
datetime.time(12, 0)
>>> obj.opening_hours.tue
datetime.time(15, 0)
>>> obj.opening_hours.wed
datetime.time(18, 0)
>>> obj.opening_hours.thu
datetime.time(18, 30)
>>> obj.opening_hours.fri
datetime.time(9, 0)
>>> obj.opening_hours.sat
datetime.time(12, 0)
>>> obj.opening_hours.sun
datetime.time(8, 45)
To update the time for a particular day, just update the corresponding field and save.
>>> # Display old value
>>> print(Restaurant.objects.first().opening_hours)
12:00 15:00 18:00 18:30 09:00 12:00 08:45
>>> print(Restaurant.objects.first().opening_hours.thu)
18:30:00
>>>
>>> # Update new value for Thursday and save
>>> obj.opening_hours.thu = datetime.time(hour=1, minute=23)
>>> obj.save()
>>>
>>> # Display updated value
>>> print(Restaurant.objects.first().opening_hours)
12:00 15:00 18:00 01:23 09:00 12:00 08:45
>>> print(Restaurant.objects.first().opening_hours.thu)
01:23:00