models.py 8.63 KB
Newer Older
Thom Wiggers's avatar
Thom Wiggers committed
1
import datetime
2
import logging
Thom Wiggers's avatar
Thom Wiggers committed
3
4

from django.contrib.auth.models import Permission
5
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
6
from django.core.validators import MinValueValidator
Thom Wiggers's avatar
Thom Wiggers committed
7
from django.db import models
Thom Wiggers's avatar
Thom Wiggers committed
8
from django.urls import reverse
9
from django.utils import timezone
Thom Wiggers's avatar
Thom Wiggers committed
10
from django.utils.translation import ugettext_lazy as _
11

Thom Wiggers's avatar
Thom Wiggers committed
12
from members.models import Member
13
from utils.translation import ModelTranslateMeta, MultilingualField
Thom Wiggers's avatar
Thom Wiggers committed
14

15
16
17
logger = logging.getLogger(__name__)


Thom Wiggers's avatar
Thom Wiggers committed
18
19
20
21
22
23
24
25
class CommitteeManager(models.Manager):
    """Returns committees only"""
    def get_queryset(self):
        return (super().get_queryset()
                .exclude(board__is_board=True))


class ActiveCommitteeManager(models.Manager):
Thom Wiggers's avatar
Thom Wiggers committed
26
27
    """Returns active committees only"""
    def get_queryset(self):
Thom Wiggers's avatar
Thom Wiggers committed
28
        return (super().get_queryset()
29
                .exclude(board__is_board=True)
30
                .exclude(active=False))
Thom Wiggers's avatar
Thom Wiggers committed
31
32


33
class Committee(models.Model, metaclass=ModelTranslateMeta):
Thom Wiggers's avatar
Thom Wiggers committed
34
35
    """A committee"""

36
    unfiltered_objects = models.Manager()
Thom Wiggers's avatar
Thom Wiggers committed
37
    objects = CommitteeManager()
38
    active_committees = ActiveCommitteeManager()
Thom Wiggers's avatar
Thom Wiggers committed
39

40
41
    name = MultilingualField(
        models.CharField,
Thom Wiggers's avatar
Thom Wiggers committed
42
43
44
45
46
        max_length=40,
        verbose_name=_('Committee name'),
        unique=True,
    )

47
48
    description = MultilingualField(
        models.TextField,
Thom Wiggers's avatar
Thom Wiggers committed
49
50
51
52
53
        verbose_name=_('Description'),
    )

    photo = models.ImageField(
        verbose_name=_('Image'),
Thom Wiggers's avatar
Thom Wiggers committed
54
        upload_to='public/committeephotos/',
Thom Wiggers's avatar
Thom Wiggers committed
55
56
        null=True,
        blank=True,
Thom Wiggers's avatar
Thom Wiggers committed
57
58
59
60
61
62
63
64
65
66
67
68
69
    )

    members = models.ManyToManyField(
        Member,
        through='CommitteeMembership'
    )

    permissions = models.ManyToManyField(
        Permission,
        verbose_name=_('permissions'),
        blank=True,
    )

Thom Wiggers's avatar
Thom Wiggers committed
70
71
72
73
74
    since = models.DateField(
        _('founded in'),
        null=True,
        blank=True,
    )
Thom Wiggers's avatar
Thom Wiggers committed
75

Thom Wiggers's avatar
Thom Wiggers committed
76
77
78
79
80
    until = models.DateField(
        _('existed until'),
        null=True,
        blank=True,
    )
Thom Wiggers's avatar
Thom Wiggers committed
81

82
83
    active = models.BooleanField(default=False)

Thom Wiggers's avatar
Thom Wiggers committed
84
85
    contact_email = models.EmailField(_('contact email address'))

Thom Wiggers's avatar
Thom Wiggers committed
86
87
88
89
90
91
    wiki_namespace = models.CharField(
        _('Wiki namespace'),
        null=True,
        blank=True,
        max_length=50)

Thom Wiggers's avatar
Thom Wiggers committed
92
93
94
    def __str__(self):
        return self.name

Thom Wiggers's avatar
Thom Wiggers committed
95
    def get_absolute_url(self):
96
        return reverse('activemembers:committee', args=[str(self.pk)])
Thom Wiggers's avatar
Thom Wiggers committed
97

Thom Wiggers's avatar
Thom Wiggers committed
98
99
100
101
102
    class Meta:
        verbose_name = _('committee')
        verbose_name_plural = _('committees')


Thom Wiggers's avatar
Thom Wiggers committed
103
104
105
106
107
108
class BoardManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_board=True)


class Board(Committee):
109
110
111
112
113
    """ Because Board inherits from Committee, Django creates a OneToOneField
    linking the two models together. This can be accessed as usual;
    given a Committee or Board b, one can access b.board, which will either
    return the object b if b is a Board, or a Board.DoesNotExist exception.
    """
Thom Wiggers's avatar
Thom Wiggers committed
114
115
116
117
118
119
120
    objects = BoardManager()

    is_board = models.BooleanField(
        verbose_name=_('Is this a board'),
        default=True,
    )

121
122
123
    class Meta:
        ordering = ['-since']

Thom Wiggers's avatar
Thom Wiggers committed
124
    def get_absolute_url(self):
125
        return reverse('activemembers:board', args=[str(self.pk)])
Thom Wiggers's avatar
Thom Wiggers committed
126

Thom Wiggers's avatar
Thom Wiggers committed
127

