Merge branch 'nabos' into 'master'

Communication screens

See merge request ae/Sith!116
This commit is contained in:
Skia 2017-12-21 18:25:56 +01:00
commit 10dfb2c122
24 changed files with 1284 additions and 5 deletions

View File

@ -11,6 +11,7 @@
<li> <a href="{{ url('trombi:detail', trombi_id=object.trombi.id) }}">{% trans %}Edit Trombi{% endtrans %}</a></li> <li> <a href="{{ url('trombi:detail', trombi_id=object.trombi.id) }}">{% trans %}Edit Trombi{% endtrans %}</a></li>
{% else %} {% else %}
<li> <a href="{{ url('trombi:create', club_id=object.id) }}">{% trans %}New Trombi{% endtrans %}</a></li> <li> <a href="{{ url('trombi:create', club_id=object.id) }}">{% trans %}New Trombi{% endtrans %}</a></li>
<li> <a href="{{ url('club:poster_list', club_id=object.id) }}">{% trans %}Posters{% endtrans %}</a></li>
{% endif %} {% endif %}
</ul> </ul>
<h4>{% trans %}Counters:{% endtrans %}</h4> <h4>{% trans %}Counters:{% endtrans %}</h4>

View File

@ -50,4 +50,8 @@ urlpatterns = [
url(r'^(?P<mailing_id>[0-9]+)/mailing/delete$', MailingDeleteView.as_view(), name='mailing_delete'), url(r'^(?P<mailing_id>[0-9]+)/mailing/delete$', MailingDeleteView.as_view(), name='mailing_delete'),
url(r'^(?P<mailing_subscription_id>[0-9]+)/mailing/delete/subscription$', MailingSubscriptionDeleteView.as_view(), name='mailing_subscription_delete'), url(r'^(?P<mailing_subscription_id>[0-9]+)/mailing/delete/subscription$', MailingSubscriptionDeleteView.as_view(), name='mailing_subscription_delete'),
url(r'^membership/(?P<membership_id>[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'), url(r'^membership/(?P<membership_id>[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'),
url(r'^(?P<club_id>[0-9]+)/poster$', PosterListView.as_view(), name='poster_list'),
url(r'^(?P<club_id>[0-9]+)/poster/create$', PosterCreateView.as_view(), name='poster_create'),
url(r'^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'),
url(r'^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'),
] ]

View File

@ -37,12 +37,14 @@ from ajax_select.fields import AutoCompleteSelectField
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect 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 core.views.forms import SelectDate, SelectDateTime
from club.models import Club, Membership, Mailing, MailingSubscription from club.models import Club, Membership, Mailing, MailingSubscription
from sith.settings import SITH_MAXIMUM_FREE_ROLE from sith.settings import SITH_MAXIMUM_FREE_ROLE
from counter.models import Selling, Counter from counter.models import Selling, Counter
from core.models import User, PageRev from core.models import User, PageRev
from com.views import PosterListBaseView, PosterCreateBaseView, PosterEditBaseView, PosterDeleteBaseView
from com.models import Poster
from django.conf import settings from django.conf import settings
@ -86,8 +88,9 @@ class MailingSubscriptionForm(forms.ModelForm):
class ClubTabsMixin(TabedViewMixin): class ClubTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
if isinstance(self.object, PageRev): obj = self.get_object()
self.object = self.object.page.club if isinstance(obj, PageRev):
self.object = obj.page.club
return self.object.get_display_name() return self.object.get_display_name()
def get_list_of_tabs(self): def get_list_of_tabs(self):
@ -141,6 +144,11 @@ class ClubTabsMixin(TabedViewMixin):
'slug': 'mailing', 'slug': 'mailing',
'name': _("Mailing list"), '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): if self.request.user.is_owner(self.object):
tab_list.append({ tab_list.append({
'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}), 'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}),
@ -592,3 +600,51 @@ class MailingAutoCleanView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.mailing.subscriptions.all().delete() self.mailing.subscriptions.all().delete()
return redirect('club:mailing', club_id=self.mailing.club.id) 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})

View File

@ -39,4 +39,6 @@ class WeekmailAdmin(SearchModelAdmin):
admin.site.register(Sith) admin.site.register(Sith)
admin.site.register(News, NewsAdmin) admin.site.register(News, NewsAdmin)
admin.site.register(Weekmail, WeekmailAdmin) admin.site.register(Weekmail, WeekmailAdmin)
admin.site.register(Screen)
admin.site.register(Poster)

View File

@ -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'),
),
]

View File

