models.py 24.1 KB
Newer Older
1
"""The models defined by the events package"""
2
from django.conf import settings
Thom Wiggers's avatar
Thom Wiggers committed
3
from django.core import validators
4
from django.core.exceptions import ValidationError, ObjectDoesNotExist
Thom Wiggers's avatar
Thom Wiggers committed
5
6
from django.db import models
from django.db.models import Q
7
from django.urls import reverse
Thom Wiggers's avatar
Thom Wiggers committed
8
from django.utils import timezone
9
from django.utils.crypto import get_random_string
10
from django.utils.translation import ugettext_lazy as _
11
from django.utils.text import format_lazy
12
from tinymce.models import HTMLField
13

14
from members.models import Member
15
from payments.models import Payment
16
from pushnotifications.models import ScheduledMessage, Category
17
from utils.translation import ModelTranslateMeta, MultilingualField
Thom Wiggers's avatar
Thom Wiggers committed
18
19


20
class Event(models.Model, metaclass=ModelTranslateMeta):
21
    """Describes an event"""
Thom Wiggers's avatar
Thom Wiggers committed
22

Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
23
24
25
26
27
28
29
30
31
    CATEGORY_DRINKS = 'drinks'
    CATEGORY_ACTIVITY = 'activity'
    CATEGORY_LUNCH = 'lunchlecture'
    CATEGORY_MEETING = 'generalmeeting'
    CAGTEGORY_WORKSHOP = 'workshop'
    CATEGORY_ALUMNI = 'alumni'
    CATEGORY_PARTY = 'party'
    CATEGORY_OTHER = 'other'

32
    EVENT_CATEGORIES = (
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
33
34
35
36
37
38
39
40
        (CATEGORY_DRINKS, _('Drinks')),
        (CATEGORY_ACTIVITY, _('Activity')),
        (CATEGORY_LUNCH, _('Lunch Lecture')),
        (CATEGORY_MEETING, _('General Meeting')),
        (CAGTEGORY_WORKSHOP, _('Workshop')),
        (CATEGORY_ALUMNI, _('Alumni')),
        (CATEGORY_PARTY, _('Party')),
        (CATEGORY_OTHER, _('Other')))
41

42
43
    DEFAULT_NO_REGISTRATION_MESSAGE = _('No registration required / '
                                        'Geen aanmelding vereist')
Thom Wiggers's avatar
Thom Wiggers committed
44

45
46
47
48
49
    title = MultilingualField(
        models.CharField,
        _("title"),
        max_length=100
    )
Thom Wiggers's avatar
Thom Wiggers committed
50

51
    description = MultilingualField(
52
        HTMLField,
Thijs de Jong's avatar
Thijs de Jong committed
53
54
55
56
        _("description"),
        help_text=_("Please fill in both of the description boxes (EN/NL),"
                    " even if your event is Dutch only! Fill in the English "
                    "description in Dutch then.")
57
    )
Thom Wiggers's avatar
Thom Wiggers committed
58
59
60
61
62
63

    start = models.DateTimeField(_("start time"))

    end = models.DateTimeField(_("end time"))

    organiser = models.ForeignKey(
64
        'activemembers.MemberGroup',
Thom Wiggers's avatar
Thom Wiggers committed
65
        models.PROTECT,
66
        verbose_name=_("organiser")
Thom Wiggers's avatar
Thom Wiggers committed
67
68
    )

69
70
71
72
73
74
75
    category = models.CharField(
        max_length=40,
        choices=EVENT_CATEGORIES,
        verbose_name=_('category'),
        default='other'
    )

Thom Wiggers's avatar
Thom Wiggers committed
76
77
78
79
    registration_start = models.DateTimeField(
        _("registration start"),
        null=True,
        blank=True,
80
81
82
        help_text=_("If you set a registration period registration will be "
                    "required. If you don't set one, registration won't be "
                    "required.")
Thom Wiggers's avatar
Thom Wiggers committed
83
84
85
86
87
    )

    registration_end = models.DateTimeField(
        _("registration end"),
        null=True,
88
89
90
91
        blank=True,
        help_text=_("If you set a registration period registration will be "
                    "required. If you don't set one, registration won't be "
                    "required.")
92
93
94
95
96
    )

    cancel_deadline = models.DateTimeField(
        _("cancel deadline"),
        null=True,
Thom Wiggers's avatar
Thom Wiggers committed
97
98
99
        blank=True
    )

