2020-02-18 20:30:26 +00:00
|
|
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
2019-07-16 10:43:23 +00:00
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2020-08-06 10:50:24 +00:00
|
|
|
|
2020-08-06 12:11:55 +00:00
|
|
|
from datetime import timedelta
|
2020-08-06 10:15:23 +00:00
|
|
|
from threading import Thread
|
2019-07-16 10:43:23 +00:00
|
|
|
|
2020-08-06 10:15:23 +00:00
|
|
|
from django.conf import settings
|
2020-03-28 18:05:21 +00:00
|
|
|
from django.contrib.auth.models import User
|
2019-07-16 10:43:23 +00:00
|
|
|
from django.db import models
|
2020-03-28 12:38:31 +00:00
|
|
|
from django.db.models import Q
|
2020-08-01 15:49:23 +00:00
|
|
|
from django.utils import timezone
|
2019-07-16 10:43:23 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2020-03-28 12:38:31 +00:00
|
|
|
from rest_framework.exceptions import ValidationError
|
2020-03-27 21:48:20 +00:00
|
|
|
from note.models import NoteUser, Transaction
|
2019-07-16 10:43:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ActivityType(models.Model):
|
2019-08-11 15:52:41 +00:00
|
|
|
"""
|
2020-01-21 21:06:06 +00:00
|
|
|
Type of Activity, (e.g "Pot", "Soirée Club") and associated properties.
|
|
|
|
|
|
|
|
Activity Type are used as a search field for Activity, and determine how
|
|
|
|
some rules about the activity:
|
|
|
|
- Can people be invited
|
|
|
|
- What is the entrance fee.
|
2019-08-11 15:52:41 +00:00
|
|
|
"""
|
2019-07-16 10:43:23 +00:00
|
|
|
name = models.CharField(
|
|
|
|
verbose_name=_('name'),
|
|
|
|
max_length=255,
|
|
|
|
)
|
|
|
|
can_invite = models.BooleanField(
|
|
|
|
verbose_name=_('can invite'),
|
|
|
|
)
|
|
|
|
guest_entry_fee = models.PositiveIntegerField(
|
|
|
|
verbose_name=_('guest entry fee'),
|
|
|
|
)
|
|
|
|
|
2019-07-16 11:50:05 +00:00
|
|
|
class Meta:
|
|
|
|
verbose_name = _("activity type")
|
|
|
|
verbose_name_plural = _("activity types")
|
|
|
|
|
2019-07-16 13:42:31 +00:00
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
|
|
|
|
class Activity(models.Model):
|
2019-08-11 15:52:41 +00:00
|
|
|
"""
|
2020-01-21 21:06:06 +00:00
|
|
|
An IRL event organized by a club for other club.
|
|
|
|
|
|
|
|
By default the invited clubs should be the Club containing all the active accounts.
|
2019-08-11 15:52:41 +00:00
|
|
|
"""
|
2019-07-16 10:43:23 +00:00
|
|
|
name = models.CharField(
|
|
|
|
verbose_name=_('name'),
|
|
|
|
max_length=255,
|
|
|
|
)
|
2020-03-27 21:48:20 +00:00
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
description = models.TextField(
|
|
|
|
verbose_name=_('description'),
|
|
|
|
)
|
2020-03-27 21:48:20 +00:00
|
|
|
|
2020-08-06 10:15:23 +00:00
|
|
|
location = models.CharField(
|
|
|
|
verbose_name=_('location'),
|
|
|
|
max_length=255,
|
|
|
|
blank=True,
|
|
|
|
default="",
|
|
|
|
)
|
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
activity_type = models.ForeignKey(
|
|
|
|
ActivityType,
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='+',
|
|
|
|
verbose_name=_('type'),
|
|
|
|
)
|
2020-03-27 21:48:20 +00:00
|
|
|
|
2020-03-28 18:05:21 +00:00
|
|
|
creater = models.ForeignKey(
|
|
|
|
User,
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
verbose_name=_("user"),
|
|
|
|
)
|
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
organizer = models.ForeignKey(
|
|
|
|
'member.Club',
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='+',
|
|
|
|
verbose_name=_('organizer'),
|
|
|
|
)
|
2020-03-27 21:48:20 +00:00
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
attendees_club = models.ForeignKey(
|
|
|
|
'member.Club',
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='+',
|
|
|
|
verbose_name=_('attendees club'),
|
|
|
|
)
|
2020-03-27 21:48:20 +00:00
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
date_start = models.DateTimeField(
|
|
|
|
verbose_name=_('start date'),
|
|
|
|
)
|
2020-03-27 21:48:20 +00:00
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
date_end = models.DateTimeField(
|
|
|
|
verbose_name=_('end date'),
|
|
|
|
)
|
|
|
|
|
2020-03-27 21:48:20 +00:00
|
|
|
valid = models.BooleanField(
|
|
|
|
default=False,
|
|
|
|
verbose_name=_('valid'),
|
|
|
|
)
|
|
|
|
|
|
|
|
open = models.BooleanField(
|
|
|
|
default=False,
|
|
|
|
verbose_name=_('open'),
|
|
|
|
)
|
|
|
|
|
2020-08-06 10:15:23 +00:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Update the activity wiki page each time the activity is updated (validation, change description, ...)
|
|
|
|
"""
|
|
|
|
ret = super().save(*args, **kwargs)
|
|
|
|
if self.pk and "scripts" in settings.INSTALLED_APPS:
|
|
|
|
def refresh_activities():
|
|
|
|
from scripts.management.commands.refresh_activities import Command as RefreshActivitiesCommand
|
|
|
|
RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name)
|
|
|
|
RefreshActivitiesCommand.refresh_raw_wiki_page("Modification de l'activité " + self.name)
|
|
|
|
Thread(daemon=True, target=refresh_activities).start()
|
|
|
|
return ret
|
|
|
|
|
2020-08-01 16:05:31 +00:00
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
|
2019-07-16 11:50:05 +00:00
|
|
|
class Meta:
|
|
|
|
verbose_name = _("activity")
|
|
|
|
verbose_name_plural = _("activities")
|
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
|
2020-03-27 17:02:22 +00:00
|
|
|
class Entry(models.Model):
|
2020-04-06 10:32:44 +00:00
|
|
|
"""
|
|
|
|
Register the entry of someone:
|
|
|
|
- a member with a :model:`note.NoteUser`
|
|
|
|
- or a :model:`activity.Guest`
|
|
|
|
In the case of a Guest Entry, the inviter note is also save.
|
|
|
|
"""
|
2020-03-28 12:38:31 +00:00
|
|
|
activity = models.ForeignKey(
|
|
|
|
Activity,
|
|
|
|
on_delete=models.PROTECT,
|
2020-03-29 22:42:32 +00:00
|
|
|
related_name="entries",
|
2020-03-28 12:38:31 +00:00
|
|
|
verbose_name=_("activity"),
|
|
|
|
)
|
|
|
|
|
2020-03-27 17:02:22 +00:00
|
|
|
time = models.DateTimeField(
|
2020-08-01 15:49:23 +00:00
|
|
|
default=timezone.now,
|
2020-03-27 17:02:22 +00:00
|
|
|
verbose_name=_("entry time"),
|
|
|
|
)
|
|
|
|
|
|
|
|
note = models.ForeignKey(
|
|
|
|
NoteUser,
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
verbose_name=_("note"),
|
|
|
|
)
|
|
|
|
|
2020-03-28 12:38:31 +00:00
|
|
|
guest = models.OneToOneField(
|
|
|
|
'activity.Guest',
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
null=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
unique_together = (('activity', 'note', 'guest', ), )
|
2020-04-06 08:58:16 +00:00
|
|
|
verbose_name = _("entry")
|
|
|
|
verbose_name_plural = _("entries")
|
2020-03-28 12:38:31 +00:00
|
|
|
|
2020-04-11 01:37:06 +00:00
|
|
|
def save(self, *args, **kwargs):
|
2020-03-28 12:38:31 +00:00
|
|
|
|
|
|
|
qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
|
|
|
|
if qs.exists():
|
|
|
|
raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
|
|
|
|
|
|
|
|
if self.guest:
|
|
|
|
self.note = self.guest.inviter
|
|
|
|
|
|
|
|
insert = not self.pk
|
|
|
|
if insert:
|
|
|
|
if self.note.balance < 0:
|
|
|
|
raise ValidationError(_("The balance is negative."))
|
|
|
|
|
2020-04-11 01:37:06 +00:00
|
|
|
ret = super().save(*args, **kwargs)
|
2020-03-28 12:38:31 +00:00
|
|
|
|
|
|
|
if insert and self.guest:
|
|
|
|
GuestTransaction.objects.create(
|
|
|
|
source=self.note,
|
2020-04-02 13:43:41 +00:00
|
|
|
destination=self.activity.organizer.note,
|
2020-03-28 12:38:31 +00:00
|
|
|
quantity=1,
|
|
|
|
amount=self.activity.activity_type.guest_entry_fee,
|
|
|
|
reason="Invitation " + self.activity.name + " " + self.guest.first_name + " " + self.guest.last_name,
|
|
|
|
valid=True,
|
2020-07-22 19:05:25 +00:00
|
|
|
entry=self,
|
2020-03-28 12:38:31 +00:00
|
|
|
).save()
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
2020-03-27 17:02:22 +00:00
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
class Guest(models.Model):
|
2019-08-11 15:52:41 +00:00
|
|
|
"""
|
2020-01-21 21:06:06 +00:00
|
|
|
People who are not current members of any clubs, and are invited by someone who is a current member.
|
2019-08-11 15:52:41 +00:00
|
|
|
"""
|
2019-07-16 10:43:23 +00:00
|
|
|
activity = models.ForeignKey(
|
|
|
|
Activity,
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
related_name='+',
|
|
|
|
)
|
2020-03-27 17:02:22 +00:00
|
|
|
|
|
|
|
last_name = models.CharField(
|
2019-07-16 10:43:23 +00:00
|
|
|
max_length=255,
|
2020-03-27 17:02:22 +00:00
|
|
|
verbose_name=_("last name"),
|
2019-07-16 10:43:23 +00:00
|
|
|
)
|
2020-03-27 17:02:22 +00:00
|
|
|
|
|
|
|
first_name = models.CharField(
|
|
|
|
max_length=255,
|
|
|
|
verbose_name=_("first name"),
|
|
|
|
)
|
|
|
|
|
2019-07-16 10:43:23 +00:00
|
|
|
inviter = models.ForeignKey(
|
2020-03-27 17:02:22 +00:00
|
|
|
NoteUser,
|
2019-07-16 10:43:23 +00:00
|
|
|
on_delete=models.PROTECT,
|
2020-03-29 22:42:32 +00:00
|
|
|
related_name='guests',
|
2020-03-27 17:02:22 +00:00
|
|
|
verbose_name=_("inviter"),
|
2019-07-16 10:43:23 +00:00
|
|
|
)
|
2020-03-27 17:02:22 +00:00
|
|
|
|
2020-03-28 12:38:31 +00:00
|
|
|
@property
|
|
|
|
def has_entry(self):
|
|
|
|
try:
|
|
|
|
if self.entry:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
except AttributeError:
|
|
|
|
return False
|
2020-03-27 17:02:22 +00:00
|
|
|
|
2020-03-29 22:42:32 +00:00
|
|
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
|
|
|
one_year = timedelta(days=365)
|
|
|
|
|
2020-03-30 15:27:02 +00:00
|
|
|
if not force_insert:
|
2020-08-06 12:11:55 +00:00
|
|
|
if self.activity.date_start > timezone.now():
|
2020-03-30 15:27:02 +00:00
|
|
|
raise ValidationError(_("You can't invite someone once the activity is started."))
|
|
|
|
|
2020-03-30 15:35:47 +00:00
|
|
|
if not self.activity.valid:
|
|
|
|
raise ValidationError(_("This activity is not validated yet."))
|
|
|
|
|
2020-03-30 15:27:02 +00:00
|
|
|
qs = Guest.objects.filter(
|
|
|
|
first_name=self.first_name,
|
|
|
|
last_name=self.last_name,
|
|
|
|
activity__date_start__gte=self.activity.date_start - one_year,
|
|
|
|
)
|
|
|
|
if len(qs) >= 5:
|
|
|
|
raise ValidationError(_("This person has been already invited 5 times this year."))
|
|
|
|
|
|
|
|
qs = qs.filter(activity=self.activity)
|
|
|
|
if qs.exists():
|
|
|
|
raise ValidationError(_("This person is already invited."))
|
|
|
|
|
|
|
|
qs = Guest.objects.filter(inviter=self.inviter, activity=self.activity)
|
|
|
|
if len(qs) >= 3:
|
|
|
|
raise ValidationError(_("You can't invite more than 3 people to this activity."))
|
2020-03-29 22:42:32 +00:00
|
|
|
|
|
|
|
return super().save(force_insert, force_update, using, update_fields)
|
|
|
|
|
2020-08-01 16:05:31 +00:00
|
|
|
def __str__(self):
|
|
|
|
return self.first_name + " " + self.last_name
|
|
|
|
|
2019-07-16 11:50:05 +00:00
|
|
|
class Meta:
|
|
|
|
verbose_name = _("guest")
|
|
|
|
verbose_name_plural = _("guests")
|
2020-03-29 22:42:32 +00:00
|
|
|
unique_together = ("activity", "last_name", "first_name", )
|
2020-03-27 21:48:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class GuestTransaction(Transaction):
|
2020-07-22 19:05:25 +00:00
|
|
|
entry = models.OneToOneField(
|
|
|
|
Entry,
|
2020-03-27 21:48:20 +00:00
|
|
|
on_delete=models.PROTECT,
|
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def type(self):
|
|
|
|
return _('Invitation')
|