Newer
Older
from decimal import Decimal
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from ckeditor.fields import RichTextField
from aleksis.apps.tezor.models.base import Client
from aleksis.apps.tezor.models.invoice import Invoice, InvoiceGroup
from aleksis.core.mixins import ExtensibleModel, GlobalPermissionModel
from aleksis.core.util.core_helpers import generate_random_code, get_site_preferences
from aleksis.core.util.email import send_email
from .data_checks import EventMembersSyncDataCheck
FIELD_CHOICES = (
("BooleanField", _("Boolean (Yes/No)")),
("CharField", _("Text (one line)")),
("DateField", _("Date")),
("DateTimeField", _("Date and time")),
("DecimalField", _("Decimal number")),
("EmailField", _("E-mail address")),
("IntegerField", _("Integer")),
("GenericIPAddressField", _("IP address")),
("NullBooleanField", _("Boolean or empty (Yes/No/Neither)")),
("TimeField", _("Time")),
("URLField", _("URL / Link")),
)
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)
send_to_retracted = models.BooleanField(
verbose_name=_("Send to participants who retracted"), default=False
)
send_to_not_checked_in = models.BooleanField(
verbose_name=_("Send to participants who did not check in"), default=True
)
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()
filter_args = {}
if not self.send_to_retracted:
filter_args["retracted"] = False
if not self.send_to_not_checked_in:
filter_args["checked_in"] = True
for registration in event.registrations.filter(**filter_args):
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 EventAdditionalField(ExtensibleModel):
"""An additional field that can be linked to a event."""
title = models.CharField(verbose_name=_("Title of field"), max_length=255, unique=True)
field_type = models.CharField(
verbose_name=_("Type of field"), choices=FIELD_CHOICES, max_length=50
)
required = models.BooleanField(verbose_name=_("Required"), default=False)
help_text = models.TextField(verbose_name=_("Help text / description"), blank=True)
def __str__(self) -> str:
return self.title
class Meta:
verbose_name = _("Addtitional field for events")
verbose_name_plural = _("Addtitional fields for events")
data_checks = [EventMembersSyncDataCheck]
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)
InfoMailing,
verbose_name=_("Info mailings"),
related_name="events",
through="EventInfoMailingThrough",
blank=True,
additional_fields = models.ManyToManyField(
EventAdditionalField, verbose_name=_("Additional fields"), blank=True
)
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)
super().save(*args, **kwargs)
self.sync_group_members()
def __str__(self) -> str:
return self.display_name
self.linked_group.members.set(
self.registrations.filter(retracted=False).values_list("person", flat=True)
)
if request and request.user.is_authenticated:
if request.user.person in self.linked_group.members.all():
return False

Nik | Klampfradler
committed
if self.registrations.filter(person=request.user.person).exists():
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 self.date_registration >= now
return self.date_event > now
return reverse("event_by_name", kwargs={"slug": self.slug})
def individual_cost(self, request=None):
if (
request
and request.user.is_authenticated
and Voucher.objects.filter(event=self, person=request.user.person, used=False).exists()
):
voucher = Voucher.objects.get(event=self, person=request.user.person, used=False)
individual_cost = (100 - voucher.discount) * self.cost / 100
else:
return self.cost
return self.linked_group.members.count() / self.max_participants * 100
return self.linked_group.members.all()
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:
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,
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"
)
retracted = models.BooleanField(verbose_name=_("Retracted"), default=False)
retracted_date = models.DateField(verbose_name=_("Retracted at"), null=True, blank=True)
checked_in = models.BooleanField(verbose_name=_("Checked in"), default=False)
checked_in_date = models.DateTimeField(verbose_name=_("Checked in at"), null=True, blank=True)
cost = models.IntegerField(verbose_name=_("Cost in €"), null=True, blank=True)
def mark_checked_in(self):
if not self.checked_in:
self.checked_in = True
self.checked_in_date = now()
self.save()
else:
raise ValidationError(_("Person is already checked in!"))
# Mark registration as retracted
self.retracted = True
self.retracted_date = datetime.today()
self.save()
# Remove person from group
self.event.linked_group.members.remove(self.person)
def get_billing_email_recipients(self):
return [self.person.email] + list(self.person.guardians.values_list("email", flat=True))
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.cost / 1.07),
# If a dnoation was made, add it
if self.donation:
yield PurchasedItem(
name=_("Social Sponsoring / Extra Donation"),
quantity=1,
price=Decimal(self.donation),
# If a voucher was used, add it
if self.voucher:
yield PurchasedItem(
name=_("Voucher / Granted discount"),
quantity=1,
price=Decimal(-1 * self.voucher.discount * (self.cost / 1.07) / 100),
total, total_tax = 0, 0
tax = item.price * item.tax_rate / 100
total_tax += tax
return total, total_tax
def __str__(self) -> str:
return f"{self.event}, {self.person.first_name} {self.person.last_name}"
def save(self, *args, **kwargs):
self.event.sync_group_members()
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
self.event.sync_group_members()
super().delete(*args, **kwargs)
class Meta:
verbose_name = _("Event registration")
verbose_name_plural = _("Event registrations")
constraints = [
models.UniqueConstraint(
fields=["person", "event"], name="unique_person_registration_per_event"
)
]
class Checkpoint(ExtensibleModel):
event = models.ForeignKey(
Event, verbose_name=_("Related event"), related_name="checkpoints", on_delete=models.CASCADE
)
person = models.ForeignKey(
Person,
verbose_name=_("Checked person"),
related_name="event_checkpoints",
on_delete=models.CASCADE,
)
checked_by = models.ForeignKey(
Person,
verbose_name=_("Checked by person"),
related_name="event_checkpoints_created",
on_delete=models.CASCADE,
)
comment = models.CharField(max_length=60, verbose_name=_("Comment"))
timestamp = models.DateTimeField(verbose_name=_("Date and time of check"), auto_now_add=True)
lat = models.DecimalField(
max_digits=10, decimal_places=8, verbose_name=_("Latitude of check"), blank=True, null=True
)
lon = models.DecimalField(
max_digits=11, decimal_places=8, verbose_name=_("Longitude of check"), blank=True, null=True
)
class PaweljongGlobalPermissions(GlobalPermissionModel):
class Meta:
managed = False
permissions = (
("generate_lists", _("Can generate lists of participants of a group")),
)