Ajout d'un calendrier dynamique

Ce patch propose aux adhérents du COF de télécharger un calendrier
dynamique (`.ics`).

Il est configurable :
    - On peut s'abonner ou non aux événements du COF.
    - On peut choisir les spectacles auxquels on veut s'abonner.
This commit is contained in:
Martin Pépin 2016-07-15 01:06:33 +02:00
parent a24ca5a19b
commit 8af49a1020
9 changed files with 158 additions and 12 deletions

View file

@ -11,7 +11,7 @@ from django.contrib import admin
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from gestioncof.urls import export_patterns, petitcours_patterns, \ from gestioncof.urls import export_patterns, petitcours_patterns, \
surveys_patterns, events_patterns surveys_patterns, events_patterns, calendar_patterns
autocomplete_light.autodiscover() autocomplete_light.autodiscover()
admin.autodiscover() admin.autodiscover()
@ -30,6 +30,8 @@ urlpatterns = patterns(
url(r'^survey/', include(surveys_patterns)), url(r'^survey/', include(surveys_patterns)),
# Evenements # Evenements
url(r'^event/', include(events_patterns)), url(r'^event/', include(events_patterns)),
# Calendrier
url(r'^calendar/', include(calendar_patterns)),
# Authentification # Authentification
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'), url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
name="cof-denied"), name="cof-denied"),

View file

@ -6,10 +6,13 @@ from django.contrib.auth.models import User
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
from django.db.models import Max from django.db.models import Max
from gestioncof.models import CofProfile, EventCommentValue from gestioncof.models import CofProfile, EventCommentValue, \
CalendarSubscription
from gestioncof.widgets import TriStateCheckbox from gestioncof.widgets import TriStateCheckbox
from gestioncof.shared import lock_table, unlock_table from gestioncof.shared import lock_table, unlock_table
from bda.models import Spectacle
class EventForm(forms.Form): class EventForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -330,3 +333,16 @@ class AdminEventForm(forms.Form):
for name, value in self.cleaned_data.items(): for name, value in self.cleaned_data.items():
if name.startswith('comment_'): if name.startswith('comment_'):
yield (self.fields[name].comment_id, value) yield (self.fields[name].comment_id, value)
class CalendarForm(forms.ModelForm):
events = forms.BooleanField(initial=True, label="S'abonner aux événements")
shows = forms.ModelMultipleChoiceField(
label="S'abonner aux spectacles suivants",
queryset=Spectacle.objects.filter(tirage__active=True).all(),
widget=forms.CheckboxSelectMultiple,
initial=Spectacle.objects.filter(tirage__active=True).all())
class Meta:
model = CalendarSubscription
exclude = ['user']

