models.py 11 KB
Newer Older
1
2
3
4
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
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.
#
#

Skia's avatar
Skia committed
26
from django.shortcuts import render
Skia's avatar
Skia committed
27
from django.db import models, transaction
28
from django.db.models import Q
Skia's avatar
Skia committed
29
from django.utils.translation import ugettext_lazy as _
30
from django.utils import timezone
31
from django.urls import reverse
Skia's avatar
Skia committed
32
from django.conf import settings
Krophil's avatar
Krophil committed
33
from django.contrib.staticfiles.templatetags.staticfiles import static
Skia's avatar
Skia committed
34
from django.core.mail import EmailMultiAlternatives
35
from django.core.exceptions import ValidationError
Skia's avatar
Skia committed
36

37
38
39
from django.utils import timezone

from core.models import User, Preferences, RealGroup, Notification, SithFile
40
from club.models import Club
Krophil's avatar
Krophil committed
41

42

Skia's avatar
Skia committed
43
class Sith(models.Model):
44
    """A one instance class storing all the modifiable infos"""
Sli's avatar
Sli committed
45

Skia's avatar
Skia committed
46
47
    alert_msg = models.TextField(_("alert message"), default="", blank=True)
    info_msg = models.TextField(_("info message"), default="", blank=True)
Skia's avatar
Skia committed
48
    weekmail_destinations = models.TextField(_("weekmail destinations"), default="")
Skia's avatar
Skia committed
49
50
51
52
53
54
55

    def is_owned_by(self, user):
        return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)

    def __str__(self):
        return "⛩ Sith ⛩"

Krophil's avatar
Krophil committed
56

57
NEWS_TYPES = [
Sli's avatar
Sli committed
58
59
60
61
    ("NOTICE", _("Notice")),
    ("EVENT", _("Event")),
    ("WEEKLY", _("Weekly")),
    ("CALL", _("Call")),
Krophil's avatar
Krophil committed
62
63
]

64
65
66

class News(models.Model):
    """The news class"""
Sli's avatar
Sli committed
67

68
69
70
    title = models.CharField(_("title"), max_length=64)
    summary = models.TextField(_("summary"))
    content = models.TextField(_("content"))
Sli's avatar
Sli committed
71
72
73
    type = models.CharField(
        _("type"), max_length=16, choices=NEWS_TYPES, default="EVENT"
    )
74
75
76
    club = models.ForeignKey(
        Club, related_name="news", verbose_name=_("club"), on_delete=models.CASCADE
    )
Sli's avatar
Sli committed
77
    author = models.ForeignKey(
78
79
80
81
        User,
        related_name="owned_news",
        verbose_name=_("author"),
        on_delete=models.CASCADE,
Sli's avatar
Sli committed
82
    )
83
    is_moderated = models.BooleanField(_("is moderated"), default=False)
Sli's avatar
Sli committed
84
    moderator = models.ForeignKey(
85
86
87
88
89
        User,
        related_name="moderated_news",
        verbose_name=_("moderator"),
        null=True,
        on_delete=models.CASCADE,
Sli's avatar
Sli committed
90
    )
Skia's avatar
Skia committed
91
92
93
94
95
96
97
98
99

    def is_owned_by(self, user):
        return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user == self.author

    def can_be_edited_by(self, user):
        return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)

    def can_be_viewed_by(self, user):
        return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
100
101

    def get_absolute_url(self):
Sli's avatar
Sli committed
102
        return reverse("com:news_detail", kwargs={"news_id": self.id})
103

Krophil's avatar
Krophil committed
104
    def get_full_url(self):
Krophil's avatar
Krophil committed
105
        return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url())
Krophil's avatar
Krophil committed
106

107
108
109
    def __str__(self):
        return "%s: %s" % (self.type, self.title)

110
111
    def save(self, *args, **kwargs):
        super(News, self).save(*args, **kwargs)
Sli's avatar
Sli committed
112
113
114
115
116
117
118
119
120
121
122
123
        for u in (
            RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
            .first()
            .users.all()
        ):
            Notification(
                user=u,
                url=reverse("com:news_admin_list"),
                type="NEWS_MODERATION",
                param="1",
            ).save()

124
125

def news_notification_callback(notif):
Sli's avatar
Sli committed
126
127
128
129
130
131
132
133
    count = (
        News.objects.filter(
            Q(dates__start_date__gt=timezone.now(), is_moderated=False)
            | Q(type="NOTICE", is_moderated=False)
        )
        .distinct()
        .count()
    )
134
135
136
137
138
139
    if count:
        notif.viewed = False
        notif.param = "%s" % count
        notif.date = timezone.now()
    else:
        notif.viewed = True
