diff --git a/club/templates/club/club_tools.jinja b/club/templates/club/club_tools.jinja index 5207a315..e35c9bc2 100644 --- a/club/templates/club/club_tools.jinja +++ b/club/templates/club/club_tools.jinja @@ -11,6 +11,7 @@
  • {% trans %}Edit Trombi{% endtrans %}
  • {% else %}
  • {% trans %}New Trombi{% endtrans %}
  • +
  • {% trans %}Posters{% endtrans %}
  • {% endif %}

    {% trans %}Counters:{% endtrans %}

    diff --git a/club/urls.py b/club/urls.py index a4022b16..4d41e120 100644 --- a/club/urls.py +++ b/club/urls.py @@ -50,4 +50,8 @@ urlpatterns = [ url(r'^(?P[0-9]+)/mailing/delete$', MailingDeleteView.as_view(), name='mailing_delete'), url(r'^(?P[0-9]+)/mailing/delete/subscription$', MailingSubscriptionDeleteView.as_view(), name='mailing_subscription_delete'), url(r'^membership/(?P[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'), + url(r'^(?P[0-9]+)/poster$', PosterListView.as_view(), name='poster_list'), + url(r'^(?P[0-9]+)/poster/create$', PosterCreateView.as_view(), name='poster_create'), + url(r'^(?P[0-9]+)/poster/(?P[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), + url(r'^(?P[0-9]+)/poster/(?P[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), ] diff --git a/club/views.py b/club/views.py index 4841c56e..7cc4cfb7 100644 --- a/club/views.py +++ b/club/views.py @@ -37,12 +37,14 @@ from ajax_select.fields import AutoCompleteSelectField from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404, redirect -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, PageEditViewBase +from core.views import CanCreateMixin, CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, PageEditViewBase from core.views.forms import SelectDate, SelectDateTime from club.models import Club, Membership, Mailing, MailingSubscription from sith.settings import SITH_MAXIMUM_FREE_ROLE from counter.models import Selling, Counter from core.models import User, PageRev +from com.views import PosterListBaseView, PosterCreateBaseView, PosterEditBaseView, PosterDeleteBaseView +from com.models import Poster from django.conf import settings @@ -86,8 +88,9 @@ class MailingSubscriptionForm(forms.ModelForm): class ClubTabsMixin(TabedViewMixin): def get_tabs_title(self): - if isinstance(self.object, PageRev): - self.object = self.object.page.club + obj = self.get_object() + if isinstance(obj, PageRev): + self.object = obj.page.club return self.object.get_display_name() def get_list_of_tabs(self): @@ -141,6 +144,11 @@ class ClubTabsMixin(TabedViewMixin): 'slug': 'mailing', 'name': _("Mailing list"), }) + tab_list.append({ + 'url': reverse('club:poster_list', kwargs={'club_id': self.object.id}), + 'slug': 'posters', + 'name': _("Posters list"), + }) if self.request.user.is_owner(self.object): tab_list.append({ 'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}), @@ -592,3 +600,51 @@ class MailingAutoCleanView(View): def get(self, request, *args, **kwargs): self.mailing.subscriptions.all().delete() return redirect('club:mailing', club_id=self.mailing.club.id) + + +class PosterListView(ClubTabsMixin, PosterListBaseView, CanViewMixin): + """List communication posters""" + + def get_object(self): + return self.club + + def get_context_data(self, **kwargs): + kwargs = super(PosterListView, self).get_context_data(**kwargs) + kwargs['app'] = "club" + kwargs['club'] = self.club + return kwargs + + +class PosterCreateView(PosterCreateBaseView, CanCreateMixin): + """Create communication poster""" + + pk_url_kwarg = "club_id" + + def get_object(self): + obj = super(PosterCreateView, self).get_object() + if not obj: + return self.club + return obj + + def get_success_url(self, **kwargs): + return reverse_lazy('club:poster_list', kwargs={'club_id': self.club.id}) + + +class PosterEditView(ClubTabsMixin, PosterEditBaseView, CanEditMixin): + """Edit communication poster""" + + def get_success_url(self): + return reverse_lazy('club:poster_list', kwargs={'club_id': self.club.id}) + + def get_context_data(self, **kwargs): + kwargs = super(PosterEditView, self).get_context_data(**kwargs) + kwargs['app'] = "club" + return kwargs + + +class PosterDeleteView(PosterDeleteBaseView, ClubTabsMixin, CanEditMixin): + """Delete communication poster""" + + def get_success_url(self): + return reverse_lazy('club:poster_list', kwargs={'club_id': self.club.id}) + diff --git a/com/admin.py b/com/admin.py index db60dd05..df553be2 100644 --- a/com/admin.py +++ b/com/admin.py @@ -39,4 +39,6 @@ class WeekmailAdmin(SearchModelAdmin): admin.site.register(Sith) admin.site.register(News, NewsAdmin) admin.site.register(Weekmail, WeekmailAdmin) +admin.site.register(Screen) +admin.site.register(Poster) diff --git a/com/migrations/0004_auto_20171221_1614.py b/com/migrations/0004_auto_20171221_1614.py new file mode 100644 index 00000000..e8d17887 --- /dev/null +++ b/com/migrations/0004_auto_20171221_1614.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0010_auto_20170912_2028'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('com', '0003_auto_20170115_2300'), + ] + + operations = [ + migrations.CreateModel( + name='Poster', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), + ('name', models.CharField(verbose_name='name', max_length=128, default='')), + ('file', models.ImageField(verbose_name='file', upload_to='com/posters')), + ('date_begin', models.DateTimeField(default=django.utils.timezone.now)), + ('date_end', models.DateTimeField(blank=True, null=True)), + ('display_time', models.IntegerField(verbose_name='display time', default=30)), + ('is_moderated', models.BooleanField(verbose_name='is moderated', default=False)), + ('club', models.ForeignKey(verbose_name='club', related_name='posters', to='club.Club')), + ('moderator', models.ForeignKey(verbose_name='moderator', blank=True, null=True, related_name='moderated_posters', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Screen', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), + ('name', models.CharField(verbose_name='name', max_length=128)), + ], + ), + migrations.AddField( + model_name='poster', + name='screens', + field=models.ManyToManyField(related_name='posters', to='com.Screen'), + ), + ] diff --git a/com/models.py b/com/models.py index 3d2089c5..3069f8bd 100644 --- a/com/models.py +++ b/com/models.py @@ -2,6 +2,7 @@ # # Copyright 2016,2017 # - Skia +# - Sli # # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, # http://ae.utbm.fr. @@ -31,11 +32,16 @@ from django.core.urlresolvers import reverse from django.conf import settings from django.contrib.staticfiles.templatetags.staticfiles import static from django.core.mail import EmailMultiAlternatives +from django.core.exceptions import ValidationError -from core.models import User, Preferences, RealGroup, Notification +from django.utils import timezone + +from core.models import User, Preferences, RealGroup, Notification, SithFile from club.models import Club + + class Sith(models.Model): """A one instance class storing all the modifiable infos""" alert_msg = models.TextField(_("alert message"), default="", blank=True) @@ -183,3 +189,54 @@ class WeekmailArticle(models.Model): def __str__(self): return "%s - %s (%s)" % (self.title, self.author, self.club) + + +class Screen(models.Model): + name = models.CharField(_("name"), max_length=128) + + 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): + return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + + def __str__(self): + return "%s" % (self.name) + + +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) + 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=30) + is_moderated = models.BooleanField(_("is moderated"), default=False) + moderator = models.ForeignKey(User, related_name="moderated_posters", verbose_name=_("moderator"), null=True, blank=True) + + def save(self, *args, **kwargs): + if self.date_end and self.date_begin > self.date_end: + raise ValidationError(_("Begin date should be before end date")) + 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(Poster, self).save(*args, **kwargs) + + def is_owned_by(self, user): + return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or Club.objects.filter(id__in=user.clubs_with_rights) + + def can_be_moderated_by(self, user): + return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + + def get_display_name(self): + return self.club.get_display_name() + + @property + def page(self): + return self.club.page + + def __str__(self): + return self.name diff --git a/com/templates/com/poster_edit.jinja b/com/templates/com/poster_edit.jinja new file mode 100644 index 00000000..5e7f15cb --- /dev/null +++ b/com/templates/com/poster_edit.jinja @@ -0,0 +1,43 @@ +{% extends "core/base.jinja" %} + +{% block title %} +{% trans %}Poster{% endtrans %} +{% endblock %} + +{% block content %} +
    + +
    + +

    {% trans %}Posters - edit{% endtrans %}

    + +
    + +
    + +
    + {% csrf_token %} + {{ form.as_p() }} +

    +
    + + +
    + +
    +{% endblock %} + + + diff --git a/com/templates/com/poster_list.jinja b/com/templates/com/poster_list.jinja new file mode 100644 index 00000000..7bd340fd --- /dev/null +++ b/com/templates/com/poster_list.jinja @@ -0,0 +1,67 @@ +{% extends "core/base.jinja" %} + +{% block script %} +{{ super() }} + +{% endblock %} + + +{% block title %} +{% trans %}Poster{% endtrans %} +{% endblock %} + +{% block content %} +
    + +
    +

    {% trans %}Posters{% endtrans %}

    + +
    + +
    + + {% if poster_list.count() == 0 %} +
    {% trans %}No posters{% endtrans %}
    + {% else %} + + {% for poster in poster_list %} +
    +
    {{ poster.name }}
    +
    +
    +
    {{ poster.date_begin | date("d/M/Y H:m") }}
    +
    {{ poster.date_end | date("d/M/Y H:m") }}
    +
    + {% if app == "com" %} + {% trans %}Edit{% endtrans %} + {% elif app == "club" %} + {% trans %}Edit{% endtrans %} + {% endif %} +
    +
      + {% for screen in poster.screens.all() %} +
    • {{ screen }}
    • + {% endfor %} +
    +
    +
    + {% endfor %} + + {% endif %} + +
    + +
    + +
    +{% endblock %} + + + diff --git a/com/templates/com/poster_moderate.jinja b/com/templates/com/poster_moderate.jinja new file mode 100644 index 00000000..d9b34082 --- /dev/null +++ b/com/templates/com/poster_moderate.jinja @@ -0,0 +1,39 @@ +{% extends "core/base.jinja" %} + +{% block script %} +{{ super() }} + +{% endblock %} + +{% block content %} +
    + +
    + +

    {% trans %}Posters - moderation{% endtrans %}

    +
    + +
    + + {% if object_list.count == 0 %} +
    {% trans %}No objects{% endtrans %}
    + {% else %} + + {% for poster in object_list %} +
    +
    {{ poster.name }}
    +
    + Moderate +
    + {% endfor %} + + {% endif %} + +
    + +
    + +
    +{% endblock %} diff --git a/com/templates/com/screen_edit.jinja b/com/templates/com/screen_edit.jinja new file mode 100644 index 00000000..70aa2b6c --- /dev/null +++ b/com/templates/com/screen_edit.jinja @@ -0,0 +1,34 @@ +{% extends "core/base.jinja" %} + +{% block title %} +{% trans %}Screen{% endtrans %} +{% endblock %} + +{% block content %} +
    + +
    + +

    {% trans %}Screen - edit{% endtrans %}

    + +
    + +
    + +
    + {% csrf_token %} + {{ form.as_p() }} +

    +
    + +
    + +
    +{% endblock %} + + + diff --git a/com/templates/com/screen_list.jinja b/com/templates/com/screen_list.jinja new file mode 100644 index 00000000..27ff7354 --- /dev/null +++ b/com/templates/com/screen_list.jinja @@ -0,0 +1,39 @@ +{% extends "core/base.jinja" %} + +{% block title %} +{% trans %}Screens{% endtrans %} +{% endblock %} + +{% block content %} +
    + +
    +

    {% trans %}Screens{% endtrans %}

    + +
    + +
    + + {% if screen_list.count() == 0 %} +
    {% trans %}No screens{% endtrans %}
    + {% else %} + + {% for screen in screen_list %} + + {% endfor %} + + {% endif %} + +
    + +
    +{% endblock %} + + + diff --git a/com/templates/com/screen_slideshow.jinja b/com/templates/com/screen_slideshow.jinja new file mode 100644 index 00000000..14ca1b46 --- /dev/null +++ b/com/templates/com/screen_slideshow.jinja @@ -0,0 +1,30 @@ + + + + {% trans %}Slideshow{% endtrans %} + + + +
    + +
    + {% for poster in posters %} +
    + +
    + {% endfor %} +
    + +
    + {% for poster in posters %} +
    + {% endfor %} +
    + +
    + +
    + + + + diff --git a/com/urls.py b/com/urls.py index 3f172693..9f5b0d97 100644 --- a/com/urls.py +++ b/com/urls.py @@ -47,5 +47,16 @@ urlpatterns = [ url(r'^mailings$', MailingListAdminView.as_view(), name='mailing_admin'), url(r'^mailings/(?P[0-9]+)/moderate$', MailingModerateView.as_view(), name='mailing_moderate'), url(r'^mailings/(?P[0-9]+)/delete$', MailingDeleteView.as_view(redirect_page='com:mailing_admin'), name='mailing_delete'), + url(r'^poster$', PosterListView.as_view(), name='poster_list'), + url(r'^poster/create$', PosterCreateView.as_view(), name='poster_create'), + url(r'^poster/(?P[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), + url(r'^poster/(?P[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), + url(r'^poster/moderate$', PosterModerateListView.as_view(), name='poster_moderate_list'), + url(r'^poster/(?P[0-9]+)/moderate$', PosterModerateView.as_view(), name='poster_moderate'), + url(r'^screen$', ScreenListView.as_view(), name='screen_list'), + url(r'^screen/create$', ScreenCreateView.as_view(), name='screen_create'), + url(r'^screen/(?P[0-9]+)/slideshow$', ScreenSlideshowView.as_view(), name='screen_slideshow'), + url(r'^screen/(?P[0-9]+)/edit$', ScreenEditView.as_view(), name='screen_edit'), + url(r'^screen/(?P[0-9]+)/delete$', ScreenDeleteView.as_view(), name='screen_delete'), ] diff --git a/com/views.py b/com/views.py index 34bd99d0..dc8c1384 100644 --- a/com/views.py +++ b/com/views.py @@ -40,7 +40,7 @@ from django import forms from datetime import timedelta -from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle +from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin from core.views.forms import SelectDateTime from core.models import Notification, RealGroup, User @@ -52,6 +52,23 @@ from club.models import Club, Mailing sith = Sith.objects.first +class PosterForm(forms.ModelForm): + class Meta: + model = Poster + fields = ['name', 'file', 'club', 'screens', 'date_begin', 'date_end', 'display_time'] + widgets = { + 'screens': forms.CheckboxSelectMultiple, + 'is_moderated': forms.HiddenInput() + } + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user', None) + super(PosterForm, self).__init__(*args, **kwargs) + if self.user: + if not self.user.is_com_admin: + self.fields['club'].queryset = Club.objects.filter(id__in=self.user.clubs_with_rights) + + class ComTabsMixin(TabedViewMixin): def get_tabs_title(self): return _("Communication administration") @@ -88,9 +105,27 @@ class ComTabsMixin(TabedViewMixin): 'slug': 'mailings', 'name': _("Mailing lists administration"), }) + tab_list.append({ + 'url': reverse('com:poster_list'), + 'slug': 'posters', + 'name': _("Posters list"), + }) + tab_list.append({ + 'url': reverse('com:screen_list'), + 'slug': 'screens', + 'name': _("Screens list"), + }) return tab_list +class IsComAdminMixin(View): + + def dispatch(self, request, *args, **kwargs): + if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)): + raise PermissionDenied + return super(IsComAdminMixin, self).dispatch(request, *args, **kwargs) + + class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView): model = Sith template_name = 'core/edit.jinja' @@ -465,3 +500,222 @@ class MailingModerateView(View): return redirect('com:mailing_admin') raise PermissionDenied + + +class PosterListBaseView(ListView): + """List communication posters""" + current_tab = "posters" + model = Poster + template_name = 'com/poster_list.jinja' + + def dispatch(self, request, *args, **kwargs): + club_id = kwargs.pop('club_id', None) + self.club = None + if club_id: + self.club = get_object_or_404(Club, pk=club_id) + return super(PosterListBaseView, self).dispatch(request, *args, **kwargs) + + def get_queryset(self): + if self.request.user.is_com_admin: + return Poster.objects.all().order_by('-date_begin') + else: + return Poster.objects.filter(club=self.club.id) + + def get_context_data(self, **kwargs): + kwargs = super(PosterListBaseView, self).get_context_data(**kwargs) + if not self.request.user.is_com_admin: + kwargs['club'] = self.club + return kwargs + + +class PosterCreateBaseView(CreateView): + """Create communication poster""" + current_tab = "posters" + form_class = PosterForm + template_name = 'core/create.jinja' + + def get_queryset(self): + return Poster.objects.all() + + def dispatch(self, request, *args, **kwargs): + if 'club_id' in kwargs: + self.club = get_object_or_404(Club, pk=kwargs['club_id']) + return super(PosterCreateBaseView, self).dispatch(request, *args, **kwargs) + + def get_form_kwargs(self): + kwargs = super(PosterCreateBaseView, self).get_form_kwargs() + kwargs.update({'user': self.request.user}) + return kwargs + + def get_context_data(self, **kwargs): + kwargs = super(PosterCreateBaseView, self).get_context_data(**kwargs) + if not self.request.user.is_com_admin: + kwargs['club'] = self.club + return kwargs + + def form_valid(self, form): + if self.request.user.is_com_admin: + form.instance.is_moderated = True + return super(PosterCreateBaseView, self).form_valid(form) + + +class PosterEditBaseView(UpdateView): + """Edit communication poster""" + pk_url_kwarg = "poster_id" + current_tab = "posters" + form_class = PosterForm + template_name = 'com/poster_edit.jinja' + + def dispatch(self, request, *args, **kwargs): + if 'club_id' in kwargs and kwargs['club_id']: + try: + self.club = Club.objects.get(pk=kwargs['club_id']) + except Club.DoesNotExist: + raise PermissionDenied + return super(PosterEditBaseView, self).dispatch(request, *args, **kwargs) + + def get_queryset(self): + return Poster.objects.all() + + def get_form_kwargs(self): + kwargs = super(PosterEditBaseView, self).get_form_kwargs() + kwargs.update({'user': self.request.user}) + return kwargs + + def get_context_data(self, **kwargs): + kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs) + if not self.request.user.is_com_admin: + kwargs['club'] = self.club + return kwargs + + def form_valid(self, form): + if self.request.user.is_com_admin: + form.instance.is_moderated = False + return super(PosterEditBaseView, self).form_valid(form) + + +class PosterDeleteBaseView(DeleteView): + """Edit communication poster""" + pk_url_kwarg = "poster_id" + current_tab = "posters" + model = Poster + template_name = 'core/delete_confirm.jinja' + + def dispatch(self, request, *args, **kwargs): + if 'club_id' in kwargs and kwargs['club_id']: + try: + self.club = Club.objects.get(pk=kwargs['club_id']) + except Club.DoesNotExist: + raise PermissionDenied + return super(PosterDeleteBaseView, self).dispatch(request, *args, **kwargs) + + +class PosterListView(IsComAdminMixin, ComTabsMixin, PosterListBaseView): + """List communication posters""" + + def get_context_data(self, **kwargs): + kwargs = super(PosterListView, self).get_context_data(**kwargs) + kwargs['app'] = "com" + return kwargs + + +class PosterCreateView(IsComAdminMixin, ComTabsMixin, PosterCreateBaseView): + """Create communication poster""" + success_url = reverse_lazy('com:poster_list') + + def get_context_data(self, **kwargs): + kwargs = super(PosterCreateView, self).get_context_data(**kwargs) + kwargs['app'] = "com" + return kwargs + + +class PosterEditView(IsComAdminMixin, ComTabsMixin, PosterEditBaseView): + """Edit communication poster""" + success_url = reverse_lazy('com:poster_list') + + def get_context_data(self, **kwargs): + kwargs = super(PosterEditView, self).get_context_data(**kwargs) + kwargs['app'] = "com" + return kwargs + + +class PosterDeleteView(IsComAdminMixin, ComTabsMixin, PosterDeleteBaseView): + """Delete communication poster""" + success_url = reverse_lazy('com:poster_list') + + +class PosterModerateListView(IsComAdminMixin, ComTabsMixin, ListView): + """Moderate list communication poster""" + current_tab = "posters" + model = Poster + template_name = 'com/poster_moderate.jinja' + queryset = Poster.objects.filter(is_moderated=False).all() + + def get_context_data(self, **kwargs): + kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) + kwargs['app'] = "com" + return kwargs + + +class PosterModerateView(IsComAdminMixin, ComTabsMixin, View): + """Moderate communication poster""" + def get(self, request, *args, **kwargs): + obj = get_object_or_404(Poster, pk=kwargs['object_id']) + if obj.can_be_moderated_by(request.user): + obj.is_moderated = True + obj.moderator = request.user + obj.save() + return redirect('com:poster_moderate_list') + raise PermissionDenied + + def get_context_data(self, **kwargs): + kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) + kwargs['app'] = "com" + return kwargs + + +class ScreenListView(IsComAdminMixin, ComTabsMixin, ListView): + """List communication screens""" + current_tab = "screens" + model = Screen + template_name = 'com/screen_list.jinja' + + +class ScreenSlideshowView(DetailView): + """Slideshow of actives posters""" + pk_url_kwarg = "screen_id" + model = Screen + template_name = 'com/screen_slideshow.jinja' + + def get_context_data(self, **kwargs): + kwargs = super(ScreenSlideshowView, self).get_context_data(**kwargs) + kwargs['posters'] = self.object.active_posters() + return kwargs + + +class ScreenCreateView(IsComAdminMixin, ComTabsMixin, CreateView): + """Create communication screen""" + current_tab = "screens" + model = Screen + fields = ['name', ] + template_name = 'core/create.jinja' + success_url = reverse_lazy('com:screen_list') + + +class ScreenEditView(IsComAdminMixin, ComTabsMixin, UpdateView): + """Edit communication screen""" + pk_url_kwarg = "screen_id" + current_tab = "screens" + model = Screen + fields = ['name', ] + template_name = 'com/screen_edit.jinja' + success_url = reverse_lazy('com:screen_list') + + +class ScreenDeleteView(IsComAdminMixin, ComTabsMixin, DeleteView): + """Delete communication screen""" + pk_url_kwarg = "screen_id" + current_tab = "screens" + model = Screen + template_name = 'core/delete_confirm.jinja' + success_url = reverse_lazy('com:screen_list') diff --git a/core/migrations/0028_auto_20171216_2044.py b/core/migrations/0028_auto_20171216_2044.py new file mode 100644 index 00000000..28403d6b --- /dev/null +++ b/core/migrations/0028_auto_20171216_2044.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0027_gift'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='type', + field=models.CharField(default='GENERIC', verbose_name='type', max_length=32, choices=[('POSTER_MODERATION', 'A new poster needs to be moderated'), ('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'There are %s pictures to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), + ), + ] diff --git a/core/models.py b/core/models.py index af5ae973..4ba7016a 100644 --- a/core/models.py +++ b/core/models.py @@ -518,6 +518,14 @@ class User(AbstractBaseUser): infos.save() return infos + @cached_property + def clubs_with_rights(self): + return [m.club.id for m in self.memberships.filter(models.Q(end_date__isnull=True) | models.Q(end_date__gte=timezone.now())).all() if m.club.has_rights_in_club(self)] + + @cached_property + def is_com_admin(self): + return self.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + class AnonymousUser(AuthAnonymousUser): def __init__(self, request): diff --git a/core/static/com/js/poster_list.js b/core/static/com/js/poster_list.js new file mode 100644 index 00000000..9d4090e9 --- /dev/null +++ b/core/static/com/js/poster_list.js @@ -0,0 +1,24 @@ +$(document).ready(function(){ + + $("#poster_list #view").click(function(e){ + $("#view").removeClass("active"); + }); + + $("#poster_list .poster .image").click(function(e){ + + el = $(e.target); + if(el.hasClass("image")) + el = el.find("img") + $("#poster_list #view #placeholder").html(el.clone()); + + $("#view").addClass("active"); + }); + + $(document).keyup(function(e) { + if (e.keyCode == 27) { // escape key maps to keycode `27` + e.preventDefault(); + $("#view").removeClass("active"); + } + }); + +}); diff --git a/core/static/com/js/slideshow.js b/core/static/com/js/slideshow.js new file mode 100644 index 00000000..74ac9765 --- /dev/null +++ b/core/static/com/js/slideshow.js @@ -0,0 +1,118 @@ +$(document).ready(function(){ + + transition_time = 1000; + + i = 0; + max = $("#slideshow .slide").length; + + next_trigger = 0 + + function enterFullscreen() { + element = document.getElementById("slideshow"); + $(element).addClass("fullscreen"); + if(element.requestFullscreen) { + element.requestFullscreen(); + } else if(element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if(element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } else if(element.msRequestFullscreen) { + element.msRequestFullscreen(); + } + } + + function exitFullscreen() { + element = document.getElementById("slideshow"); + $(element).removeClass("fullscreen"); + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + } + + function init_progress_bar() + { + + $("#slideshow #progress_bar").css("transition", "none"); + $("#slideshow #progress_bar").removeClass("progress"); + $("#slideshow #progress_bar").addClass("init"); + + } + + function start_progress_bar(display_time) + { + + $("#slideshow #progress_bar").removeClass("init"); + $("#slideshow #progress_bar").addClass("progress"); + $("#slideshow #progress_bar").css("transition", "width " + display_time + "s linear") + + } + + function next() + { + + init_progress_bar(); + slide = $($("#slideshow .slide").get(i % max)); + slide.removeClass("center"); + slide.addClass("left"); + + next_slide = $($("#slideshow .slide").get((i + 1) % max)); + next_slide.removeClass("right"); + next_slide.addClass("center"); + display_time = next_slide.attr("display_time") || 2; + + $("#slideshow .bullet").removeClass("active"); + bullet = $("#slideshow .bullet")[(i + 1) % max]; + $(bullet).addClass("active"); + + i = (i + 1) % max; + + setTimeout(function(){ + + others_left = $("#slideshow .slide.left"); + others_left.removeClass("left"); + others_left.addClass("right"); + + start_progress_bar(display_time); + next_trigger = setTimeout(next, display_time * 1000); + + }, transition_time); + + } + + + display_time = $("#slideshow .center").attr("display_time"); + init_progress_bar(); + setTimeout(function(){ + if(max > 1){ + start_progress_bar(display_time); + setTimeout(next, display_time * 1000); + } + }, 10); + + + $("#slideshow").click(function(e){ + if(!$("#slideshow").hasClass("fullscreen")) + { + console.log("Entering fullscreen ..."); + enterFullscreen(); + }else{ + console.log("Exiting fullscreen ..."); + exitFullscreen(); + } + }); + + $(document).keyup(function(e) { + if (e.keyCode == 27) { // escape key maps to keycode `27` + e.preventDefault(); + console.log("Exiting fullscreen ..."); + exitFullscreen(); + } + }); + +}); diff --git a/core/static/com/slideshow.scss b/core/static/com/slideshow.scss new file mode 100644 index 00000000..895fc9e2 --- /dev/null +++ b/core/static/com/slideshow.scss @@ -0,0 +1,148 @@ +body{ + position: absolute; + width: 100vw; + height: 100vh; + + padding: 0; + margin: 0; +} + +#slideshow{ + position: relative; + background-color: lightgrey; + + height: 100%; + + *{ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + &:hover{ + + &::before{ + + position: absolute; + width: 100%; + height: 100%; + + display: flex; + justify-content: center; + align-items: center; + + z-index: 10; + + content: "Click to expand"; + + color: white; + background-color: rgba(black, 0.5); + + } + + } + + &.fullscreen{ + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: none; + + &:before{ + display:none; + } + + #slides{ + height: 100vh; + } + + } + + #slides{ + position: relative; + height: 100%; + overflow: hidden; + + .slide{ + position: absolute; + width: 100%; + height: 100%; + + display: inline-flex; + justify-content: center; + + top: 0px; + + background-color: grey; + transition: left 1s ease-out; + + img{ + max-width: 100%; + max-height: 100%; + } + } + + .slide.left{ + left: -100%; + } + + .slide.center{ + left: 0px; + } + + .slide.right{ + left: 100%; + transition: none; + } + } + + + #progress_bullets{ + position: absolute; + bottom: 10px; + width: 100%; + height: 10px; + + display: flex; + justify-content: center; + + margin-bottom: 10px; + + .bullet{ + height: 10px; + width: 10px; + + margin-left: 5px; + margin-right: 5px; + + border-radius: 50%; + + background-color: grey; + + &.active{ + background-color: #c99836; + } + } + } + + #progress_bar{ + position: absolute; + bottom: 0px; + height: 10px; + background-color: #304c83; + + &.init{ + width: 0px; + transition: none; + } + + &.progress{ + width: 100%; + transition: width 10s linear; + } + } +} + diff --git a/core/static/core/style.scss b/core/static/core/style.scss index c6964963..183fea3a 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -642,6 +642,200 @@ header { display: block; } +/*---------------------------POSTERS----------------------------*/ + +#poster_list, #screen_list, #poster_edit, #screen_edit{ + position: relative; + #title{ + position: relative; + padding: 10px; + margin: 10px; + border-bottom: 2px solid black; + h3{ + display: flex; + justify-content: center; + align-items: center; + } + #links{ + position: absolute; + display: flex; + bottom: 5px; + &.left{ + left: 0; + } + &.right{ + right: 0; + } + .link{ + padding: 5px; + padding-left: 20px; + padding-right: 20px; + margin-left: 5px; + border-radius: 20px; + background-color: #ffaa00; + color: black; + &:hover{ + color: black; + background-color: #c99836; + } + &.delete{ + background-color: #cb0000; + } + } + } + } + #posters, #screens{ + position: relative; + display: flex; + flex-wrap: wrap; + #no-posters, #no-screens{ + display: flex; + justify-content: center; + align-items: center; + } + .poster, .screen{ + min-width: 10%; + max-width: 20%; + display: flex; + flex-direction: column; + margin: 10px; + border: 2px solid darkgrey; + border-radius: 4px; + padding: 10px; + background-color: lightgrey; + *{ + display: flex; + justify-content: center; + align-items: center; + } + .name{ + padding-bottom: 5px; + margin-bottom: 5px; + border-bottom: 1px solid whitesmoke; + } + .image{ + flex-grow: 1; + position: relative; + padding-bottom: 5px; + margin-bottom: 5px; + border-bottom: 1px solid whitesmoke; + img{ + max-height: 20vw; + max-width: 100%; + } + &:hover{ + &::before{ + position: absolute; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + top: 0; + left: 0; + z-index: 10; + content: "Click to expand"; + color: white; + background-color: rgba(black, 0.5); + } + } + } + .dates{ + padding-bottom: 5px; + margin-bottom: 5px; + border-bottom: 1px solid whitesmoke; + *{ + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + margin-left: 5px; + margin-right: 5px; + } + .begin, .end{ + width: 48%; + } + .begin{ + border-right: 1px solid whitesmoke; + padding-right: 2%; + } + } + .edit, .moderate, .slideshow{ + padding: 5px; + border-radius: 20px; + background-color: #ffaa00; + color: black; + &:hover{ + color: black; + background-color: #c99836; + } + &:nth-child(2n){ + margin-top: 5px; + margin-bottom: 5px; + } + } + .tooltip{ + visibility: hidden; + width: 120px; + background-color: #f9fafb; + color: #000; + text-align: center; + padding: 5px 0; + border-radius: 6px; + position: absolute; + z-index: 10; + ul{ + margin-left: 0; + display: inline-block; + li{ + display: list-item; + list-style-type: none; + } + } + } + &.not_moderated + { + border: 1px solid red; + } + &:hover .tooltip{ + visibility: visible; + } + } + } + #view{ + position: fixed; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + z-index: 10; + visibility: hidden; + background-color: rgba(10, 10, 10, 0.9); + overflow: hidden; + &.active{ + visibility: visible; + } + #placeholder{ + width: 80vw; + height: 80vh; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + img{ + max-width: 100%; + max-height: 100%; + } + } + } +} + + /*---------------------------ACCOUNTING----------------------------*/ #accounting { .journal-table { diff --git a/core/templates/core/poster_list.jinja b/core/templates/core/poster_list.jinja new file mode 100644 index 00000000..816c8332 --- /dev/null +++ b/core/templates/core/poster_list.jinja @@ -0,0 +1,54 @@ +{% extends "core/base.jinja" %} + +{% block script %} +{{ super() }} + +{% endblock %} + + +{% block title %} +{% trans %}Poster{% endtrans %} +{% endblock %} + +{% block content %} +
    + +
    +

    {% trans %}Posters{% endtrans %}

    + +
    + +
    + + {% if poster_list.count() == 0 %} +
    {% trans %}No posters{% endtrans %}
    + {% else %} + + {% for poster in poster_list %} +
    +
    {{ poster.name }}
    +
    +
    +
    {{ poster.date_begin | date("d/M/Y H:m") }}
    +
    {{ poster.date_end | date("d/M/Y H:m") }}
    +
    + {% trans %}Edit{% endtrans %} +
    + {% endfor %} + + {% endif %} + +
    + +
    + +
    +{% endblock %} + + + diff --git a/core/templates/core/screen_slideshow.jinja b/core/templates/core/screen_slideshow.jinja new file mode 100644 index 00000000..2ffa3fbf --- /dev/null +++ b/core/templates/core/screen_slideshow.jinja @@ -0,0 +1,30 @@ + + + + {% trans %}Slideshow{% endtrans %} + + + +
    + +
    + {% for poster in posters %} +
    + +
    + {% endfor %} +
    + +
    + {% for poster in posters %} +
    + {% endfor %} +
    + +
    + +
    + + + + diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja index 78b9ffc9..e887f356 100644 --- a/core/templates/core/user_tools.jinja +++ b/core/templates/core/user_tools.jinja @@ -87,6 +87,8 @@
  • {% trans %}Edit information message{% endtrans %}
  • {% trans %}Moderate files{% endtrans %}
  • {% trans %}Mailing lists administration{% endtrans %}
  • +
  • {% trans %}Posters{% endtrans %}
  • +
  • {% trans %}Screens{% endtrans %}
  • {% endif %} {% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %}
  • {% trans %}Moderate pictures{% endtrans %}
  • diff --git a/sith/settings.py b/sith/settings.py index da718838..a70fcf7c 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -562,6 +562,7 @@ SITH_LAUNDERETTE_PRICES = { } SITH_NOTIFICATIONS = [ + ('POSTER_MODERATION', _("A new poster needs to be moderated")), ('MAILING_MODERATION', _("A new mailing list needs to be moderated")), ('NEWS_MODERATION', _("There are %s fresh news to be moderated")), ('FILE_MODERATION', _("New files to be moderated")),