View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('bda', '0004_mails-rappel'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('gestioncof', '0004_registration_mail'),
]
operations = [
migrations.CreateModel(
name='CalendarSubscription',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('events', models.BooleanField(default=True)),
('shows', models.ManyToManyField(to='bda.Spectacle')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterField(
model_name='event',
name='end_date',
field=models.DateTimeField(null=True, verbose_name=b'Date de fin',
blank=True),
),
migrations.AlterField(
model_name='event',
name='start_date',
field=models.DateTimeField(null=True,
verbose_name=b'Date de d\xc3\xa9but',
blank=True),
),
]

View file

@ -7,6 +7,8 @@ from django.db.models.signals import post_save
from petits_cours_models import * from petits_cours_models import *
from bda.models import Spectacle
OCCUPATION_CHOICES = ( OCCUPATION_CHOICES = (
('exterieur', _(u"Extérieur")), ('exterieur', _(u"Extérieur")),
('1A', _(u"1A")), ('1A', _(u"1A")),
@ -94,8 +96,7 @@ class CustomMail(models.Model):
blank=True) blank=True)
class Meta: class Meta:
verbose_name = "Mail personnalisable" verbose_name = "Mails personnalisables"
verbose_name_plural = "Mails personnalisables"
def __unicode__(self): def __unicode__(self):
return u"%s: %s" % (self.shortname, self.title) return u"%s: %s" % (self.shortname, self.title)
@ -104,8 +105,8 @@ class CustomMail(models.Model):
class Event(models.Model): class Event(models.Model):
title = models.CharField("Titre", max_length=200) title = models.CharField("Titre", max_length=200)
location = models.CharField("Lieu", max_length=200) location = models.CharField("Lieu", max_length=200)
start_date = models.DateField("Date de début", blank=True, null=True) start_date = models.DateTimeField("Date de début", blank=True, null=True)
end_date = models.DateField("Date de fin", blank=True, null=True) end_date = models.DateTimeField("Date de fin", blank=True, null=True)
description = models.TextField("Description", blank=True) description = models.TextField("Description", blank=True)
image = models.ImageField("Image", blank=True, null=True, image = models.ImageField("Image", blank=True, null=True,
upload_to="imgs/events/") upload_to="imgs/events/")
@ -159,7 +160,6 @@ class EventOptionChoice(models.Model):
class Meta: class Meta:
verbose_name = "Choix" verbose_name = "Choix"
verbose_name_plural = "Choix"
def __unicode__(self): def __unicode__(self):
return unicode(self.value) return unicode(self.value)
@ -232,3 +232,9 @@ class SurveyAnswer(models.Model):
class Clipper(models.Model): class Clipper(models.Model):
username = models.CharField("Identifiant", max_length=20) username = models.CharField("Identifiant", max_length=20)
fullname = models.CharField("Nom complet", max_length=200) fullname = models.CharField("Nom complet", max_length=200)
class CalendarSubscription(models.Model):
user = models.ForeignKey(User)
shows = models.ManyToManyField(Spectacle)
events = models.BooleanField(default=True)

View file

@ -0,0 +1,22 @@
{% extends "base_title.html" %}
{% block realcontent %}
{% if success %}
<p class="success">Calendrier mis à jour avec succès</p>
{% endif %}
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
<p>Calendrier disponible à
<a href="{% url 'gestioncof.views.calendar_ics' %}">cette adresse</a></p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Enregistrer" />
</form>
{% endblock %}

View file

@ -41,7 +41,7 @@
<h3>Divers</h3> <h3>Divers</h3>
<ul> <ul>
<li><a href="{% url "gestioncof.views.calendar" %}">Calendrier dynamique</a></li>
{% if user.profile.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %} {% if user.profile.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %}
<li><a href="{% url "gestioncof.views.profile" %}">Éditer mon profil</a></li> <li><a href="{% url "gestioncof.views.profile" %}">Éditer mon profil</a></li>

View file

@ -39,3 +39,8 @@ events_patterns = [
url(r'^(?P<event_id>\d+)$', 'gestioncof.views.event'), url(r'^(?P<event_id>\d+)$', 'gestioncof.views.event'),
url(r'^(?P<event_id>\d+)/status$', 'gestioncof.views.event_status'), url(r'^(?P<event_id>\d+)/status$', 'gestioncof.views.event_status'),
] ]
calendar_patterns = [
url(r'^subscription$', 'gestioncof.views.calendar'),
url(r'^calendar.ics$', 'gestioncof.views.calendar_ics')
]

View file

@ -1,8 +1,11 @@
# coding: utf-8 # coding: utf-8
import unicodecsv import unicodecsv
from datetime import timedelta
from icalendar import Calendar, Event as Vevent
from django.shortcuts import redirect, get_object_or_404, render from django.shortcuts import redirect, get_object_or_404, render, \
HttpResponse
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import login as django_login_view from django.contrib.auth.views import login as django_login_view
@ -12,13 +15,14 @@ from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
SurveyQuestionAnswer SurveyQuestionAnswer
from gestioncof.models import Event, EventRegistration, EventOption, \ from gestioncof.models import Event, EventRegistration, EventOption, \
EventOptionChoice EventOptionChoice
from gestioncof.models import EventCommentField, EventCommentValue from gestioncof.models import EventCommentField, EventCommentValue, \
CalendarSubscription
from gestioncof.shared import send_custom_mail from gestioncof.shared import send_custom_mail
from gestioncof.models import CofProfile, Clipper from gestioncof.models import CofProfile, Clipper
from gestioncof.decorators import buro_required from gestioncof.decorators import buro_required, cof_required
from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \ from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \
SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \ SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \
RegistrationProfileForm, AdminEventForm, EventForm RegistrationProfileForm, AdminEventForm, EventForm, CalendarForm
from bda.models import Tirage from bda.models import Tirage
@ -618,3 +622,53 @@ def liste_diffcof(request):
personnes = CofProfile.objects.filter(mailing_cof=True, is_cof=True).all() personnes = CofProfile.objects.filter(mailing_cof=True, is_cof=True).all()
return render(request, "liste_mails.html", {"titre": titre, return render(request, "liste_mails.html", {"titre": titre,
"personnes": personnes}) "personnes": personnes})
@cof_required
def calendar(request):
if request.method == 'POST':
form = CalendarForm(request.POST)
if form.is_valid():
CalendarSubscription.objects.filter(user=request.user).delete()
subscription = form.save(commit=False)
subscription.user = request.user
subscription.save()
form.save_m2m()
return render(request, "calendar_subscription.html",
{'form': form, 'success': True})
else:
return render(request, "calendar_subscription.html",
{'form': form, 'error': "Formulaire incorect"})
else:
try:
subscription = CalendarSubscription.objects.get(user=request.user)
form = CalendarForm(instance=subscription)
except:
form = CalendarForm()
return render(request, "calendar_subscription.html", {'form': form})
@cof_required
def calendar_ics(request):
subscription, _ = CalendarSubscription.objects.get_or_create(
user=request.user)
vcal = Calendar()
for show in subscription.shows.all():
vevent = Vevent()
vevent.add('dtstart', show.date)
vevent.add('dtend', show.date + timedelta(seconds=7200))
vevent.add('summary', show.title)
vevent.add('location', show.location.name)
vcal.add_component(vevent)
if subscription.events:
for event in Event.objects.filter(old=False).all():
vevent = Vevent()
vevent.add('dtstart', event.start_date)
vevent.add('dtend', event.end_date)
vevent.add('summary', event.title)
vevent.add('location', event.location)
vevent.add('description', event.description)
vcal.add_component(vevent)
response = HttpResponse(content=vcal.to_ical())
response['Content-Type'] = "text/calendar"
return response

View file

@ -10,3 +10,4 @@ Pillow==2.9.0
simplejson==3.8.2 simplejson==3.8.2
six==1.10.0 six==1.10.0
unicodecsv==0.14.1 unicodecsv==0.14.1
icalendar==3.10