Krophil's avatar
Krophil committed
140

Sli's avatar
Sli committed
141

142
143
144
145
146
147
148
class NewsDate(models.Model):
    """
    A date class, useful for weekly events, or for events that just have no date

    This class allows more flexibilty managing the dates related to a news, particularly when this news is weekly, since
    we don't have to make copies
    """
Sli's avatar
Sli committed
149

150
151
152
153
154
155
    news = models.ForeignKey(
        News,
        related_name="dates",
        verbose_name=_("news_date"),
        on_delete=models.CASCADE,
    )
Sli's avatar
Sli committed
156
157
    start_date = models.DateTimeField(_("start_date"), null=True, blank=True)
    end_date = models.DateTimeField(_("end_date"), null=True, blank=True)
158
159
160

    def __str__(self):
        return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date)
Skia's avatar
Skia committed
161

Krophil's avatar
Krophil committed
162

Skia's avatar
Skia committed
163
164
165
class Weekmail(models.Model):
    """
    The weekmail class
Sli's avatar
Sli committed
166
167
168
169
170
171
172

    :ivar title: Title of the weekmail
    :ivar intro: Introduction of the weekmail
    :ivar joke: Joke of the week
    :ivar protip: Tip of the week
    :ivar conclusion: Conclusion of the weekmail
    :ivar sent: Track if the weekmail has been sent
Skia's avatar
Skia committed
173
    """
Sli's avatar
Sli committed
174

Skia's avatar
Skia committed
175
    title = models.CharField(_("title"), max_length=64, blank=True)
Skia's avatar
Skia committed
176
177
178
179
180
181
182
    intro = models.TextField(_("intro"), blank=True)
    joke = models.TextField(_("joke"), blank=True)
    protip = models.TextField(_("protip"), blank=True)
    conclusion = models.TextField(_("conclusion"), blank=True)
    sent = models.BooleanField(_("sent"), default=False)

    class Meta:
Sli's avatar
Sli committed
183
        ordering = ["-id"]
Skia's avatar
Skia committed
184
185

    def send(self):
Sli's avatar
Sli committed
186
187
188
189
        """
        Send the weekmail to all users with the receive weekmail option opt-in.
        Also send the weekmail to the mailing list in settings.SITH_COM_EMAIL.
        """
Sli's avatar
Sli committed
190
191
192
193
194
195
        dest = [
            i[0]
            for i in Preferences.objects.filter(receive_weekmail=True).values_list(
                "user__email"
            )
        ]
Skia's avatar
Skia committed
196
        with transaction.atomic():
Skia's avatar
Skia committed
197
            email = EmailMultiAlternatives(
Krophil's avatar
Krophil committed
198
199
200
                subject=self.title,
                body=self.render_text(),
                from_email=settings.SITH_COM_EMAIL,
Sli's avatar
Sli committed
201
                to=Sith.objects.first().weekmail_destinations.split(" "),
Krophil's avatar
Krophil committed
202
203
                bcc=dest,
            )
Skia's avatar
Skia committed
204
            email.attach_alternative(self.render_html(), "text/html")
Skia's avatar
Skia committed
205
            email.send()
Skia's avatar
Skia committed
206
207
208
            self.sent = True
            self.save()
            Weekmail().save()
Skia's avatar
Skia committed
209

Skia's avatar
Skia committed
210
    def render_text(self):
Sli's avatar
Sli committed
211
212
213
        """
        Renders a pure text version of the mail for readers without HTML support.
        """
Sli's avatar
Sli committed
214
215
216
        return render(
            None, "com/weekmail_renderer_text.jinja", context={"weekmail": self}
        ).content.decode("utf-8")
Skia's avatar
Skia committed
217

Skia's avatar
Skia committed
218
    def render_html(self):
Sli's avatar
Sli committed
219
220
221
        """
        Renders an HTML version of the mail with images and fancy CSS.
        """
Sli's avatar
Sli committed
222
223
224
        return render(
            None, "com/weekmail_renderer_html.jinja", context={"weekmail": self}
        ).content.decode("utf-8")
Skia's avatar
Skia committed
225

Krophil's avatar
Krophil committed
226
    def get_banner(self):
Sli's avatar
Sli committed
227
228
229
        """
        Return an absolute link to the banner.
        """
230
        return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA19.jpg")
Soldat's avatar
Soldat committed
231
232

    def get_footer(self):
Sli's avatar
Sli committed
233
234
235
        """
        Return an absolute link to the footer.
        """
236
        return "http://" + settings.SITH_URL + static("com/img/weekmail_footerA19.jpg")
Krophil's avatar
Krophil committed
237

