Sith/com/models.py

396 lines
12 KiB
Python
Raw Normal View History

#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr>
#
# 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 Software Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
2025-01-10 21:05:37 +00:00
from typing import Self
2024-06-24 11:07:36 +00:00
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.mail import EmailMultiAlternatives
2017-01-03 15:50:53 +00:00
from django.db import models, transaction
2025-01-06 21:55:23 +00:00
from django.db.models import F, Q
2024-06-24 11:07:36 +00:00
from django.shortcuts import render
from django.templatetags.static import static
2024-06-24 11:07:36 +00:00
from django.urls import reverse
from django.utils import timezone
2024-06-24 11:07:36 +00:00
from django.utils.translation import gettext_lazy as _
from club.models import Club
from core.models import Notification, Preferences, User
2017-06-12 06:59:03 +00:00
class Sith(models.Model):
2024-07-12 07:34:16 +00:00
"""A one instance class storing all the modifiable infos."""
2018-10-04 19:29:19 +00:00
alert_msg = models.TextField(_("alert message"), default="", blank=True)
info_msg = models.TextField(_("info message"), default="", blank=True)
2017-01-02 23:07:45 +00:00
weekmail_destinations = models.TextField(_("weekmail destinations"), default="")
def __str__(self):
return "⛩ Sith ⛩"
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_com_admin
2017-06-12 06:59:03 +00:00
2025-01-06 21:55:23 +00:00
class NewsQuerySet(models.QuerySet):
2025-01-10 21:05:37 +00:00
def moderated(self) -> Self:
2025-01-06 21:55:23 +00:00
return self.filter(is_moderated=True)
2017-06-12 06:59:03 +00:00
2025-01-10 21:05:37 +00:00
def viewable_by(self, user: User) -> Self:
"""Filter news that the given user can view.
If the user has the `com.view_unmoderated_news` permission,
all news are viewable.
Else the viewable news are those that are either moderated
or authored by the user.
"""
2025-01-09 23:45:25 +00:00
if user.has_perm("com.view_unmoderated_news"):
return self
2025-01-11 01:22:12 +00:00
q_filter = Q(is_moderated=True)
if user.is_authenticated:
q_filter |= Q(author_id=user.id)
return self.filter(q_filter)
2025-01-09 23:45:25 +00:00
class News(models.Model):
2024-12-16 15:32:07 +00:00
"""News about club events."""
2018-10-04 19:29:19 +00:00
title = models.CharField(_("title"), max_length=64)
2024-12-16 15:32:07 +00:00
summary = models.TextField(
_("summary"),
help_text=_(
"A description of the event (what is the activity ? "
"is there an associated clic ? is there a inscription form ?)"
),
)
content = models.TextField(
_("content"),
blank=True,
default="",
help_text=_("A more detailed and exhaustive description of the event."),
)
club = models.ForeignKey(
2024-12-16 15:32:07 +00:00
Club,
related_name="news",
verbose_name=_("club"),
on_delete=models.CASCADE,
help_text=_("The club which organizes the event."),
)
2018-10-04 19:29:19 +00:00
author = models.ForeignKey(
User,
related_name="owned_news",
verbose_name=_("author"),
2025-01-06 21:55:23 +00:00
on_delete=models.PROTECT,
2018-10-04 19:29:19 +00:00
)
is_moderated = models.BooleanField(_("is moderated"), default=False)
2018-10-04 19:29:19 +00:00
moderator = models.ForeignKey(
User,
related_name="moderated_news",
verbose_name=_("moderator"),
null=True,
2024-12-16 15:32:07 +00:00
on_delete=models.SET_NULL,
2018-10-04 19:29:19 +00:00
)
2016-12-23 17:40:12 +00:00
2025-01-06 21:55:23 +00:00
objects = NewsQuerySet.as_manager()
class Meta:
verbose_name = _("news")
permissions = [
("moderate_news", "Can moderate news"),
("view_unmoderated_news", "Can view non-moderated news"),
]
def __str__(self):
2025-01-06 21:55:23 +00:00
return self.title
def save(self, *args, **kwargs):
2024-06-27 12:46:43 +00:00
super().save(*args, **kwargs)
2025-01-06 21:55:23 +00:00
if self.is_moderated:
return
for user in User.objects.filter(
groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID]
2018-10-04 19:29:19 +00:00
):
Notification.objects.create(
2025-01-06 21:55:23 +00:00
user=user, url=reverse("com:news_admin_list"), type="NEWS_MODERATION"
)
2018-10-04 19:29:19 +00:00
def get_absolute_url(self):
return reverse("com:news_detail", kwargs={"news_id": self.id})
def get_full_url(self):
return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url())
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_com_admin or user == self.author
2025-01-09 23:45:25 +00:00
def can_be_edited_by(self, user: User):
return user.is_authenticated and (
self.author_id == user.id or user.has_perm("com.change_news")
)
2025-01-11 01:22:12 +00:00
def can_be_viewed_by(self, user: User):
return (
self.is_moderated
or user.has_perm("com.view_unmoderated_news")
or (user.is_authenticated and self.author_id == user.id)
)
def news_notification_callback(notif):
2025-01-06 21:55:23 +00:00
count = News.objects.filter(
dates__start_date__gt=timezone.now(), is_moderated=False
).count()
if count:
notif.viewed = False
2025-01-06 21:55:23 +00:00
notif.param = str(count)
notif.date = timezone.now()
else:
notif.viewed = True
2017-06-12 06:59:03 +00:00
2018-10-04 19:29:19 +00:00
class NewsDate(models.Model):
2025-01-06 21:55:23 +00:00
"""A date associated with news.
2025-01-06 21:55:23 +00:00
A [News][] can have multiple dates, for example if it is a recurring event.
"""
2018-10-04 19:29:19 +00:00
news = models.ForeignKey(
News,
related_name="dates",
verbose_name=_("news_date"),
on_delete=models.CASCADE,
)
2025-01-06 21:55:23 +00:00
start_date = models.DateTimeField(_("start_date"))
end_date = models.DateTimeField(_("end_date"))
class Meta:
verbose_name = _("news date")
verbose_name_plural = _("news dates")
constraints = [
models.CheckConstraint(
check=Q(end_date__gte=F("start_date")),
name="news_date_end_date_after_start_date",
)
]
def __str__(self):
2025-01-06 21:55:23 +00:00
return f"{self.news.title}: {self.start_date} - {self.end_date}"
2017-01-02 23:07:45 +00:00
2017-06-12 06:59:03 +00:00
2017-01-02 23:07:45 +00:00
class Weekmail(models.Model):
2024-07-12 07:34:16 +00:00
"""The weekmail class.
2019-08-26 18:30:05 +00:00
: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
2017-01-02 23:07:45 +00:00
"""
2018-10-04 19:29:19 +00:00
2017-01-03 15:50:53 +00:00
title = models.CharField(_("title"), max_length=64, blank=True)
2017-01-02 23:07:45 +00:00
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:
2018-10-04 19:29:19 +00:00
ordering = ["-id"]
2017-01-02 23:07:45 +00:00
def __str__(self):
return f"Weekmail {self.id} (sent: {self.sent}) - {self.title}"
2017-01-02 23:07:45 +00:00
def send(self):
2024-07-12 07:34:16 +00:00
"""Send the weekmail to all users with the receive weekmail option opt-in.
2019-08-26 18:30:05 +00:00
Also send the weekmail to the mailing list in settings.SITH_COM_EMAIL.
"""
2018-10-04 19:29:19 +00:00
dest = [
i[0]
for i in Preferences.objects.filter(receive_weekmail=True).values_list(
"user__email"
)
]
2017-01-03 15:50:53 +00:00
with transaction.atomic():
2017-01-11 00:34:16 +00:00
email = EmailMultiAlternatives(
2017-06-12 06:59:03 +00:00
subject=self.title,
body=self.render_text(),
from_email=settings.SITH_COM_EMAIL,
2018-10-04 19:29:19 +00:00
to=Sith.objects.first().weekmail_destinations.split(" "),
2017-06-12 06:59:03 +00:00
bcc=dest,
)
2017-01-11 00:34:16 +00:00
email.attach_alternative(self.render_html(), "text/html")
2017-01-10 17:30:27 +00:00
email.send()
2017-01-03 15:50:53 +00:00
self.sent = True
self.save()
Weekmail().save()
2017-01-02 23:07:45 +00:00
2017-01-11 00:34:16 +00:00
def render_text(self):
2024-07-12 07:34:16 +00:00
"""Renders a pure text version of the mail for readers without HTML support."""
2018-10-04 19:29:19 +00:00
return render(
None, "com/weekmail_renderer_text.jinja", context={"weekmail": self}
).content.decode("utf-8")
2017-01-10 17:30:27 +00:00
2017-01-11 00:34:16 +00:00
def render_html(self):
2024-07-12 07:34:16 +00:00
"""Renders an HTML version of the mail with images and fancy CSS."""
2018-10-04 19:29:19 +00:00
return render(
None, "com/weekmail_renderer_html.jinja", context={"weekmail": self}
).content.decode("utf-8")
2017-01-11 00:34:16 +00:00
2017-04-27 12:39:25 +00:00
def get_banner(self):
2024-07-12 07:34:16 +00:00
"""Return an absolute link to the banner."""
2022-06-01 20:46:12 +00:00
return (
2022-09-25 19:44:56 +00:00
"http://" + settings.SITH_URL + static("com/img/weekmail_bannerV2P22.png")
2022-06-01 20:46:12 +00:00
)
2018-03-23 21:44:18 +00:00
def get_footer(self):
2024-07-12 07:34:16 +00:00
"""Return an absolute link to the footer."""
return "http://" + settings.SITH_URL + static("com/img/weekmail_footerP22.png")
2017-04-27 12:39:25 +00:00
2017-01-15 22:09:30 +00:00
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_com_admin
2017-01-15 22:09:30 +00:00
2017-06-12 06:59:03 +00:00
2017-01-02 23:07:45 +00:00
class WeekmailArticle(models.Model):
2018-10-04 19:29:19 +00:00
weekmail = models.ForeignKey(
Weekmail,
related_name="articles",
verbose_name=_("weekmail"),
null=True,
on_delete=models.CASCADE,
2018-10-04 19:29:19 +00:00
)
2017-01-02 23:07:45 +00:00
title = models.CharField(_("title"), max_length=64)
content = models.TextField(_("content"))
2018-10-04 19:29:19 +00:00
author = models.ForeignKey(
User,
related_name="owned_weekmail_articles",
verbose_name=_("author"),
on_delete=models.CASCADE,
2018-10-04 19:29:19 +00:00
)
club = models.ForeignKey(
Club,
related_name="weekmail_articles",
verbose_name=_("club"),
on_delete=models.CASCADE,
2018-10-04 19:29:19 +00:00
)
rank = models.IntegerField(_("rank"), default=-1)
2017-01-02 23:07:45 +00:00
def __str__(self):
return "%s - %s (%s)" % (self.title, self.author, self.club)
2017-01-15 22:09:30 +00:00
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_com_admin
2017-01-15 22:09:30 +00:00
class Screen(models.Model):
name = models.CharField(_("name"), max_length=128)
def __str__(self):
return self.name
def active_posters(self):
now = timezone.now()
2018-10-04 19:29:19 +00:00
return self.posters.filter(is_moderated=True, date_begin__lte=now).filter(
Q(date_end__isnull=True) | Q(date_end__gte=now)
)
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_com_admin
class Poster(models.Model):
2018-10-04 19:29:19 +00:00
name = models.CharField(
_("name"), blank=False, null=False, max_length=128, default=""
)
file = models.ImageField(_("file"), null=False, upload_to="com/posters")
2018-10-04 19:29:19 +00:00
club = models.ForeignKey(
Club,
related_name="posters",
verbose_name=_("club"),
null=False,
on_delete=models.CASCADE,
2018-10-04 19:29:19 +00:00
)
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)
2018-10-04 19:29:19 +00:00
display_time = models.IntegerField(
_("display time"), blank=False, null=False, default=15
)
is_moderated = models.BooleanField(_("is moderated"), default=False)
2018-10-04 19:29:19 +00:00
moderator = models.ForeignKey(
User,
related_name="moderated_posters",
verbose_name=_("moderator"),
null=True,
blank=True,
on_delete=models.CASCADE,
2018-10-04 19:29:19 +00:00
)
2025-01-06 21:55:23 +00:00
class Meta:
permissions = [("moderate_poster", "Can moderate poster")]
2025-01-06 21:55:23 +00:00
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.is_moderated:
for user in User.objects.filter(
groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID]
2018-10-04 19:29:19 +00:00
):
Notification.objects.create(
user=user,
2018-10-04 19:29:19 +00:00
url=reverse("com:poster_moderate_list"),
type="POSTER_MODERATION",
)
2024-06-27 12:46:43 +00:00
return super().save(*args, **kwargs)
2017-12-22 11:53:43 +00:00
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"))
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_com_admin or len(user.clubs_with_rights) > 0
2017-10-23 07:30:26 +00:00
def can_be_moderated_by(self, user):
return user.is_com_admin
def get_display_name(self):
return self.club.get_display_name()
@property
def page(self):
return self.club.page