models.py 11.1 KB
Newer Older
1 2 3 4
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
Sli's avatar
Sli committed
5
# - Sli <antoine@bartuccio.fr>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#

26 27 28 29 30
from django.db import models
from django.core import validators
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
Krophil's avatar
Krophil committed
31
from django.db import transaction
32
from django.core.urlresolvers import reverse
Skia's avatar
Skia committed
33
from django.utils import timezone
34

Skia's avatar
Skia committed
35
from core.models import User, MetaGroup, Group, SithFile
36

Krophil's avatar
Krophil committed
37

38 39 40 41 42 43
# Create your models here.

class Club(models.Model):
    """
    The Club class, made as a tree to allow nice tidy organization
    """
Skia's avatar
Skia committed
44
    id = models.AutoField(primary_key=True, db_index=True)
Skia's avatar
Skia committed
45
    name = models.CharField(_('name'), max_length=64)
46 47
    parent = models.ForeignKey('Club', related_name='children', null=True, blank=True)
    unix_name = models.CharField(_('unix name'), max_length=30, unique=True,
Krophil's avatar
Krophil committed
48 49 50 51 52 53 54 55 56 57 58
                                 validators=[
        validators.RegexValidator(
            r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$',
            _('Enter a valid unix name. This value may contain only '
              'letters, numbers ./-/_ characters.')
        ),
    ],
        error_messages={
        'unique': _("A club with that unix name already exists."),
    },
    )
59 60
    address = models.CharField(_('address'), max_length=254)
    # email = models.EmailField(_('email address'), unique=True) # This should, and will be generated automatically
61
    owner_group = models.ForeignKey(Group, related_name="owned_club",
Skia's avatar
Skia committed
62
                                    default=settings.SITH_GROUP_ROOT_ID)
Skia's avatar
Skia committed
63 64
    edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True)
    view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True)
Skia's avatar
Skia committed
65
    home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True,
Krophil's avatar
Krophil committed
66
                                on_delete=models.SET_NULL)
67

68 69 70
    class Meta:
        ordering = ['name', 'unix_name']

71 72 73 74 75 76 77 78 79 80 81 82 83
    def check_loop(self):
        """Raise a validation error when a loop is found within the parent list"""
        objs = []
        cur = self
        while cur.parent is not None:
            if cur in objs:
                raise ValidationError(_('You can not make loops in clubs'))
            objs.append(cur)
            cur = cur.parent

    def clean(self):
        self.check_loop()

Skia's avatar
Skia committed
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
    def _change_unixname(self, new_name):
        c = Club.objects.filter(unix_name=new_name).first()
        if c is None:
            if self.home:
                self.home.name = new_name
                self.home.save()
        else:
            raise ValidationError(_("A club with that unix_name already exists"))

    def make_home(self):
        if not self.home:
            home_root = SithFile.objects.filter(parent=None, name="clubs").first()
            root = User.objects.filter(username="root").first()
            if home_root and root:
                home = SithFile(parent=home_root, name=self.unix_name, owner=root)
                home.save()
                self.home = home
                self.save()

    def save(self, *args, **kwargs):
        with transaction.atomic():
            creation = False
Skia's avatar
Skia committed
106 107
            old = Club.objects.filter(id=self.id).first()
            if not old:
Skia's avatar
Skia committed
108 109 110 111 112 113
                creation = True
            else:
                if old.unix_name != self.unix_name:
                    self._change_unixname(self.unix_name)
            super(Club, self).save(*args, **kwargs)
            if creation:
Krophil's avatar
Krophil committed
114
                board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX)
Skia's avatar
Skia committed
115
                board.save()
Krophil's avatar
Krophil committed
116
                member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX)
Skia's avatar
Skia committed
117 118 119 120 121 122
                member.save()
                subscribers = Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()
                self.make_home()
                self.home.edit_groups = [board]
                self.home.view_groups = [member, subscribers]
                self.home.save()
Skia's avatar
Skia committed
123

