-
Nik | Klampfradler authoredNik | Klampfradler authored
models.py 12.41 KiB
from datetime import datetime
from decimal import Decimal
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from ckeditor.fields import RichTextField
from colorfield.fields import ColorField
from payments import PurchasedItem
from aleksis.apps.tezor.models.base import Client
from aleksis.apps.tezor.models.invoice import Invoice, InvoiceGroup
from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import Group, Person
from aleksis.core.util.core_helpers import generate_random_code, get_site_preferences
from aleksis.core.util.email import send_email
class RegistrationState(ExtensibleModel):
name = models.CharField(verbose_name=_("Name"), max_length=255)
colour = ColorField(blank=True, verbose_name=_("Colour"))
def __str__(self) -> str:
return self.name
class Terms(ExtensibleModel):
title = models.CharField(max_length=255, verbose_name=_("Title"))
term = RichTextField(verbose_name=_("Term"))
confirmation_text = models.TextField(verbose_name=_("Confirmation text"))
def __str__(self) -> str:
return self.title
class InfoMailing(ExtensibleModel):
subject = models.CharField(max_length=255, verbose_name=_("subject"))
text = RichTextField(verbose_name=_("Text"))
reply_to = models.EmailField(verbose_name=_("Request replies to"), blank=True)
active = models.BooleanField(verbose_name=_("Mailing is active"), default=False)
sender = models.EmailField(verbose_name=_("Sender"), blank=True)
send_to_person = models.BooleanField(verbose_name=_("Send to registered person"), default=True)
send_to_guardians = models.BooleanField(verbose_name=_("Send to guardians"), default=False)
def __str__(self) -> str:
return self.subject
@classmethod
def get_active_mailings(cls):
return cls.objects.filter(active=True)
def send(self):
for event in self.events.all():
through = EventInfoMailingThrough.objects.get(info_mailing=self, event=event)
sent_to = through.sent_to.all()
for registration in event.registrations.all():
if registration.person in sent_to:
continue
subject = self.subject.format(
event=event, registration=registration, person=registration.person
)
body = self.text.format(
event=event, registration=registration, person=registration.person
)
if self.send_to_person:
to = [registration.person.email]
if self.send_to_guardians:
cc = registration.person.guardians.values_list("email", flat=True).all()
else:
cc = []
elif self.send_to_guardians:
to = registration.person.guardians.values_list("email", flat=True).all()
cc = []
sender = self.sender or get_site_preferences()["mail__address"]
reply_to = self.reply_to or sender
context = {"subject": subject, "body": body}
send_email(
template_name="info_mailing",
context=context,
from_email=sender,
recipient_list=to,
cc=cc,
headers={
"Reply-To": reply_to,
},
)
through.sent_to.add(registration.person)
class Event(ExtensibleModel):
# Event details
display_name = models.CharField(verbose_name=_("Display name"), max_length=255)
linked_group = models.OneToOneField(
Group, on_delete=models.CASCADE, verbose_name=_("Group"), related_name="linked_event"
)
description = models.CharField(max_length=500, verbose_name=_("Description"))
published = models.BooleanField(default=False, verbose_name=_("Publish"))
place = models.CharField(max_length=50, verbose_name="Place")
slug = models.SlugField(max_length=255, verbose_name=_("Slug"), blank=True)
# Date details
date_event = models.DateField(verbose_name=_("Date of event"))
date_registration = models.DateField(verbose_name=_("Registration deadline"))
date_retraction = models.DateField(verbose_name=_("Retraction deadline"))
# Other details
cost = models.IntegerField(verbose_name=_("Cost in €"))
max_participants = models.PositiveSmallIntegerField(verbose_name=_("Maximum participants"))
information = RichTextField(verbose_name=_("Information about the event"))
terms = models.ManyToManyField(Terms, verbose_name=_("Terms"), related_name="event", blank=True)
info_mailings = models.ManyToManyField(
InfoMailing,
verbose_name=_("Info mailings"),
related_name="events",
through="EventInfoMailingThrough",
blank=True,
)
def save(self, *args, **kwargs):
if not self.slug:
if self.linked_group.short_name:
self.slug = slugify(self.linked_group.short_name)
else:
self.slug = slugify(self.display_name)
return super().save(*args, **kwargs)
def __str__(self) -> str:
return self.display_name
def can_register(self, request=None):
now = datetime.today().date()
if request and request.user.is_authenticated:
if request.user.person in self.linked_group.members.all():
return False
if EventRegistration.objects.filter(person=request.user.person).exists():
return False
if (
Voucher.objects.filter(event=self, person=request.user.person, used=False).count()
> 0
):
return True
if self.linked_group.members.count() >= self.max_participants:
return False
if self.date_registration:
return self.date_registration >= now
return self.date_event > now
def get_absolute_url(self):
return reverse("event_by_name", kwargs={"slug": self.slug})
@property
def booked_percentage(self):
return self.linked_group.members.count() / self.max_participants * 100
@property
def members_persons(self):
return self.linked_group.members.all()
@property
def owners_persons(self):
return self.linked_group.owners.all()
@classmethod
def upcoming_published_events(cls):
return Event.objects.filter(published=True, date_event__gte=now())
class EventInfoMailingThrough(ExtensibleModel):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
info_mailing = models.ForeignKey(InfoMailing, on_delete=models.CASCADE)
sent_to = models.ManyToManyField(
Person,
verbose_name=_("Sent to persons"),
related_name="received_info_mailings",
editable=False,
blank=True,
)
class Voucher(ExtensibleModel):
class Meta:
verbose_name = _("Vouchers")
verbose_name_plural = _("Vouchers")
code = models.CharField(max_length=255, blank=True, default="")
event = models.ForeignKey(
Event,
related_name="vouchers",
verbose_name=_("Event"),
on_delete=models.CASCADE,
null=True,
)
person = models.ForeignKey(
Person,
related_name="vouchers",
verbose_name=_("Person"),
on_delete=models.CASCADE,
)
discount = models.IntegerField(default=100)
used = models.BooleanField(default=False)
used_person_uid = models.ForeignKey(
Person,
on_delete=models.CASCADE,
verbose_name=_("Used by"),
related_name="used_vouchers",
null=True,
)
deleted = models.BooleanField(default=False)
def __str__(self) -> str:
return self.code
def save(self, *args, **kwargs):
if not self.code:
self.code = generate_random_code(5, 3)
super().save(*args, **kwargs)
class EventRegistration(ExtensibleModel):
event = models.ForeignKey(
Event, on_delete=models.CASCADE, verbose_name=_("Event"), related_name="registrations"
)
person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person"))
date_registered = models.DateTimeField(auto_now_add=True, verbose_name=_("Registration date"))
school = models.CharField(verbose_name=_("Name of school"), max_length=255)
school_class = models.CharField(verbose_name=_("School class"), max_length=255)
school_place = models.CharField(verbose_name=_("Place of the school"), max_length=255)
comment = models.TextField(verbose_name=_("Comment / remarks"), blank=True, default="")
medical_information = models.TextField(
verbose_name=_("Medical information / intolerances"), blank=True, default=""
)
voucher = models.ForeignKey(
Voucher,
on_delete=models.CASCADE,
verbose_name=_("Voucher"),
blank=True,
null=True,
)
donation = models.PositiveIntegerField(verbose_name=_("Donation"), blank=True, null=True)
accepted_terms = models.ManyToManyField(
Terms,
verbose_name=_("Accepted terms"),
related_name="registrations",
)
states = models.ManyToManyField(
RegistrationState, verbose_name=_("States"), related_name="registrations"
)
def get_invoice(self):
# FIXME Maybe do not hard-code this
client, __ = Client.objects.get_or_create(name="Teckids e.V.")
group, __ = InvoiceGroup.objects.get_or_create(
name="Hack'n'Fun-Veranstaltungen",
client=client,
defaults={
"template_name": "paweljong/invoice_pdf.html",
},
)
invoice, __ = Invoice.objects.get_or_create(
for_content_type=ContentType.objects.get_for_model(self),
for_object_id=self.pk,
defaults={
"group": group,
"number": f"HNF-{self.date_registered.strftime('%Y-%m')}-{self.id}",
"currency": "EUR",
"total": self._get_total_amount()[0],
"tax": self._get_total_amount()[1],
"description": _("Participation of {} in event {}").format(
self.person.addressing_name, self.event.display_name
),
"billing_first_name": self.person.first_name,
"billing_last_name": self.person.last_name,
"billing_address_1": f"{self.person.street} {self.person.housenumber}",
"billing_city": self.person.place,
"billing_postcode": self.person.postal_code,
"billing_email": self.person.email,
},
)
return invoice
def get_purchased_items(self):
# FIXME Maybe do not hard-code the tax rate and currency
# First, return main amount
yield PurchasedItem(
name=self.event.display_name,
quantity=1,
price=Decimal(self.event.cost),
currency="EUR",
sku="",
tax_rate=7,
)
# If a dnoation was made, add it
if self.donation:
yield PurchasedItem(
name=_("Social Sponsoring / Extra Donation"),
quantity=1,
price=self.donation,
currency="EUR",
sku="",
tax_rate=0,
)
# If a voucher was used, add it
if self.voucher:
yield PurchasedItem(
name=_("Voucher / Granted discount"),
quantity=1,
price=-1 * self.voucher.discount * self.event.cost / 100,
currency="EUR",
sku="",
tax_rate=7,
)
def _get_total_amount(self):
total, tax = 0, 0
for item in self.get_purchased_items():
total += item.price
tax += item.price / (item.tax_rate + 100) * item.tax_rate
return total, tax
def __str__(self) -> str:
return f"{self.event}, {self.person.first_name} {self.person.last_name}"
class Meta:
verbose_name = _("Event registration")
verbose_name_plural = _("Event registrations")
constraints = [
models.UniqueConstraint(
fields=["person", "event"], name="unique_person_registration_per_event"
)
]