100
101
102
103
104
105
106
    send_cancel_email = models.BooleanField(
        _('send cancellation notifications'),
        default=True,
        help_text=_("Send an email to the organising party when a member "
                    "cancels their registration after the deadline."),
    )

107
108
109
110
111
    location = MultilingualField(
        models.CharField,
        _("location"),
        max_length=255,
    )
Thom Wiggers's avatar
Thom Wiggers committed
112
113
114
115
116

    map_location = models.CharField(
        _("location for minimap"),
        max_length=255,
        help_text=_('Location of Huygens: Heyendaalseweg 135, Nijmegen. '
117
                    'Location of Mercator 1: Toernooiveld 212, Nijmegen. '
Thom Wiggers's avatar
Thom Wiggers committed
118
119
120
121
122
123
124
125
126
127
128
                    'Not shown as text!!'),
    )

    price = models.DecimalField(
        _("price"),
        max_digits=5,
        decimal_places=2,
        default=0,
        validators=[validators.MinValueValidator(0)],
    )

129
130
    fine = models.DecimalField(
        _("fine"),
Thom Wiggers's avatar
Thom Wiggers committed
131
132
133
        max_digits=5,
        decimal_places=2,
        default=0,
134
135
        # Minimum fine is checked in this model's clean(), as it is only for
        # events that require registration.
136
        help_text=_("Fine if participant does not show up (at least €5)."),
137
        validators=[validators.MinValueValidator(0)],
Thom Wiggers's avatar
Thom Wiggers committed
138
139
140
141
142
143
144
145
    )

    max_participants = models.PositiveSmallIntegerField(
        _('maximum number of participants'),
        blank=True,
        null=True,
    )

146
147
    no_registration_message = MultilingualField(
        models.CharField,
Thom Wiggers's avatar
Thom Wiggers committed
148
149
150
151
        _('message when there is no registration'),
        max_length=200,
        blank=True,
        null=True,
152
153
        help_text=(format_lazy("{} {}", _("Default:"),
                               DEFAULT_NO_REGISTRATION_MESSAGE)),
Thom Wiggers's avatar
Thom Wiggers committed
154
155
156
157
    )

    published = models.BooleanField(_("published"), default=False)

158
159
160
161
162
163
164
    registration_reminder = models.ForeignKey(
        ScheduledMessage, on_delete=models.deletion.SET_NULL,
        related_name='registration_event', blank=True, null=True)
    start_reminder = models.ForeignKey(
        ScheduledMessage, on_delete=models.deletion.SET_NULL,
        related_name='start_event', blank=True, null=True)

165
166
167
168
169
170
    documents = models.ManyToManyField(
        'documents.Document',
        verbose_name=_("documents"),
        blank=True,
    )

171
172
173
174
175
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._price = self.price
        self._registration_start = self.registration_start

176
    @property
177
    def after_cancel_deadline(self):
178
179
180
181
182
        return self.cancel_deadline and self.cancel_deadline <= timezone.now()

    @property
    def registration_started(self):
        return self.registration_start <= timezone.now()
183

184
    @property
185
186
187
    def registration_required(self):
        return bool(self.registration_start) or bool(self.registration_end)

188
189
190
191
    def has_fields(self):
        return self.registrationinformationfield_set.count() > 0

    def reached_participants_limit(self):
192
        """Is this event up to capacity?"""
193
        return (self.max_participants is not None and
194
195
                self.max_participants <= self.registration_set.filter(
                    date_cancelled=None).count())
196

197
198
    @property
    def registrations(self):
199
        """Queryset with all non-cancelled registrations"""
200
201
202
203
        return self.registration_set.filter(date_cancelled=None)

    @property
    def participants(self):
204
        """Return the active participants"""
205
206
207
        if self.max_participants is not None:
            return self.registrations.order_by('date')[:self.max_participants]
        return self.registrations.order_by('date')
208
209
210

    @property
    def queue(self):
211
        """Return the waiting queue"""
212
213
214
        if self.max_participants is not None:
            return self.registrations.order_by('date')[self.max_participants:]
        return []
215

216
217
    @property
    def cancellations(self):