124 125 126
    def __str__(self):
        return self.name

127 128 129
    def get_absolute_url(self):
        return reverse('club:club_view', kwargs={'club_id': self.id})

Skia's avatar
Skia committed
130 131 132
    def get_display_name(self):
        return self.name

Skia's avatar
Skia committed
133 134 135 136
    def is_owned_by(self, user):
        """
        Method to see if that object can be super edited by the given user
        """
Skia's avatar
Skia committed
137
        return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
Skia's avatar
Skia committed
138 139 140 141 142

    def can_be_edited_by(self, user):
        """
        Method to see if that object can be edited by the given user
        """
143
        return self.has_rights_in_club(user)
Skia's avatar
Skia committed
144 145 146 147 148

    def can_be_viewed_by(self, user):
        """
        Method to see if that object can be seen by the given user
        """
Skia's avatar
Skia committed
149
        sub = User.objects.filter(pk=user.pk).first()
Skia's avatar
Skia committed
150 151
        if sub is None:
            return False
152
        return sub.is_subscribed
Skia's avatar
Skia committed
153

Skia's avatar
Skia committed
154
    _memberships = {}
Krophil's avatar
Krophil committed
155

Skia's avatar
Skia committed
156 157 158 159
    def get_membership_for(self, user):
        """
        Returns the current membership the given user
        """
Skia's avatar
Skia committed
160 161 162 163 164 165 166 167 168 169
        try:
            return Club._memberships[self.id][user.id]
        except:
            m = self.members.filter(user=user.id).filter(end_date=None).first()
            try:
                Club._memberships[self.id][user.id] = m
            except:
                Club._memberships[self.id] = {}
                Club._memberships[self.id][user.id] = m
            return m
Skia's avatar
Skia committed
170

171 172 173 174
    def has_rights_in_club(self, user):
        m = self.get_membership_for(user)
        return m is not None and m.role > settings.SITH_MAXIMUM_FREE_ROLE

Krophil's avatar
Krophil committed
175

176 177 178 179 180 181 182 183 184 185 186
class Membership(models.Model):
    """
    The Membership class makes the connection between User and Clubs

    Both Users and Clubs can have many Membership objects:
       - a user can be a member of many clubs at a time
       - a club can have many members at a time too

    A User is currently member of all the Clubs where its Membership has an end_date set to null/None.
    Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past.
    """
Skia's avatar
Skia committed
187
    user = models.ForeignKey(User, verbose_name=_('user'), related_name="memberships", null=False, blank=False)
Skia's avatar
Skia committed
188
    club = models.ForeignKey(Club, verbose_name=_('club'), related_name="members", null=False, blank=False)
Skia's avatar
Skia committed
189
    start_date = models.DateField(_('start date'), default=timezone.now)
190
    end_date = models.DateField(_('end date'), null=True, blank=True)
191
    role = models.IntegerField(_('role'), choices=sorted(settings.SITH_CLUB_ROLES.items()),
Krophil's avatar
Krophil committed
192
                               default=sorted(settings.SITH_CLUB_ROLES.items())[0][0])
Skia's avatar
Skia committed
193
    description = models.CharField(_('description'), max_length=128, null=False, blank=True)
194 195

    def clean(self):
Skia's avatar
Skia committed
196
        sub = User.objects.filter(pk=self.user.pk).first()
197
        if sub is None or not sub.is_subscribed:
Skia's avatar
Skia committed
198 199
            raise ValidationError(_('User must be subscriber to take part to a club'))
        if Membership.objects.filter(user=self.user).filter(club=self.club).filter(end_date=None).exists():
200 201 202
            raise ValidationError(_('User is already member of that club'))

    def __str__(self):
Krophil's avatar
Krophil committed
203 204 205
        return self.club.name + ' - ' + self.user.username + ' - ' + str(settings.SITH_CLUB_ROLES[self.role]) + str(
            " - " + str(_('past member')) if self.end_date is not None else ""
        )