@ -2,6 +2,7 @@
# #
# Copyright 2016,2017 # Copyright 2016,2017
# - Skia <skia@libskia.so> # - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr>
# #
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr. # http://ae.utbm.fr.
@ -31,11 +32,16 @@ from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles.templatetags.staticfiles import static from django.contrib.staticfiles.templatetags.staticfiles import static
from django.core.mail import EmailMultiAlternatives 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 from club.models import Club
class Sith(models.Model): class Sith(models.Model):
"""A one instance class storing all the modifiable infos""" """A one instance class storing all the modifiable infos"""
alert_msg = models.TextField(_("alert message"), default="", blank=True) alert_msg = models.TextField(_("alert message"), default="", blank=True)
@ -183,3 +189,54 @@ class WeekmailArticle(models.Model):
def __str__(self): def __str__(self):
return "%s - %s (%s)" % (self.title, self.author, self.club) 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

View File

@ -0,0 +1,43 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Poster{% endtrans %}
{% endblock %}
{% block content %}
<div id="poster_edit">
<div id="title">
<div id="links" class="left">
{% if app == "com" %}
<a id="list" class="link" href="{{ url(app + ":poster_list") }}">{% trans %}List{% endtrans %}</a>
{% elif app == "club" %}
<a id="list" class="link" href="{{ url(app + ":poster_list", club.id) }}">{% trans %}List{% endtrans %}</a>
{% endif %}
</div>
<h3>{% trans %}Posters - edit{% endtrans %}</h3>
<div id="links" class="right">
{% if app == "com" %}
<a class="link delete" href="{{ url(app + ":poster_delete", poster.id) }}">{% trans %}Delete{% endtrans %}</a>
{% elif app == "club" %}
<a class="link delete" href="{{ url(app + ":poster_delete", club.id, poster.id) }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
</div>
</div>
<div id="poster">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,67 @@
{% extends "core/base.jinja" %}
{% block script %}
{{ super() }}
<script src="{{ static('com/js/poster_list.js') }}"></script>
{% endblock %}
{% block title %}
{% trans %}Poster{% endtrans %}
{% endblock %}
{% block content %}
<div id="poster_list">
<div id="title">
<h3>{% trans %}Posters{% endtrans %}</h3>
<div id="links" class="right">
{% if app == "com" %}
<a id="create" class="link" href="{{ url(app + ":poster_create") }}">{% trans %}Create{% endtrans %}</a>
<a id="moderation" class="link" href="{{ url("com:poster_moderate_list") }}">{% trans %}Moderation{% endtrans %}</a>
{% elif app == "club" %}
<a id="create" class="link" href="{{ url(app + ":poster_create", club.id) }}">{% trans %}Create{% endtrans %}</a>
{% endif %}
</div>
</div>
<div id="posters">
{% if poster_list.count() == 0 %}
<div id="no-posters">{% trans %}No posters{% endtrans %}</div>
{% else %}
{% for poster in poster_list %}
<div class="poster{% if not poster.is_moderated %} not_moderated{% endif %}">
<div class="name">{{ poster.name }}</div>
<div class="image"><img src="{{ poster.file.url }}"></img></div>
<div class="dates">
<div class="begin">{{ poster.date_begin | date("d/M/Y H:m") }}</div>
<div class="end">{{ poster.date_end | date("d/M/Y H:m") }}</div>
</div>
{% if app == "com" %}
<a class="edit" href="{{ url(app + ":poster_edit", poster.id) }}">{% trans %}Edit{% endtrans %}</a>
{% elif app == "club" %}
<a class="edit" href="{{ url(app + ":poster_edit", club.id, poster.id) }}">{% trans %}Edit{% endtrans %}</a>
{% endif %}
<div class="tooltip">
<ul>
{% for screen in poster.screens.all() %}
<li>{{ screen }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
{% endif %}
</div>
<div id="view"><div id="placeholder"></div></div>
</div>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "core/base.jinja" %}
{% block script %}
{{ super() }}
<script src="{{ static('com/js/poster_list.js') }}"></script>
{% endblock %}
{% block content %}
<div id="poster_list">
<div id="title">
<div id="links" class="left">
<a id="list" class="link" href="{{ url("com:poster_list") }}">{% trans %}List{% endtrans %}</a>
</div>
<h3>{% trans %}Posters - moderation{% endtrans %}</h3>
</div>
<div id="posters">
{% if object_list.count == 0 %}
<div id="no-posters">{% trans %}No objects{% endtrans %}</div>
{% else %}
{% for poster in object_list %}
<div class="poster{% if not poster.is_moderated %} not_moderated{% endif %}">
<div class="name"> {{ poster.name }} </div>
<div class="image"> <img src="{{ poster.file.url }}"></img> </div>
<a class="moderate" href="{{ url("com:poster_moderate", object_id=poster.id) }}">Moderate</a>
</div>
{% endfor %}
{% endif %}
</div>
<div id="view"><div id="placeholder"></div></div>
</div>
{% endblock %}

