From 8af49a1020bba94f8db617eba7a587e4373dc4f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20P=C3=A9pin?=
Date: Fri, 15 Jul 2016 01:06:33 +0200
Subject: [PATCH 1/9] Ajout d'un calendrier dynamique
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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.
---
cof/urls.py | 4 +-
gestioncof/forms.py | 18 +++++-
gestioncof/migrations/0005_add_calendar.py | 40 ++++++++++++
gestioncof/models.py | 16 +++--
.../templates/calendar_subscription.html | 22 +++++++
gestioncof/templates/home.html | 2 +-
gestioncof/urls.py | 5 ++
gestioncof/views.py | 62 +++++++++++++++++--
requirements.txt | 1 +
9 files changed, 158 insertions(+), 12 deletions(-)
create mode 100644 gestioncof/migrations/0005_add_calendar.py
create mode 100644 gestioncof/templates/calendar_subscription.html
diff --git a/cof/urls.py b/cof/urls.py
index b5d2b65a..1fd7b2c1 100644
--- a/cof/urls.py
+++ b/cof/urls.py
@@ -11,7 +11,7 @@ from django.contrib import admin
from django.views.generic.base import TemplateView
from gestioncof.urls import export_patterns, petitcours_patterns, \
- surveys_patterns, events_patterns
+ surveys_patterns, events_patterns, calendar_patterns
autocomplete_light.autodiscover()
admin.autodiscover()
@@ -30,6 +30,8 @@ urlpatterns = patterns(
url(r'^survey/', include(surveys_patterns)),
# Evenements
url(r'^event/', include(events_patterns)),
+ # Calendrier
+ url(r'^calendar/', include(calendar_patterns)),
# Authentification
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
name="cof-denied"),
diff --git a/gestioncof/forms.py b/gestioncof/forms.py
index bf983df5..500f3a8b 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -6,10 +6,13 @@ from django.contrib.auth.models import User
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
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.shared import lock_table, unlock_table
+from bda.models import Spectacle
+
class EventForm(forms.Form):
def __init__(self, *args, **kwargs):
@@ -330,3 +333,16 @@ class AdminEventForm(forms.Form):
for name, value in self.cleaned_data.items():
if name.startswith('comment_'):
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']
diff --git a/gestioncof/migrations/0005_add_calendar.py b/gestioncof/migrations/0005_add_calendar.py
new file mode 100644
index 00000000..b8686bb5
--- /dev/null
+++ b/gestioncof/migrations/0005_add_calendar.py
@@ -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),
+ ),
+ ]
diff --git a/gestioncof/models.py b/gestioncof/models.py
index 540145d5..b3ef9921 100644
--- a/gestioncof/models.py
+++ b/gestioncof/models.py
@@ -7,6 +7,8 @@ from django.db.models.signals import post_save
from petits_cours_models import *
+from bda.models import Spectacle
+
OCCUPATION_CHOICES = (
('exterieur', _(u"Extérieur")),
('1A', _(u"1A")),
@@ -94,8 +96,7 @@ class CustomMail(models.Model):
blank=True)
class Meta:
- verbose_name = "Mail personnalisable"
- verbose_name_plural = "Mails personnalisables"
+ verbose_name = "Mails personnalisables"
def __unicode__(self):
return u"%s: %s" % (self.shortname, self.title)
@@ -104,8 +105,8 @@ class CustomMail(models.Model):
class Event(models.Model):
title = models.CharField("Titre", max_length=200)
location = models.CharField("Lieu", max_length=200)
- start_date = models.DateField("Date de début", blank=True, null=True)
- end_date = models.DateField("Date de fin", blank=True, null=True)
+ start_date = models.DateTimeField("Date de début", blank=True, null=True)
+ end_date = models.DateTimeField("Date de fin", blank=True, null=True)
description = models.TextField("Description", blank=True)
image = models.ImageField("Image", blank=True, null=True,
upload_to="imgs/events/")
@@ -159,7 +160,6 @@ class EventOptionChoice(models.Model):
class Meta:
verbose_name = "Choix"
- verbose_name_plural = "Choix"
def __unicode__(self):
return unicode(self.value)
@@ -232,3 +232,9 @@ class SurveyAnswer(models.Model):
class Clipper(models.Model):
username = models.CharField("Identifiant", max_length=20)
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)
diff --git a/gestioncof/templates/calendar_subscription.html b/gestioncof/templates/calendar_subscription.html
new file mode 100644
index 00000000..65a8b9e6
--- /dev/null
+++ b/gestioncof/templates/calendar_subscription.html
@@ -0,0 +1,22 @@
+{% extends "base_title.html" %}
+
+{% block realcontent %}
+
+{% if success %}
+Calendrier mis à jour avec succès
+{% endif %}
+
+{% if error %}
+{{ error }}
+{% endif %}
+
+Calendrier disponible à
+cette adresse
+
+
+
+{% endblock %}
diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html
index ed567455..ab21c541 100644
--- a/gestioncof/templates/home.html
+++ b/gestioncof/templates/home.html
@@ -41,7 +41,7 @@
Divers
-
+ - Calendrier dynamique
{% if user.profile.is_cof %}- Inscription pour donner des petits cours
{% endif %}
- Éditer mon profil
diff --git a/gestioncof/urls.py b/gestioncof/urls.py
index 787dd80f..4eaef916 100644
--- a/gestioncof/urls.py
+++ b/gestioncof/urls.py
@@ -39,3 +39,8 @@ events_patterns = [
url(r'^(?P\d+)$', 'gestioncof.views.event'),
url(r'^(?P\d+)/status$', 'gestioncof.views.event_status'),
]
+
+calendar_patterns = [
+ url(r'^subscription$', 'gestioncof.views.calendar'),
+ url(r'^calendar.ics$', 'gestioncof.views.calendar_ics')
+]
diff --git a/gestioncof/views.py b/gestioncof/views.py
index 11d15d53..f906bc42 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -1,8 +1,11 @@
# coding: utf-8
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.contrib.auth.decorators import login_required
from django.contrib.auth.views import login as django_login_view
@@ -12,13 +15,14 @@ from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
SurveyQuestionAnswer
from gestioncof.models import Event, EventRegistration, EventOption, \
EventOptionChoice
-from gestioncof.models import EventCommentField, EventCommentValue
+from gestioncof.models import EventCommentField, EventCommentValue, \
+ CalendarSubscription
from gestioncof.shared import send_custom_mail
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, \
SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \
- RegistrationProfileForm, AdminEventForm, EventForm
+ RegistrationProfileForm, AdminEventForm, EventForm, CalendarForm
from bda.models import Tirage
@@ -618,3 +622,53 @@ def liste_diffcof(request):
personnes = CofProfile.objects.filter(mailing_cof=True, is_cof=True).all()
return render(request, "liste_mails.html", {"titre": titre,
"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
diff --git a/requirements.txt b/requirements.txt
index e7e7087b..32f60fc3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,3 +10,4 @@ Pillow==2.9.0
simplejson==3.8.2
six==1.10.0
unicodecsv==0.14.1
+icalendar==3.10
From b5704c7f6df4d7ed09e7b4d80e7732f7641d6d1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20P=C3=A9pin?=
Date: Fri, 15 Jul 2016 02:49:56 +0200
Subject: [PATCH 2/9] Corrections
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Typos dans les modèles
- Choix d'un `OneToOneField` dans `CalendarSubscription`
- Suppressions d'un `except` catch-all
- Amélioration de la vue calendar
---
gestioncof/forms.py | 4 ++--
gestioncof/migrations/0005_add_calendar.py | 22 +++++++++++++---------
gestioncof/models.py | 8 +++++---
gestioncof/views.py | 20 +++++++++-----------
4 files changed, 29 insertions(+), 25 deletions(-)
diff --git a/gestioncof/forms.py b/gestioncof/forms.py
index 500f3a8b..373d308e 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -339,9 +339,9 @@ 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(),
+ queryset=Spectacle.objects.filter(tirage__active=True),
widget=forms.CheckboxSelectMultiple,
- initial=Spectacle.objects.filter(tirage__active=True).all())
+ initial=Spectacle.objects.filter(tirage__active=True))
class Meta:
model = CalendarSubscription
diff --git a/gestioncof/migrations/0005_add_calendar.py b/gestioncof/migrations/0005_add_calendar.py
index b8686bb5..83b68a12 100644
--- a/gestioncof/migrations/0005_add_calendar.py
+++ b/gestioncof/migrations/0005_add_calendar.py
@@ -17,24 +17,28 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='CalendarSubscription',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False,
- auto_created=True, primary_key=True)),
- ('events', models.BooleanField(default=True)),
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('subscribe_to_events', models.BooleanField(default=True)),
('shows', models.ManyToManyField(to='bda.Spectacle')),
- ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+ ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
],
),
+ migrations.AlterModelOptions(
+ name='custommail',
+ options={'verbose_name': 'Mail personnalisable', 'verbose_name_plural': 'Mails personnalisables'},
+ ),
+ migrations.AlterModelOptions(
+ name='eventoptionchoice',
+ options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
+ ),
migrations.AlterField(
model_name='event',
name='end_date',
- field=models.DateTimeField(null=True, verbose_name=b'Date de fin',
- blank=True),
+ 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),
+ field=models.DateTimeField(null=True, verbose_name=b'Date de d\xc3\xa9but', blank=True),
),
]
diff --git a/gestioncof/models.py b/gestioncof/models.py
index b3ef9921..aedd43e7 100644
--- a/gestioncof/models.py
+++ b/gestioncof/models.py
@@ -96,7 +96,8 @@ class CustomMail(models.Model):
blank=True)
class Meta:
- verbose_name = "Mails personnalisables"
+ verbose_name = "Mail personnalisable"
+ verbose_name_plural = "Mails personnalisables"
def __unicode__(self):
return u"%s: %s" % (self.shortname, self.title)
@@ -160,6 +161,7 @@ class EventOptionChoice(models.Model):
class Meta:
verbose_name = "Choix"
+ verbose_name_plural = "Choix"
def __unicode__(self):
return unicode(self.value)
@@ -235,6 +237,6 @@ class Clipper(models.Model):
class CalendarSubscription(models.Model):
- user = models.ForeignKey(User)
+ user = models.OneToOneField(User)
shows = models.ManyToManyField(Spectacle)
- events = models.BooleanField(default=True)
+ subscribe_to_events = models.BooleanField(default=True)
diff --git a/gestioncof/views.py b/gestioncof/views.py
index f906bc42..25fff177 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -4,8 +4,7 @@ import unicodecsv
from datetime import timedelta
from icalendar import Calendar, Event as Vevent
-from django.shortcuts import redirect, get_object_or_404, render, \
- HttpResponse
+from django.shortcuts import redirect, get_object_or_404, render
from django.http import Http404, HttpResponse
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import login as django_login_view
@@ -626,10 +625,13 @@ def liste_diffcof(request):
@cof_required
def calendar(request):
+ try:
+ instance = CalendarSubscription.objects.get(user=request.user)
+ except CalendarSubscription.DoesNotExist:
+ instance = None
if request.method == 'POST':
- form = CalendarForm(request.POST)
+ form = CalendarForm(request.POST, instance=instance)
if form.is_valid():
- CalendarSubscription.objects.filter(user=request.user).delete()
subscription = form.save(commit=False)
subscription.user = request.user
subscription.save()
@@ -640,12 +642,8 @@ def calendar(request):
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})
+ return render(request, "calendar_subscription.html",
+ {'form': CalendarForm(instance=instance)})
@cof_required
@@ -660,7 +658,7 @@ def calendar_ics(request):
vevent.add('summary', show.title)
vevent.add('location', show.location.name)
vcal.add_component(vevent)
- if subscription.events:
+ if subscription.subscribe_to_events:
for event in Event.objects.filter(old=False).all():
vevent = Vevent()
vevent.add('dtstart', event.start_date)
From 301c374ee40209548360e199980661737e0e1c5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20P=C3=A9pin?=
Date: Fri, 15 Jul 2016 14:04:01 +0200
Subject: [PATCH 3/9] Typos
---
gestioncof/forms.py | 3 ++-
gestioncof/views.py | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/gestioncof/forms.py b/gestioncof/forms.py
index 373d308e..5d936885 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -336,7 +336,8 @@ class AdminEventForm(forms.Form):
class CalendarForm(forms.ModelForm):
- events = forms.BooleanField(initial=True, label="S'abonner aux événements")
+ subscribe_to_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),
diff --git a/gestioncof/views.py b/gestioncof/views.py
index 25fff177..e954fde8 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -640,7 +640,7 @@ def calendar(request):
{'form': form, 'success': True})
else:
return render(request, "calendar_subscription.html",
- {'form': form, 'error': "Formulaire incorect"})
+ {'form': form, 'error': "Formulaire incorrect"})
else:
return render(request, "calendar_subscription.html",
{'form': CalendarForm(instance=instance)})
From 7b32edbd381f0c79e34b591156ba5b9164f44848 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20P=C3=A9pin?=
Date: Fri, 15 Jul 2016 23:31:26 +0200
Subject: [PATCH 4/9] Rend le calendrier plus dynamique
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Plus besoin de s'authentifier pour accéder au calendrier : il suffit
d'entrer la bonne url qui contient un token aléatoire propre à chaque
utilisateur.
Ce token ne change pas lorsqu'on modifie son inscription
---
gestioncof/forms.py | 2 +-
gestioncof/migrations/0005_add_calendar.py | 14 ++++++++++----
gestioncof/models.py | 1 +
.../templates/calendar_subscription.html | 4 +++-
gestioncof/urls.py | 3 ++-
gestioncof/views.py | 18 +++++++++++-------
6 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/gestioncof/forms.py b/gestioncof/forms.py
index 5d936885..9c9b63f1 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -346,4 +346,4 @@ class CalendarForm(forms.ModelForm):
class Meta:
model = CalendarSubscription
- exclude = ['user']
+ exclude = ['user', 'token']
diff --git a/gestioncof/migrations/0005_add_calendar.py b/gestioncof/migrations/0005_add_calendar.py
index 83b68a12..780586aa 100644
--- a/gestioncof/migrations/0005_add_calendar.py
+++ b/gestioncof/migrations/0005_add_calendar.py
@@ -17,7 +17,9 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='CalendarSubscription',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('id', models.AutoField(verbose_name='ID', serialize=False,
+ auto_created=True, primary_key=True)),
+ ('token', models.UUIDField()),
('subscribe_to_events', models.BooleanField(default=True)),
('shows', models.ManyToManyField(to='bda.Spectacle')),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
@@ -25,7 +27,8 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='custommail',
- options={'verbose_name': 'Mail personnalisable', 'verbose_name_plural': 'Mails personnalisables'},
+ options={'verbose_name': 'Mail personnalisable',
+ 'verbose_name_plural': 'Mails personnalisables'},
),
migrations.AlterModelOptions(
name='eventoptionchoice',
@@ -34,11 +37,14 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='end_date',
- field=models.DateTimeField(null=True, verbose_name=b'Date de fin', blank=True),
+ 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),
+ field=models.DateTimeField(null=True,
+ verbose_name=b'Date de d\xc3\xa9but',
+ blank=True),
),
]
diff --git a/gestioncof/models.py b/gestioncof/models.py
index aedd43e7..1a9c0e16 100644
--- a/gestioncof/models.py
+++ b/gestioncof/models.py
@@ -237,6 +237,7 @@ class Clipper(models.Model):
class CalendarSubscription(models.Model):
+ token = models.UUIDField()
user = models.OneToOneField(User)
shows = models.ManyToManyField(Spectacle)
subscribe_to_events = models.BooleanField(default=True)
diff --git a/gestioncof/templates/calendar_subscription.html b/gestioncof/templates/calendar_subscription.html
index 65a8b9e6..64cd1826 100644
--- a/gestioncof/templates/calendar_subscription.html
+++ b/gestioncof/templates/calendar_subscription.html
@@ -10,8 +10,10 @@
{{ error }}
{% endif %}
+{% if token %}
Calendrier disponible à
-cette adresse
+cette adresse
+{% endif %}