Source code for rpaper.apps.reservations.models

import uuid
import datetime
from django.db import models
from django.urls import reverse
from django.conf import settings
from django.utils import formats
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _

from thumbnailfield.fields import ThumbnailField

from rpaper.core.utils import validate_on_save
from rpaper.core.fields.hashids import HashidsField


[docs]class Thing(models.Model): hashid = HashidsField( verbose_name=_('Hashids'), primary_key=True, hashids_salt="%s:reservations:thing" % settings.HASHIDS_SOLT, hashids_min_length=8, ) name = models.CharField(_('Name'), max_length=255) remarks = models.TextField(_("Remarks"), blank=True) thumbnail = ThumbnailField( _("Thumbnail"), upload_to="reservations/thing/thumbnails", blank=True, ) owner = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Owner"), related_name="things", editable=False, ) ipaddress = models.GenericIPAddressField( _("IPAddress"), editable=False, blank=True, null=True, ) created_at = models.DateTimeField(_("Created at"), auto_now_add=True) updated_at = models.DateTimeField(_("Modified at"), auto_now=True) class Meta: verbose_name = _("Thing") verbose_name_plural = _("Things") def __str__(self): return self.name
[docs] def get_absolute_url(self): return reverse('reservations:things-detail', kwargs=dict( pk=self.pk, ))
[docs]class RecordManager(models.Manager): """A management class of Record model""" def _may_collide_with(self, record): # There is no record which time span is greater than threshold return self.filter( thing=record.thing, start_at__gt=record.start_at - Record.TIMESPAN_THRESHOLD, end_at__lt=record.end_at + Record.TIMESPAN_THRESHOLD, )
[docs] def collide_with(self, record): """Return records queryset which collide with a record""" qs = self._may_collide_with(record) record_ids = ( r.pk for r in qs if record.is_collided(r) ) return self.filter(pk__in=record_ids)
@validate_on_save
[docs]class Record(models.Model): TIMESPAN_MAX_HOURS = 24 TIMESPAN_THRESHOLD = datetime.timedelta( hours=TIMESPAN_MAX_HOURS ) hashid = HashidsField( verbose_name=_("Hashid"), primary_key=True, hashids_salt="%s:reservations:record" % settings.HASHIDS_SOLT, hashids_min_length=8, ) name = models.CharField(_('Name'), max_length=255) contact = models.CharField(_('Contact'), max_length=255) remarks = models.TextField(_("Remarks"), blank=True) thing = models.ForeignKey( Thing, verbose_name=_("Thing"), related_name="records", ) owner = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Owner"), related_name="records", editable=False, blank=True, null=True, ) start_at = models.DateTimeField(_("Start at")) end_at = models.DateTimeField(_("End at")) credential = models.UUIDField( _("Credential"), default=uuid.uuid4, editable=False, ) ipaddress = models.GenericIPAddressField( _("IPAddress"), editable=False, blank=True, null=True, ) created_at = models.DateTimeField(_("Created at"), auto_now_add=True) updated_at = models.DateTimeField(_("Modified at"), auto_now=True) objects = RecordManager() class Meta: verbose_name = _("Record") verbose_name_plural = _("Records") ordering = ( 'start_at', 'end_at', 'name', ) def __str__(self): return _( "%(timespan)s by %(name)s", ) % dict( timespan=self.get_timespan_display(), name=self.name, )
[docs] def is_collided(self, other): """Return if the record is collided with other""" if self.end_at <= other.start_at or other.end_at <= self.start_at: return False return True
[docs] def get_timespan_display(self): """Return a human readable time-span string""" if self.start_at.day == self.end_at.day: return _("%(date)s %(start_at)s to %(end_at)s") % dict( date=formats.date_format(self.start_at, 'DATE_FORMAT'), start_at=formats.date_format(self.start_at, 'TIME_FORMAT'), end_at=formats.date_format(self.end_at, 'TIME_FORMAT'), ) else: return _("%(start_at)s to %(end_at)s") % dict( start_at=formats.date_format(self.start_at, 'DATETIME_FORMAT'), end_at=formats.date_format(self.end_at, 'DATETIME_FORMAT'), )
[docs] def clean(self): if self.start_at is None or self.end_at is None: return self._validate_timespan() self._validate_collision()
def _validate_timespan(self): if self.end_at <= self.start_at: raise ValidationError( _("'end_at' could not be a greater value than 'start_at'"), code='invalid', ) elif self.end_at - self.start_at > type(self).TIMESPAN_THRESHOLD: raise ValidationError( _('The time-span could not be over %(threshold)d hours.'), code='invalid', params={'threshold': type(self).TIMESPAN_MAX_HOURS}, ) def _validate_collision(self): qs = Record.objects.collide_with(self) if qs.count() > 0: raise ValidationError( _("The record collide with %(record)s"), code='invalid', params={'record': qs.first()}, )