Source code for pizzas.models

"""The models defined by the pizzas package."""
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.template.defaulttags import date

from events.models import Event
import members
from members.models import Member
from payments.models import Payment, Payable
from payments.services import delete_payment
from pushnotifications.models import ScheduledMessage, Category
from utils.translation import ModelTranslateMeta, MultilingualField


[docs]class PizzaEvent(models.Model): """Describes an event where pizzas can be ordered.""" start = models.DateTimeField(_("Order from")) end = models.DateTimeField(_("Order until")) event = models.OneToOneField(Event, on_delete=models.CASCADE) send_notification = models.BooleanField( _("Send an order notification"), default=True ) end_reminder = models.OneToOneField(ScheduledMessage, models.CASCADE, null=True) @property def title(self): return self.event.title @property def in_the_future(self): return self.start > timezone.now() @property def has_ended(self): return self.end < timezone.now() @property def just_ended(self): return ( self.has_ended and self.end + timezone.timedelta(hours=8) > timezone.now() )
[docs] @classmethod def current(cls): """Get the currently relevant pizza event: the first one that starts within 8 hours from now.""" try: events = PizzaEvent.objects.filter( end__gt=timezone.now() - timezone.timedelta(hours=8), start__lte=timezone.now() + timezone.timedelta(hours=8), ).order_by("start") if events.count() > 1: return events.exclude(end__lt=timezone.now()).first() return events.get() except PizzaEvent.DoesNotExist: return None
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._end = self.end
[docs] def validate_unique(self, exclude=None): super().validate_unique(exclude) for other in PizzaEvent.objects.filter( Q(end__gte=self.start, end__lte=self.end) | Q(start=self.start, start__lte=self.start) ): if other.pk == self.pk: continue raise ValidationError( { "start": _("This event cannot overlap with {}.").format(other), "end": _("This event cannot overlap with {}.").format(other), } )
[docs] def clean(self): super().clean() if self.start >= self.end: raise ValidationError( { "start": _("The start is after the end of this event."), "end": _("The end is before the start of this event."), } )
[docs] def save(self, **kwargs): if self.send_notification and not self.end_reminder: end_reminder = ScheduledMessage() end_reminder.title_en = "Order pizza" end_reminder.body_en = "You can order pizzas for 10 more minutes" end_reminder.category = Category.objects.get(key=Category.PIZZA) end_reminder.time = self.end - timezone.timedelta(minutes=10) end_reminder.save() if self.event.registration_required: end_reminder.users.set( self.event.registrations.select_related("member").values_list( "member", flat=True ) ) else: end_reminder.users.set(Member.current_members.all()) self.end_reminder = end_reminder elif self.send_notification and self.end_reminder and self._end != self.end: self.end_reminder.time = self.end self.end_reminder.save() elif not self.send_notification and self.end_reminder: end_reminder = self.end_reminder self.end_reminder = None if not end_reminder.sent: end_reminder.delete() super().save(**kwargs)
[docs] def delete(self, using=None, keep_parents=False): if self.end_reminder is not None and not self.end_reminder.sent: self.end_reminder.delete() return super().delete(using, keep_parents)
def __str__(self): return "Pizzas for " + str(self.event) class Meta: ordering = ("-start",)
[docs]class AvailableProductManager(models.Manager): """Only shows available products."""
[docs] def get_queryset(self): return super().get_queryset().filter(available=True)
[docs]class Product(models.Model, metaclass=ModelTranslateMeta): """Describes a product.""" objects = models.Manager() available_products = AvailableProductManager() name = models.CharField(max_length=50) description = MultilingualField(models.TextField) price = models.DecimalField(max_digits=5, decimal_places=2) available = models.BooleanField(default=True) restricted = models.BooleanField( default=False, help_text=_( "Only allow to be ordered by people with the " "'order restricted products' permission." ), ) def __str__(self): return self.name class Meta: ordering = ("name",) permissions = (("order_restricted_products", _("Order restricted products")),)
[docs]class Order(models.Model, Payable): """Describes an order of an item during an event.""" member = models.ForeignKey( members.models.Member, on_delete=models.CASCADE, blank=True, null=True, ) name = models.CharField( verbose_name=_("name"), max_length=50, help_text=_("Use this for non-members"), null=True, blank=True, ) payment = models.OneToOneField( verbose_name=_("payment"), to="payments.Payment", related_name="pizzas_order", on_delete=models.PROTECT, blank=True, null=True, ) product = models.ForeignKey( verbose_name=_("product"), to=Product, on_delete=models.PROTECT, ) pizza_event = models.ForeignKey( verbose_name=_("event"), to=PizzaEvent, on_delete=models.CASCADE ) @property def payment_amount(self): return self.product.price @property def payment_topic(self): start_date = date(self.pizza_event.start, "Y-m-d") return f"Pizzas {self.pizza_event.event.title_en} [{start_date}]" @property def payment_notes(self): return ( f"Pizza order by {self.member_name} " f"for {self.pizza_event.event.title_en}" ) @property def payment_payer(self): return self.member
[docs] def clean(self): if (self.member is None and not self.name) or (self.member and self.name): raise ValidationError( { "member": _("Either specify a member or a name"), "name": _("Either specify a member or a name"), } )
@property def member_name(self): if self.member is not None: return self.member.get_full_name() return self.name @property def member_last_name(self): if self.member is not None: return self.member.last_name return " ".join(self.name.split(" ")[1:]) @property def member_first_name(self): if self.member is not None: return self.member.first_name return self.name.strip(" ").split(" ")[0] @property def can_be_changed(self): try: return ( not self.payment or self.payment.type == Payment.TPAY ) and not self.pizza_event.has_ended except ObjectDoesNotExist: return False
[docs] def delete(self, using=None, keep_parents=False): if self.payment is not None and self.can_be_changed: delete_payment(self) return super().delete(using, keep_parents)
class Meta: unique_together = ( "pizza_event", "member", ) def __str__(self): return _("Order by {member_name}: {product}").format( member_name=self.member_name, product=self.product )