View File

@ -0,0 +1,34 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Screen{% endtrans %}
{% endblock %}
{% block content %}
<div id="screen_edit">
<div id="title">
<div id="links" class="left">
<a id="list" class="link" href="{{ url("com:screen_list") }}">{% trans %}List{% endtrans %}</a>
</div>
<h3>{% trans %}Screen - edit{% endtrans %}</h3>
<div id="links" class="right">
<a class="link delete" href="{{ url("com:screen_delete", screen.id) }}">{% trans %}Delete{% endtrans %}</a>
</div>
</div>
<div id="screen">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Screens{% endtrans %}
{% endblock %}
{% block content %}
<div id="screen_list">
<div id="title">
<h3>{% trans %}Screens{% endtrans %}</h3>
<div id="links" class="right">
<a id="create" class="link" href="{{ url("com:screen_create") }}">{% trans %}Create{% endtrans %}</a>
</div>
</div>
<div id="screens">
{% if screen_list.count() == 0 %}
<div id="no-screens">{% trans %}No screens{% endtrans %}</div>
{% else %}
{% for screen in screen_list %}
<div class="screen">
<div class="name">{{ screen.name }}</div>
<a class="edit" href="{{ url("com:screen_edit", screen.id) }}">{% trans %}Edit{% endtrans %}</a>
<a class="slideshow" href="{{ url("com:screen_slideshow", screen.id) }}" target="_blank">{% trans %}Slideshow{% endtrans %}</a>
</div>
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<title>{% trans %}Slideshow{% endtrans %}</title>
<link href="{{ scss('com/slideshow.scss') }}" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="slideshow">
<div id="slides">
{% for poster in posters %}
<div class="slide {% if loop.first %}center{% else %}right{% endif %}" display_time="{{ poster.display_time }}">
<img src="{{ poster.file.url }}"></img>
</div>
{% endfor %}
</div>
<div id="progress_bullets">
{% for poster in posters %}
<div class="bullet {% if loop.first %}active{% endif %}"></div>
{% endfor %}
</div>
<div id="progress_bar"></div>
</div>
<script src="{{ static('core/js/jquery-3.1.0.min.js') }}"></script>
<script src="{{ static('com/js/slideshow.js') }}"></script>
</body>
</html>

View File

@ -47,5 +47,16 @@ urlpatterns = [
url(r'^mailings$', MailingListAdminView.as_view(), name='mailing_admin'), url(r'^mailings$', MailingListAdminView.as_view(), name='mailing_admin'),
url(r'^mailings/(?P<mailing_id>[0-9]+)/moderate$', MailingModerateView.as_view(), name='mailing_moderate'), url(r'^mailings/(?P<mailing_id>[0-9]+)/moderate$', MailingModerateView.as_view(), name='mailing_moderate'),
url(r'^mailings/(?P<mailing_id>[0-9]+)/delete$', MailingDeleteView.as_view(redirect_page='com:mailing_admin'), name='mailing_delete'), url(r'^mailings/(?P<mailing_id>[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<poster_id>[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'),
url(r'^poster/(?P<poster_id>[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'),
url(r'^poster/moderate$', PosterModerateListView.as_view(), name='poster_moderate_list'),
url(r'^poster/(?P<object_id>[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<screen_id>[0-9]+)/slideshow$', ScreenSlideshowView.as_view(), name='screen_slideshow'),
url(r'^screen/(?P<screen_id>[0-9]+)/edit$', ScreenEditView.as_view(), name='screen_edit'),
url(r'^screen/(?P<screen_id>[0-9]+)/delete$', ScreenDeleteView.as_view(), name='screen_delete'),
] ]

View File

@ -40,7 +40,7 @@ from django import forms
from datetime import timedelta 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 import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin
from core.views.forms import SelectDateTime from core.views.forms import SelectDateTime
from core.models import Notification, RealGroup, User from core.models import Notification, RealGroup, User
@ -52,6 +52,23 @@ from club.models import Club, Mailing
sith = Sith.objects.first 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): class ComTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
return _("Communication administration") return _("Communication administration")
@ -88,9 +105,27 @@ class ComTabsMixin(TabedViewMixin):
'slug': 'mailings', 'slug': 'mailings',
'name': _("Mailing lists administration"), '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 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): class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView):
model = Sith model = Sith
template_name = 'core/edit.jinja' template_name = 'core/edit.jinja'
@ -465,3 +500,222 @@ class MailingModerateView(View):
return redirect('com:mailing_admin') return redirect('com:mailing_admin')
raise PermissionDenied 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')

View File

@ -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')]),
),
]

View File