Skia's avatar
Skia committed
238
239
240
    def __str__(self):
        return "Weekmail %s (sent: %s) - %s" % (self.id, self.sent, self.title)

Skia's avatar
Skia committed
241
242
243
    def is_owned_by(self, user):
        return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)

Krophil's avatar
Krophil committed
244

Skia's avatar
Skia committed
245
class WeekmailArticle(models.Model):
Sli's avatar
Sli committed
246
    weekmail = models.ForeignKey(
247
248
249
250
251
        Weekmail,
        related_name="articles",
        verbose_name=_("weekmail"),
        null=True,
        on_delete=models.CASCADE,
Sli's avatar
Sli committed
252
    )
Skia's avatar
Skia committed
253
254
    title = models.CharField(_("title"), max_length=64)
    content = models.TextField(_("content"))
Sli's avatar
Sli committed
255
    author = models.ForeignKey(
256
257
258
259
        User,
        related_name="owned_weekmail_articles",
        verbose_name=_("author"),
        on_delete=models.CASCADE,
Sli's avatar
Sli committed
260
261
    )
    club = models.ForeignKey(
262
263
264
265
        Club,
        related_name="weekmail_articles",
        verbose_name=_("club"),
        on_delete=models.CASCADE,
Sli's avatar
Sli committed
266
267
    )
    rank = models.IntegerField(_("rank"), default=-1)
Skia's avatar
Skia committed
268

Skia's avatar
Skia committed
269
270
271
    def is_owned_by(self, user):
        return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)

272
273
    def __str__(self):
        return "%s - %s (%s)" % (self.title, self.author, self.club)
274
275
276
277
278
279
280


class Screen(models.Model):
    name = models.CharField(_("name"), max_length=128)

    def active_posters(self):
        now = timezone.now()
Sli's avatar
Sli committed
281
282
283
        return self.posters.filter(is_moderated=True, date_begin__lte=now).filter(
            Q(date_end__isnull=True) | Q(date_end__gte=now)
        )
284
285
286
287
288

    def is_owned_by(self, user):
        return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)

    def __str__(self):
289
        return "%s" % (self.name)
290

291

292
class Poster(models.Model):
Sli's avatar
Sli committed
293
294
295
    name = models.CharField(
        _("name"), blank=False, null=False, max_length=128, default=""
    )
296
    file = models.ImageField(_("file"), null=False, upload_to="com/posters")
Sli's avatar
Sli committed
297
    club = models.ForeignKey(
298
299
300
301
302
        Club,
        related_name="posters",
        verbose_name=_("club"),
        null=False,
        on_delete=models.CASCADE,
Sli's avatar
Sli committed
303
    )
304
305
306
    screens = models.ManyToManyField(Screen, related_name="posters")
    date_begin = models.DateTimeField(blank=False, null=False, default=timezone.now)
    date_end = models.DateTimeField(blank=True, null=True)
Sli's avatar
Sli committed
307
308
309
    display_time = models.IntegerField(
        _("display time"), blank=False, null=False, default=15
    )
310
    is_moderated = models.BooleanField(_("is moderated"), default=False)
Sli's avatar
Sli committed
311
312
313
314
315
316
    moderator = models.ForeignKey(
        User,
        related_name="moderated_posters",
        verbose_name=_("moderator"),
        null=True,
        blank=True,
317
        on_delete=models.CASCADE,
Sli's avatar
Sli committed
318
    )
319
320

    def save(self, *args, **kwargs):
321
        if not self.is_moderated:
Sli's avatar
Sli committed
322
323
324
325
326
327
328
329
330
331
            for u in (
                RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID)
                .first()
                .users.all()
            ):
                Notification(
                    user=u,
                    url=reverse("com:poster_moderate_list"),
                    type="POSTER_MODERATION",
                ).save()
332
333
        return super(Poster, self).save(*args, **kwargs)

Sli's avatar
Sli committed
334
335
336
337
    def clean(self, *args, **kwargs):
        if self.date_end and self.date_begin > self.date_end:
            raise ValidationError(_("Begin date should be before end date"))

338
    def is_owned_by(self, user):
Sli's avatar
Sli committed
339
340
341
        return user.is_in_group(
            settings.SITH_GROUP_COM_ADMIN_ID
        ) or Club.objects.filter(id__in=user.clubs_with_rights)
342

Nicolas Ballet's avatar
Nicolas Ballet committed
343
    def can_be_moderated_by(self, user):
344
345
        return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)

346
347
348
349
350
351
352
    def get_display_name(self):
        return self.club.get_display_name()

    @property
    def page(self):
        return self.club.page

353
354
    def __str__(self):
        return self.name