206

Skia's avatar
Skia committed
207 208 209 210 211 212 213 214 215 216
    def is_owned_by(self, user):
        """
        Method to see if that object can be super edited by the given user
        """
        return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)

    def can_be_edited_by(self, user):
        """
        Method to see if that object can be edited by the given user
        """
Skia's avatar
Skia committed
217 218
        if user.memberships:
            ms = user.memberships.filter(club=self.club, end_date=None).first()
Skia's avatar
Skia committed
219 220 221
            return (ms and ms.role >= self.role) or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
        return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)

Skia's avatar
Skia committed
222 223
    def get_absolute_url(self):
        return reverse('club:club_members', kwargs={'club_id': self.club.id})
Sli's avatar
Sli committed
224 225 226 227 228


class Mailing(models.Model):
    """
    This class correspond to a mailing list
Sli's avatar
Sli committed
229
    Remember that mailing lists should be validated by UTBM
Sli's avatar
Sli committed
230 231 232 233
    """
    club = models.ForeignKey(Club, verbose_name=_('Club'), related_name="mailings", null=False, blank=False)
    email = models.EmailField(_('Email address'), unique=True)

Sli's avatar
Sli committed
234 235 236 237 238
    def clean(self):
        if '@' + settings.SITH_MAILING_ALLOWED_DOMAIN not in self.email:
            raise ValidationError(_('Unothorized mailing domain'))
            super(Mailing, self).clean()

Sli's avatar
Sli committed
239
    def is_owned_by(self, user):
Sli's avatar
Sli committed
240
        return user.is_in_group(self) or user.is_root or user.is_board_member
Sli's avatar
Sli committed
241

Sli's avatar
Sli committed
242 243 244 245 246 247 248
    def can_view(self, user):
        return self.club.has_rights_in_club(user)

    def delete(self):
        for sub in self.subscriptions.all():
            sub.delete()
        super(Mailing, self).delete()
Sli's avatar
Sli committed
249

Sli's avatar
Sli committed
250 251 252 253 254 255 256 257 258
    def base_mail(self):
        return self.email.split('@')[0]

    def fetch_format(self):
        resp = self.base_mail() + ': '
        for sub in self.subscriptions.all():
            resp += sub.fetch_format()
        return resp

Sli's avatar
Sli committed
259 260 261 262 263 264
    def __str__(self):
        return "%s - %s" % (self.club, self.email)


class MailingSubscription(models.Model):
    """
Sli's avatar
Sli committed
265
    This class makes the link between user and mailing list
Sli's avatar
Sli committed
266 267 268
    """
    mailing = models.ForeignKey(Mailing, verbose_name=_('Mailing'), related_name="subscriptions", null=False, blank=False)
    user = models.ForeignKey(User, verbose_name=_('User'), related_name="mailing_subscriptions", null=True, blank=True)
Sli's avatar
Sli committed
269 270 271 272 273 274 275 276 277 278 279
    email = models.EmailField(_('Email address'), blank=False, null=False)

    class Meta:
        unique_together = (('user', 'email', 'mailing'),)

    def clean(self):
        if not self.user and not self.email:
            raise ValidationError(_("At least user or email is required"))
        if self.user and not self.email:
            self.email = self.user.email
        super(MailingSubscription, self).clean()
Sli's avatar
Sli committed
280 281 282 283 284 285

    def is_owned_by(self, user):
        return self.mailing.club.has_rights_in_club(user) or user.is_root

    def can_be_edited_by(self, user):
        return self.is_owned_by(user) or (user is not None and user.id == self.user.id)
Sli's avatar
Sli committed
286

Sli's avatar
Sli committed
287 288 289
    def fetch_format(self):
        return self.email + ' '

Sli's avatar
Sli committed
290 291 292 293 294 295
    def __str__(self):
        if self.user:
            user = str(self.user)
        else:
            user = _("Unregistered user")
        return "(%s) - %s : %s" % (self.mailing, user, self.email)