@ -518,6 +518,14 @@ class User(AbstractBaseUser):
infos.save() infos.save()
return infos 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): class AnonymousUser(AuthAnonymousUser):
def __init__(self, request): def __init__(self, request):

View File

@ -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");
}
});
});

View File

@ -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();
}
});
});

View File

@ -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;
}
}
}

View File

@ -642,6 +642,200 @@ header {
display: block; 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----------------------------*/
#accounting { #accounting {
.journal-table { .journal-table {

View File

@ -0,0 +1,54 @@
{% extends "core/base.jinja" %}
{% block script %}
{{ super() }}
<script src="{{ static('com/js/poster_list.js') }}"></script>
{% endblock %}
{% block title %}
{% trans %}Poster{% endtrans %}
{% endblock %}
{% block content %}
<div id="poster_list">
<div id="title">
<h3>{% trans %}Posters{% endtrans %}</h3>
<div id="links" class="right">
<a id="create" class="link" href="{{ url(app + ":poster_list") }}">{% trans %}Create{% endtrans %}</a>
{% if app == "com" %}
<a id="moderation" class="link" href="{{ url("com:poster_moderate_list") }}">{% trans %}Moderation{% endtrans %}</a>
{% endif %}
</div>
</div>
<div id="posters">
{% if poster_list.count() == 0 %}
<div id="no-posters">{% trans %}No posters{% endtrans %}</div>
{% else %}
{% for poster in poster_list %}
<div class="poster">
<div class="name">{{ poster.name }}</div>
<div class="image"><img src="{{ poster.file.url }}"></img></div>
<div class="dates">
<div class="begin">{{ poster.date_begin | date("d/M/Y H:m") }}</div>
<div class="end">{{ poster.date_end | date("d/M/Y H:m") }}</div>
</div>
<a class="edit" href="{{ url(poster_edit_url_name, poster.id) }}">{% trans %}Edit{% endtrans %}</a>
</div>
{% endfor %}
{% endif %}
</div>
<div id="view"><div id="placeholder"></div></div>
</div>
{% endblock %}

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<title>{% trans %}Slideshow{% endtrans %}</title>
<link href="{{ scss('com/slideshow.scss') }}" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="slideshow">
<div id="slides">
{% for poster in posters %}
<div class="slide {% if loop.first %}center{% else %}right{% endif %}" display_time="{{ poster.display_time }}">
<img src="{{ poster.file.url }}"></img>
</div>
{% endfor %}
</div>
<div id="progress_bullets">
{% for poster in posters %}
<div class="bullet {% if loop.first %}active{% endif %}"></div>
{% endfor %}
</div>
<div id="progress_bar"></div>
</div>
<script src="{{ static('core/js/jquery-3.1.0.min.js') }}"></script>
<script src="{{ static('com/js/slideshow.js') }}"></script>
</body>
</html>

View File

@ -87,6 +87,8 @@
<li><a href="{{ url('com:info_edit') }}">{% trans %}Edit information message{% endtrans %}</a></li> <li><a href="{{ url('com:info_edit') }}">{% trans %}Edit information message{% endtrans %}</a></li>
<li><a href="{{ url('core:file_moderation') }}">{% trans %}Moderate files{% endtrans %}</a></li> <li><a href="{{ url('core:file_moderation') }}">{% trans %}Moderate files{% endtrans %}</a></li>
<li><a href="{{ url('com:mailing_admin') }}">{% trans %}Mailing lists administration{% endtrans %}</a></li> <li><a href="{{ url('com:mailing_admin') }}">{% trans %}Mailing lists administration{% endtrans %}</a></li>
<li><a href="{{ url('com:poster_list') }}">{% trans %}Posters{% endtrans %}</a></li>
<li><a href="{{ url('com:screen_list') }}">{% trans %}Screens{% endtrans %}</a></li>
{% endif %} {% endif %}
{% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %} {% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %}
<li><a href="{{ url('sas:moderation') }}">{% trans %}Moderate pictures{% endtrans %}</a></li> <li><a href="{{ url('sas:moderation') }}">{% trans %}Moderate pictures{% endtrans %}</a></li>

View File

@ -562,6 +562,7 @@ SITH_LAUNDERETTE_PRICES = {
} }
SITH_NOTIFICATIONS = [ SITH_NOTIFICATIONS = [
('POSTER_MODERATION', _("A new poster needs to be moderated")),
('MAILING_MODERATION', _("A new mailing list needs to be moderated")), ('MAILING_MODERATION', _("A new mailing list needs to be moderated")),
('NEWS_MODERATION', _("There are %s fresh news to be moderated")), ('NEWS_MODERATION', _("There are %s fresh news to be moderated")),
('FILE_MODERATION', _("New files to be moderated")), ('FILE_MODERATION', _("New files to be moderated")),