# Imports
from datetime import timedelta
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.timezone import now
from ..audit.utils import is_audit_model
# Exports
__all__ = (
"TimedModel",
)
# Constants
SECONDS_PER_DAY = 24 * 60 * 60
# Models
[docs]class TimedModel(models.Model):
"""Provides date/time fields for noting a starting time and ending time, as well as convenience methods for
calculating elapsed time.
.. code-block:: python
from superdjango.db.timed.models import TimedModel
class Meeting(TimedModel):
end_date = models.DateField()
end_time = models.TimeField()
is_all_day = models.BooleanField()
start_date = models.DateField()
start_time = models.TimeField()
"""
timer_elapsed_seconds = models.PositiveIntegerField(
_("elapsed seconds"),
blank=True,
help_text=_("Total seconds elapsed from start to end."),
null=True
)
timer_is_running = models.BooleanField(
_("running"),
default=False,
help_text=_("Indicates whether the timer is running.")
)
timer_start_dt = models.DateTimeField(
_("start date/time"),
blank=True,
help_text=_("Date and time the timer was started."),
null=True,
)
timer_stop_dt = models.DateTimeField(
_("stop date/time"),
blank=True,
help_text=_("Date and time the timer was stopped."),
null=True
)
class Meta:
abstract = True
@property
def elapsed_duration(self):
"""Get the elapsed duration either as the current ``timer_elapsed_seconds`` or by calling
``get_elapsed_duration()``.
:rtype: timedelta
"""
if self.timer_elapsed_seconds:
return timedelta(seconds=self.timer_elapsed_seconds)
return self.get_elapsed_duration()
[docs] def get_elapsed_duration(self):
"""Get the time that has elapsed as a duration.
:rtype: timedelta
"""
return timedelta(seconds=self.get_timer_elapsed_seconds())
[docs] def get_elapsed_duration_display(self):
"""Get a human-friendly display for the duration.
:rtype: str
"""
d = self.get_elapsed_duration()
total_seconds = d.seconds
if total_seconds == 0:
return ""
hours, remainder = divmod(total_seconds, 3600)
minutes, seconds = divmod(remainder, 60)
s = list()
if hours > 0:
s.append(str(hours))
if hours == 1:
s.append(str(_("hour")))
else:
s.append(str(_("hours")))
if minutes > 0:
s.append(str(minutes))
if minutes == 1:
s.append(str(_("minute")))
else:
s.append(str(_("minutes")))
return " ".join(s)
[docs] def get_timer_elapsed_seconds(self):
"""Get the number of seconds that have passed since the timer started until now *or* until the item ended, if a
stop datetime is available.
:rtype: int
This method will:
- Calculate the elapsed seconds from the start datetime until the current datetime.
- Also include the elapsed seconds already calculated.
"""
if not self.timer_start_dt:
return 0
current_dt = now()
delta = current_dt - self.timer_start_dt
return delta.seconds + (delta.days * SECONDS_PER_DAY) + (self.timer_elapsed_seconds or 0)
[docs] def reset_timer(self, user, commit=True):
"""Reset the current timer.
:param user: The user updating the record. If the record is an audit model, the ``audit()`` method will be
called. Pass ``None`` to suppress this behavior.
:type user: AUTH_USER_MODEL | None
:param commit: Indicates whether the record should be saved after resetting the timer.
:type commit: bool
:rtype: int
:returns: The value of elapsed seconds prior to the reset.
This method will:
- Remove the stop datetime and set elapsed seconds to ``0``.
- Change the start datetime to the current datetime.
- Ensure that the timer is still running.
"""
current_elapsed_seconds = self.timer_elapsed_seconds or 0
self.timer_elapsed_seconds = 0
self.timer_is_running = True
self.timer_stop_dt = None
self.timer_start_dt = now()
if is_audit_model(self) and user is not None:
# noinspection PyUnresolvedReferences
self.audit(user, commit=commit)
return current_elapsed_seconds
if commit:
self.save(update_fields=["timer_elapsed_seconds", "timer_is_running", "timer_stop_dt", "timer_start_dt"])
return current_elapsed_seconds
[docs] def start_timer(self, user, commit=True):
"""Start the timer.
:param user: The user updating the record. If the record is an audit model, the ``audit()`` method will be
called. Pass ``None`` to suppress this behavior.
:type user: AUTH_USER_MODEL | None
:param commit: Indicates whether the record should be saved after
starting the timer.
:type commit: bool
:rtype: int
:returns: The current elapsed seconds if the timer is already running. Otherwise, ``0``.
This method will:
- Set the start datetime to the current datetime.
- Ensure that the time is running.
- Leave the elapsed seconds unchanged.
"""
if self.timer_is_running:
return self.get_timer_elapsed_seconds()
self.timer_is_running = True
self.timer_start_dt = now()
if is_audit_model(self) and user is not None:
# noinspection PyUnresolvedReferences
self.audit(user, commit=commit)
return 0
if commit:
self.save(update_fields=["timer_is_running", "timer_start_dt"])
return 0
[docs] def stop_timer(self, user, commit=True):
"""Stop the timer.
:param user: The user updating the record. If the record is an audit model, the ``audit()`` method will be
called. Pass ``None`` to suppress this behavior.
:type user: AUTH_USER_MODEL | None
:param commit: Indicates whether the record should be saved after
stopping the timer.
:type commit: bool
:rtype: int
:returns: The elpased seconds.
This method will:
- Set the stop datetime to the current datetime.
- Update the elapsed seconds.
- Ensure that the time is not running.
"""
if not self.timer_is_running:
return self.get_timer_elapsed_seconds()
self.timer_elapsed_seconds = self.get_timer_elapsed_seconds()
self.timer_is_running = False
self.timer_stop_dt = now()
if is_audit_model(self) and user is not None:
# noinspection PyUnresolvedReferences
self.audit(user, commit=commit)
return self.timer_elapsed_seconds
if commit:
self.save(update_fields=["timer_elapsed_seconds", "timer_is_running", "timer_stop_dt"])
return self.timer_elapsed_seconds