# # Copyright 2016,2017 # - Skia # - Sli # # 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. # # from django.conf import settings from django.core.exceptions import ValidationError from django.core.mail import EmailMultiAlternatives from django.db import models, transaction from django.db.models import Q from django.shortcuts import render from django.templatetags.static import static from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ from club.models import Club from core.models import Notification, Preferences, RealGroup, User class Sith(models.Model): """A one instance class storing all the modifiable infos.""" alert_msg = models.TextField(_("alert message"), default="", blank=True) info_msg = models.TextField(_("info message"), default="", blank=True) 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 NEWS_TYPES = [ ("NOTICE", _("Notice")), ("EVENT", _("Event")), ("WEEKLY", _("Weekly")), ("CALL", _("Call")), ] class News(models.Model): """News about club events.""" title = models.CharField(_("title"), max_length=64) 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."), ) type = models.CharField( _("type"), max_length=16, choices=NEWS_TYPES, default="EVENT" ) club = models.ForeignKey( Club, related_name="news", verbose_name=_("club"), on_delete=models.CASCADE, help_text=_("The club which organizes the event."), ) author = models.ForeignKey( User, related_name="owned_news", verbose_name=_("author"), on_delete=models.CASCADE, ) is_moderated = models.BooleanField(_("is moderated"), default=False) moderator = models.ForeignKey( User, related_name="moderated_news", verbose_name=_("moderator"), null=True, on_delete=models.SET_NULL, ) def __str__(self): return "%s: %s" % (self.type, self.title) def save(self, *args, **kwargs): super().save(*args, **kwargs) 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() 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 def can_be_edited_by(self, user): return user.is_com_admin def can_be_viewed_by(self, user): return self.is_moderated or user.is_com_admin def news_notification_callback(notif): count = ( News.objects.filter( Q(dates__start_date__gt=timezone.now(), is_moderated=False) | Q(type="NOTICE", is_moderated=False) ) .distinct() .count() ) if count: notif.viewed = False notif.param = "%s" % count notif.date = timezone.now() else: notif.viewed = True 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 """ news = models.ForeignKey( News, related_name="dates", verbose_name=_("news_date"), on_delete=models.CASCADE, ) start_date = models.DateTimeField(_("start_date"), null=True, blank=True) end_date = models.DateTimeField(_("end_date"), null=True, blank=True) def __str__(self): return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date) class Weekmail(models.Model): """The weekmail class. :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 """ title = models.CharField(_("title"), max_length=64, blank=True) 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: ordering = ["-id"] def __str__(self): return f"Weekmail {self.id} (sent: {self.sent}) - {self.title}" def send(self): """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. """ dest = [ i[0] for i in Preferences.objects.filter(receive_weekmail=True).values_list( "user__email" ) ] with transaction.atomic(): email = EmailMultiAlternatives( subject=self.title, body=self.render_text(), from_email=settings.SITH_COM_EMAIL, to=Sith.objects.first().weekmail_destinations.split(" "), bcc=dest, ) email.attach_alternative(self.render_html(), "text/html") email.send() self.sent = True self.save() Weekmail().save() def render_text(self): """Renders a pure text version of the mail for readers without HTML support.""" return render( None, "com/weekmail_renderer_text.jinja", context={"weekmail": self} ).content.decode("utf-8") def render_html(self): """Renders an HTML version of the mail with images and fancy CSS.""" return render( None, "com/weekmail_renderer_html.jinja", context={"weekmail": self} ).content.decode("utf-8") def get_banner(self): """Return an absolute link to the banner.""" return ( "http://" + settings.SITH_URL + static("com/img/weekmail_bannerV2P22.png") ) def get_footer(self): """Return an absolute link to the footer.""" return "http://" + settings.SITH_URL + static("com/img/weekmail_footerP22.png") def is_owned_by(self, user): if user.is_anonymous: return False return user.is_com_admin class WeekmailArticle(models.Model): weekmail = models.ForeignKey( Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True, on_delete=models.CASCADE, ) title = models.CharField(_("title"), max_length=64) content = models.TextField(_("content")) author = models.ForeignKey( User, related_name="owned_weekmail_articles", verbose_name=_("author"), on_delete=models.CASCADE, ) club = models.ForeignKey( Club, related_name="weekmail_articles", verbose_name=_("club"), on_delete=models.CASCADE, ) rank = models.IntegerField(_("rank"), default=-1) def __str__(self): return "%s - %s (%s)" % (self.title, self.author, self.club) def is_owned_by(self, user): if user.is_anonymous: return False return user.is_com_admin class Screen(models.Model): name = models.CharField(_("name"), max_length=128) def __str__(self): return self.name def active_posters(self): now = timezone.now() 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): name = models.CharField( _("name"), blank=False, null=False, max_length=128, default="" ) file = models.ImageField(_("file"), null=False, upload_to="com/posters") club = models.ForeignKey( Club, related_name="posters", verbose_name=_("club"), null=False, on_delete=models.CASCADE, ) 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) display_time = models.IntegerField( _("display time"), blank=False, null=False, default=15 ) is_moderated = models.BooleanField(_("is moderated"), default=False) moderator = models.ForeignKey( User, related_name="moderated_posters", verbose_name=_("moderator"), null=True, blank=True, on_delete=models.CASCADE, ) def __str__(self): return self.name def save(self, *args, **kwargs): if not self.is_moderated: 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() return super().save(*args, **kwargs) 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 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