218
219
220
221
        """Return a queryset with the cancelled events"""
        return (self.registration_set
                .exclude(date_cancelled=None)
                .order_by('date_cancelled'))
222

223
    @property
224
    def registration_allowed(self):
225
        now = timezone.now()
226
227
228
229
230
231
232
        return ((self.registration_start or self.registration_end) and
                self.registration_end > now >= self.registration_start)

    @property
    def cancellation_allowed(self):
        now = timezone.now()
        return ((self.registration_start or self.registration_end)
233
                and self.registration_start <= now < self.start)
234

235
236
237
238
239
240
241
    def is_pizza_event(self):
        try:
            self.pizzaevent
            return True
        except ObjectDoesNotExist:
            return False

Thom Wiggers's avatar
Thom Wiggers committed
242
243
    def clean(self):
        super().clean()
Thom Wiggers's avatar
Thom Wiggers committed
244
        errors = {}
245
        if self.start is None:
Thom Wiggers's avatar
Thom Wiggers committed
246
            errors.update({
247
248
249
250
251
252
253
254
                'start': _("Start cannot have an empty date or time field")
            })
        if self.end is None:
            errors.update({
                'end': _("End cannot have an empty date or time field")
            })
        if self.start is not None and self.end is not None:
            if self.end < self.start:
255
                errors.update({
256
257
258
259
260
261
262
263
264
265
266
267
268
269
                    'end': _("Can't have an event travel back in time")})
            if self.registration_required:
                if self.fine < 5:
                    errors.update({
                        'fine': _("The fine for this event is too low "
                                  "(must be at least €5).")
                    })
                for lang in settings.LANGUAGES:
                    field = 'no_registration_message_' + lang[0]
                    if getattr(self, field):
                        errors.update(
                            {field: _("Doesn't make sense to have this "
                                      "if you require registrations.")})
                if not self.registration_start:
270
                    errors.update(
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
                        {'registration_start': _(
                            "If registration is required, you need a start of "
                            "registration")})
                if not self.registration_end:
                    errors.update(
                        {'registration_end': _(
                            "If registration is required, you need an end of "
                            "registration")})
                if not self.cancel_deadline:
                    errors.update(
                        {'cancel_deadline': _(
                            "If registration is required, "
                            "you need a deadline for the cancellation")})
                elif self.cancel_deadline > self.start:
                    errors.update(
                        {'cancel_deadline': _(
                            "The cancel deadline should be"
                            " before the start of the event.")})
                if self.registration_start and self.registration_end and (
                        self.registration_start >= self.registration_end):
                    message = _('Registration start should be before '
                                'registration end')
                    errors.update({
                        'registration_start': message,
                        'registration_end': message})
296
297
298
299
300
301
        if (self.organiser is not None and
                self.send_cancel_email and
                self.organiser.contact_mailinglist is None):
            errors.update(
                {'send_cancel_email': _("This organiser does not "
                                        "have a contact mailinglist.")})
302
        if self.published:
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
303
            if (self.price != self._price and self._registration_start
304
305
306
307
                    and self._registration_start <= timezone.now()):
                errors.update(
                    {'price': _("You cannot change this field after "
                                "the registration has started.")})
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
308
309
            if (self._registration_start
                    and self.registration_start != self._registration_start
310
311
312
313
314
                    and self._registration_start <= timezone.now()):
                errors.update(
                    {'registration_start':
                     _("You cannot change this field after "
                       "the registration has started.")})
315

Thom Wiggers's avatar
Thom Wiggers committed
316
317
        if errors:
            raise ValidationError(errors)
Thom Wiggers's avatar
Thom Wiggers committed
318
319

    def get_absolute_url(self):
320
        return reverse('events:event', args=[str(self.pk)])
Thom Wiggers's avatar
Thom Wiggers committed
321

322
    def save(self, *args, **kwargs):
323
324
325
        if not self.pk:
            super().save(*args, **kwargs)

326
327
        if self.published:
            if self.registration_required:
328
                registration_reminder_time = (self.registration_start -
329
                                              timezone.timedelta(hours=1))
330
331
                registration_reminder = ScheduledMessage()
                if (self.registration_reminder is not None
332
                        and not self.registration_reminder.sent):
333
                    registration_reminder = self.registration_reminder