Thom Wiggers's avatar
Thom Wiggers committed
128
129
130
131
class ActiveMembershipManager(models.Manager):
    """Get only active memberships"""
    def get_queryset(self):
        """Get the currently active committee memberships"""
132
        return super().get_queryset().exclude(until__lt=timezone.now().date())
Thom Wiggers's avatar
Thom Wiggers committed
133
134


135
class CommitteeMembership(models.Model, metaclass=ModelTranslateMeta):
Thom Wiggers's avatar
Thom Wiggers committed
136
    objects = models.Manager()
Thom Wiggers's avatar
Thom Wiggers committed
137
    active_memberships = ActiveMembershipManager()
Thom Wiggers's avatar
Thom Wiggers committed
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

    member = models.ForeignKey(
        Member,
        on_delete=models.CASCADE,
        verbose_name=_('Member'),
    )

    committee = models.ForeignKey(
        Committee,
        on_delete=models.CASCADE,
        verbose_name=_('Committee'),
    )

    since = models.DateField(
        verbose_name=_('Committee member since'),
        help_text=_('The date this member joined the committee in this role'),
Thom Wiggers's avatar
Thom Wiggers committed
154
        default=datetime.date.today
Thom Wiggers's avatar
Thom Wiggers committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
    )

    until = models.DateField(
        verbose_name=_('Committee member until'),
        help_text=_("A member of this committee until this time "
                    "(can't be in the future)."),
        blank=True,
        null=True,
    )

    chair = models.BooleanField(
        verbose_name=_('Chair of the committee'),
        help_text=_('There can only be one chair at a time!'),
        default=False,
    )

171
172
    role = MultilingualField(
        models.CharField,
Thom Wiggers's avatar
Thom Wiggers committed
173
174
175
176
177
178
179
        _('role'),
        help_text=_('The role of this member'),
        max_length=255,
        blank=True,
        null=True,
    )

180
181
182
183
184
185
186
187
188
189
190
    @property
    def initial_connected_membership(self):
        """ Find the oldest membership directly connected to the current one"""
        qs = CommitteeMembership.objects.filter(committee=self.committee,
                                                member=self.member,
                                                until=self.since)
        if qs.count() >= 1:  # should actually only be one; should be unique
            return qs.first().initial_connected_membership
        else:
            return self

Thom Wiggers's avatar
Thom Wiggers committed
191
192
193
    @property
    def is_active(self):
        """Is this membership currently active"""
194
        return self.until is None or self.until > timezone.now().date()
Thom Wiggers's avatar
Thom Wiggers committed
195
196
197
198
199
200

    def clean(self):
        """Validation"""
        if self.until and (not self.since or self.until < self.since):
            raise ValidationError(
                {'until': _("End date can't be before start date")})
201
202
203
204
205
206
        try:
            if self.until and self.committee.board:
                raise ValidationError(
                    {'until': _("End date cannot be set for boards")})
        except Board.DoesNotExist:
            pass
Thom Wiggers's avatar
Thom Wiggers committed
207
208
209
210
211

    def validate_unique(self, *args, **kwargs):
        """ Check uniqueness"""
        super().validate_unique(*args, **kwargs)
        # Check if a committee has more than one chair
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
        if self.chair:
            chairs = (CommitteeMembership.objects
                      .filter(committee=self.committee,
                              chair=True))
            for chair in chairs:
                if chair.pk == self.pk:
                    continue
                if ((chair.until is None and
                        (self.until is None or self.until > chair.since)) or
                    (self.until is None and self.since < chair.until) or
                    (self.until and chair.until and
                        self.since < chair.until and
                        self.until > chair.since)):
                    raise ValidationError({
                        NON_FIELD_ERRORS:
                        _('There already is a chair for this time period')})

        # check if this member is already in the committee in this period
        memberships = (CommitteeMembership.objects
                       .filter(committee=self.committee,
                               member=self.member))
        for mship in memberships:
            if mship.pk == self.pk:
                continue
            if ((mship.until is None and
                    (self.until is None or self.until > mship.since)) or
                (self.until is None and self.since < mship.until) or
                (self.until and mship.until and
                    self.since < mship.until and
                    self.until > mship.since)):
242
243
244
                raise ValidationError({
                    'member': _('This member is already in the committee for '
                                'this period')})
245
246
247

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
248
249
250
251
252
253
        self.member.user.is_staff = (
            self.member
            .committeemembership_set
            .exclude(
                until__lt=timezone.now().date())
            .count()) >= 1
254
255
        self.member.user.save()

Thom Wiggers's avatar
Thom Wiggers committed
256
    def __str__(self):
257
258
259
260
        return "{} membership of {} since {}, until {}".format(self.member,
                                                               self.committee,
                                                               self.since,
                                                               self.until)
Thom Wiggers's avatar
Thom Wiggers committed
261
262
263
264

    class Meta:
        verbose_name = _('committee membership')
        verbose_name_plural = _('committee memberships')
Joost Rijneveld's avatar
Joost Rijneveld committed
265
266


267
class Mentorship(models.Model):
268
269
270
271
272
273
    member = models.ForeignKey(
        Member,
        on_delete=models.CASCADE,
        verbose_name=_('Member'),
    )
    year = models.IntegerField(validators=MinValueValidator(1990))
Joost Rijneveld's avatar
Joost Rijneveld committed
274
275

    def __str__(self):
276
277
278
279
280
        return _("{name} mentor in {year}").format(name=self.member,
                                                   year=self.year)

    class Meta:
        unique_together = ('member', 'year')