334
335
336
337
338
339
340
341
342
343
344
345
346

                if registration_reminder_time > timezone.now():
                    registration_reminder.title_en = 'Event registration'
                    registration_reminder.title_nl = 'Evenement registratie'
                    registration_reminder.body_en = ('Registration for \'{}\' '
                                                     'starts in 1 hour'
                                                     .format(self.title_en))
                    registration_reminder.body_nl = ('Registratie voor \'{}\' '
                                                     'start in 1 uur'
                                                     .format(self.title_nl))
                    registration_reminder.category = Category.objects.get(
                        key='event')
                    registration_reminder.time = registration_reminder_time
347
348
349
                    registration_reminder.url = (
                        f'{settings.BASE_URL}'
                        f'{reverse("events:event", args=[self.id])}')
350

351
352
353
                    registration_reminder.save()
                    self.registration_reminder = registration_reminder
                    self.registration_reminder.users.set(
354
                        Member.current_members.all())
355
356
357
                elif registration_reminder.pk is not None:
                    self.registration_reminder = None
                    registration_reminder.delete()
358

359
            start_reminder_time = (self.start - timezone.timedelta(hours=1))
360
361
            start_reminder = ScheduledMessage()
            if (self.start_reminder is not None
362
                    and not self.start_reminder.sent):
363
                start_reminder = self.start_reminder
364
365
366
367

            if start_reminder_time > timezone.now():
                start_reminder.title_en = 'Event'
                start_reminder.title_nl = 'Evenement'
368
369
370
371
                start_reminder.body_en = (f'\'{self.title_en}\' starts in '
                                          '1 hour')
                start_reminder.body_nl = (f'\'{self.title_nl}\' begint over '
                                          '1 uur')
372
373
374
375
376
                start_reminder.category = Category.objects.get(key='event')
                start_reminder.time = start_reminder_time
                start_reminder.save()
                self.start_reminder = start_reminder
                if self.registration_required:
Thom Wiggers's avatar
Thom Wiggers committed
377
378
                    self.start_reminder.users.set(
                            self.participants.values_list('member', flat=True))
379
                else:
380
                    self.start_reminder.users.set(Member.current_members.all())
381
382
383
            elif start_reminder.pk is not None:
                self.start_reminder = None
                start_reminder.delete()
384
        else:
385
386
387
            if (self.registration_reminder is not None
                    and not self.registration_reminder.sent):
                self.registration_reminder.delete()
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
388
                self.registration_reminder = None
389
390
391
            if (self.start_reminder is not None
                    and not self.start_reminder.sent):
                self.start_reminder.delete()
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
392
                self.start_reminder = None
393

394
        super().save()
395

396
397
398
399
400
401
402
    def delete(self, using=None, keep_parents=False):
        if (self.registration_reminder is not None
                and not self.registration_reminder.sent):
            self.registration_reminder.delete()
        if (self.start_reminder is not None
                and not self.start_reminder.sent):
            self.start_reminder.delete()
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
403
404
        if self.is_pizza_event():
            self.pizzaevent.delete()
405
406
        return super().delete(using, keep_parents)

Thom Wiggers's avatar
Thom Wiggers committed
407
408
409
410
411
412
413
    def __str__(self):
        return '{}: {}'.format(
            self.title,
            timezone.localtime(self.start).strftime('%Y-%m-%d %H:%M'))

    class Meta:
        ordering = ('-start',)
414
415
416
        permissions = (
            ("override_organiser", "Can access events as if organizing"),
        )
Thom Wiggers's avatar
Thom Wiggers committed
417
418


419
def registration_member_choices_limit():
420
    """Defines queryset filters to only include current members"""
421
422
423
424
    return (Q(membership__until__isnull=True) |
            Q(membership__until__gt=timezone.now().date()))


Thom Wiggers's avatar
Thom Wiggers committed
425
class Registration(models.Model):
426
    """Describes a registration for an Event"""
Thom Wiggers's avatar
Thom Wiggers committed
427

428
429
430
    PAYMENT_NONE = Payment.NONE
    PAYMENT_CARD = Payment.CARD
    PAYMENT_CASH = Payment.CASH
431

Thom Wiggers's avatar
Thom Wiggers committed
432
433
434
    event = models.ForeignKey(Event, models.CASCADE)

    member = models.ForeignKey(
435
        'members.Member', models.CASCADE,
Thom Wiggers's avatar
Thom Wiggers committed
436
437
        blank=True,
        null=True,
438
        limit_choices_to=registration_member_choices_limit
Thom Wiggers's avatar
Thom Wiggers committed
439
440
441
442
443
444
445
446
447
448
    )

    name = models.CharField(
        _('name'),
        max_length=50,
        help_text=_('Use this for non-members'),
        null=True,
        blank=True
    )

449
450
    date = models.DateTimeField(_('registration date'),
                                default=timezone.now)
Thom Wiggers's avatar
Thom Wiggers committed
451
452
453
454
455
    date_cancelled = models.DateTimeField(_('cancellation date'),
                                          null=True,
                                          blank=True)

    present = models.BooleanField(
456
        _('present'),
Thom Wiggers's avatar
Thom Wiggers committed
457
458
        default=False,
    )
459

460
461
462
463
464
465
    payment = models.OneToOneField(
        'payments.Payment',
        related_name='events_registration',
        on_delete=models.PROTECT,
        blank=True,
        null=True,
Thom Wiggers's avatar
Thom Wiggers committed
466
467
    )

468
469
    @property
    def information_fields(self):
Thom Wiggers's avatar
Thom Wiggers committed
470
471
472
473
        fields = self.event.registrationinformationfield_set.all()
        return [{'field': field, 'value': field.get_value_for(self)}
                for field in fields]

474
475
476
477
478
479
480
481
482
483
484
485
486
    @property
    def is_registered(self):
        return self.date_cancelled is None

    @property
    def queue_position(self):
        if self.event.max_participants is not None:
            try:
                return list(self.event.queue).index(self) + 1
            except ValueError:
                pass
        return 0

487
488
489
490
491
    @property
    def is_invited(self):
        return (self.is_registered and
                self.queue_position == 0)

492
493
494
    def is_external(self):
        return bool(self.name)

Luuk Scholten's avatar
Luuk Scholten committed
495
    def is_late_cancellation(self):
496
497
        # First check whether or not the user cancelled
        # If the user cancelled then check if this was after the deadline
498
499
        # And if there is a max participants number:
        # do a complex check to calculate if this user was on
500
501
502
        # the waiting list at the time of cancellation, since
        # you shouldn't need to pay the costs of something
        # you weren't even able to go to.
Luuk Scholten's avatar
Luuk Scholten committed
503
        return (self.date_cancelled and
504
                self.event.cancel_deadline and
505
                self.date_cancelled > self.event.cancel_deadline and
506
507
                (self.event.max_participants is None or
                 self.event.registration_set.filter(
508
509
510
                     (Q(date_cancelled__gte=self.date_cancelled) |
                      Q(date_cancelled=None)) &
                     Q(date__lte=self.date)
511
                 ).count() < self.event.max_participants))
Luuk Scholten's avatar
Luuk Scholten committed
512

513
    def is_paid(self):
514
        return self.payment and self.payment.processed
515

516
    def would_cancel_after_deadline(self):
517
518
519
        now = timezone.now()
        return (self.queue_position == 0 and
                now >= self.event.cancel_deadline)
520

Thom Wiggers's avatar
Thom Wiggers committed
521
522
523
524
525
526
527
528
529
530
531
    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'),
            })

    def validate_unique(self, exclude=None):
        super().validate_unique(exclude)

532
533
534
535
536
537
538
539
540
541
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        if self.event.start_reminder and self.date_cancelled:
            self.event.start_reminder.users.remove(self.member)
        elif (self.event.start_reminder and self.member is not None and
              not self.event.start_reminder.users
                  .filter(pk=self.member.pk).exists()):
            self.event.start_reminder.users.add(self.member)

Thom Wiggers's avatar
Thom Wiggers committed
542
543
544
545
546
547
548
549
    def __str__(self):
        if self.member:
            return '{}: {}'.format(self.member.get_full_name(), self.event)
        else:
            return '{}: {}'.format(self.name, self.event)

    class Meta:
        ordering = ('date',)
550
        unique_together = (('member', 'event'),)
Thom Wiggers's avatar
Thom Wiggers committed
551
552


553
class RegistrationInformationField(models.Model, metaclass=ModelTranslateMeta):
554
    """Describes a field description to ask for when registering"""
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
    BOOLEAN_FIELD = 'boolean'
    INTEGER_FIELD = 'integer'
    TEXT_FIELD = 'text'

    FIELD_TYPES = ((BOOLEAN_FIELD, _('Checkbox')),
                   (TEXT_FIELD, _('Text')),
                   (INTEGER_FIELD, _('Integer')),)

    event = models.ForeignKey(Event, models.CASCADE)

    type = models.CharField(
        _('field type'),
        choices=FIELD_TYPES,
        max_length=10,
    )

    name = MultilingualField(
        models.CharField,
        _('field name'),
        max_length=100,
    )

    description = MultilingualField(
        models.TextField,
        _('description'),
        null=True,
        blank=True,
    )

    required = models.BooleanField(
        _('required'),
    )

    def get_value_for(self, registration):
        if self.type == self.TEXT_FIELD:
            value_set = self.textregistrationinformation_set
        elif self.type == self.BOOLEAN_FIELD:
            value_set = self.booleanregistrationinformation_set
        elif self.type == self.INTEGER_FIELD:
            value_set = self.integerregistrationinformation_set

        try:
            return value_set.get(registration=registration).value
        except (TextRegistrationInformation.DoesNotExist,
                BooleanRegistrationInformation.DoesNotExist,
                IntegerRegistrationInformation.DoesNotExist):
            return None

    def set_value_for(self, registration, value):
        if self.type == self.TEXT_FIELD:
            value_set = self.textregistrationinformation_set
        elif self.type == self.BOOLEAN_FIELD:
            value_set = self.booleanregistrationinformation_set
        elif self.type == self.INTEGER_FIELD:
            value_set = self.integerregistrationinformation_set

        try:
            field_value = value_set.get(registration=registration)
        except BooleanRegistrationInformation.DoesNotExist:
            field_value = BooleanRegistrationInformation()
        except TextRegistrationInformation.DoesNotExist:
            field_value = TextRegistrationInformation()
        except IntegerRegistrationInformation.DoesNotExist:
            field_value = IntegerRegistrationInformation()

        field_value.registration = registration
        field_value.field = self
        field_value.value = value
        field_value.save()

    def __str__(self):
        return "{} ({})".format(self.name, dict(self.FIELD_TYPES)[self.type])

    class Meta:
        order_with_respect_to = 'event'


Thom Wiggers's avatar
Thom Wiggers committed
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
class AbstractRegistrationInformation(models.Model):
    """Abstract to contain common things for registration information"""

    registration = models.ForeignKey(Registration, models.CASCADE)
    field = models.ForeignKey(RegistrationInformationField, models.CASCADE)
    changed = models.DateTimeField(_('last changed'), auto_now=True)

    def __str__(self):
        return '{} - {}: {}'.format(self.registration, self.field, self.value)

    class Meta:
        abstract = True


class BooleanRegistrationInformation(AbstractRegistrationInformation):
Joren Vrancken's avatar
Joren Vrancken committed
647
    """Checkbox information filled in by members when registering"""
Thom Wiggers's avatar
Thom Wiggers committed
648
649
650
651
652

    value = models.BooleanField()


class TextRegistrationInformation(AbstractRegistrationInformation):
Joren Vrancken's avatar
Joren Vrancken committed
653
    """Checkbox information filled in by members when registering"""
Thom Wiggers's avatar
Thom Wiggers committed
654
655
656
657
    value = models.TextField()


class IntegerRegistrationInformation(AbstractRegistrationInformation):
Joren Vrancken's avatar
Joren Vrancken committed
658
    """Checkbox information filled in by members when registering"""
Thom Wiggers's avatar
Thom Wiggers committed
659
    value = models.IntegerField()
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680


class FeedToken(models.Model):
    """Used to personalize the ical Feed"""

    member = models.OneToOneField('members.Member', models.CASCADE)
    token = models.CharField(max_length=32, editable=False)

    def save(self, *args, **kwargs):
        self.token = get_random_string(32)
        super().save(*args, **kwargs)

    @staticmethod
    def get_member(token):
        try:
            return FeedToken.objects.get(token=token).member
        except FeedToken.DoesNotExist:
            return None

    def __str__(self):
        return '{} ({})'.format(self.member.get_full_name(), self.token)