diff --git a/bda/templates/liste_spectacles.ics b/bda/templates/liste_spectacles.ics
deleted file mode 100644
index 1ce599f3..00000000
--- a/bda/templates/liste_spectacles.ics
+++ /dev/null
@@ -1,10 +0,0 @@
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//GESTIOCOF/bda//EN
-{% for spectacle in spectacles %}BEGIN:VEVENT
-DTSTART;TZID=Europe/Paris:{{ spectacle.date|date:'Ymd\\THis' }}
-DTEND;TZID=Europe/Paris:{{ spectacle.dtend|date:'Ymd\\THis' }}
-SUMMARY:{{ spectacle.title|safe }}
-LOCATION:{{ spectacle.location.name|safe }}
-END:VEVENT
-{% endfor %}END:VCALENDAR
\ No newline at end of file
diff --git a/bda/templates/resume_places.html b/bda/templates/resume_places.html
index 4ed33e72..9ca5c78f 100644
--- a/bda/templates/resume_places.html
+++ b/bda/templates/resume_places.html
@@ -18,7 +18,9 @@
Total à payer : {{ total|floatformat }}€
-
+ Ne manque pas un spectacle avec le
+ calendrier
+ automatique !
{% else %}
Vous n'avez aucune place :(
{% endif %}
diff --git a/bda/templates/resume_places.ics b/bda/templates/resume_places.ics
deleted file mode 100644
index 72aa4347..00000000
--- a/bda/templates/resume_places.ics
+++ /dev/null
@@ -1,10 +0,0 @@
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//GESTIOCOF/bda//EN
-{% for place in places %}BEGIN:VEVENT
-DTSTART;TZID=Europe/Paris:{{ place.spectacle.date|date:'Ymd\\THis' }}
-DTEND;TZID=Europe/Paris:{{ place.spectacle.dtend|date:'Ymd\\THis' }}
-SUMMARY:{{ place.spectacle.title|safe }}{% if place.double %} (deux places){% endif %}
-LOCATION:{{ place.spectacle.location.name|safe }}
-END:VEVENT
-{% endfor %}END:VCALENDAR
\ No newline at end of file
diff --git a/bda/templates/spectacle_list.html b/bda/templates/spectacle_list.html
index dedc655d..0c3c7317 100644
--- a/bda/templates/spectacle_list.html
+++ b/bda/templates/spectacle_list.html
@@ -48,6 +48,5 @@
Exports
{% endblock %}
diff --git a/bda/urls.py b/bda/urls.py
index 4e5811a1..e3c5a9fb 100644
--- a/bda/urls.py
+++ b/bda/urls.py
@@ -15,9 +15,6 @@ urlpatterns = patterns(
url(r'^places/(?P\d+)$',
'bda.views.places',
name="bda-places-attribuees"),
- url(r'^places/(?P\d+)/places_bda.ics$',
- 'bda.views.places_ics',
- name="bda-places-attribuees-ics"),
url(r'^revente/(?P\d+)$',
'bda.views.revente',
name='bda-revente'),
@@ -31,9 +28,6 @@ urlpatterns = patterns(
url(r'^spectacles/(?P\d+)/(?P\d+)$',
"bda.views.spectacle",
name="bda-spectacle"),
- url(r'^spectacles-ics/(?P\d+)$',
- 'bda.views.liste_spectacles_ics',
- name="bda-liste-spectacles-ics"),
url(r'^spectacles/unpaid/(?P\d+)$',
"bda.views.unpaid",
name="bda-unpaid"),
diff --git a/bda/views.py b/bda/views.py
index bf666f08..ededccd7 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -106,31 +106,6 @@ def places(request, tirage_id):
"warning": warning})
-@cof_required
-def places_ics(request, tirage_id):
- tirage = get_object_or_404(Tirage, id=tirage_id)
- participant, created = Participant.objects.get_or_create(
- user=request.user, tirage=tirage)
- places = participant.attribution_set.order_by(
- "spectacle__date", "spectacle").all()
- filtered_places = []
- places_dict = {}
- spectacles = []
- for place in places:
- if place.spectacle in spectacles:
- places_dict[place.spectacle].double = True
- else:
- place.double = False
- place.spectacle.dtend = place.spectacle.date \
- + timedelta(seconds=7200)
- places_dict[place.spectacle] = place
- spectacles.append(place.spectacle)
- filtered_places.append(place)
- return render(request, "resume_places.ics",
- {"participant": participant,
- "places": filtered_places}, content_type="text/calendar")
-
-
@cof_required
def inscription(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
@@ -365,17 +340,6 @@ def unpaid(request, tirage_id):
return render(request, "bda-unpaid.html", {"unpaid": unpaid})
-@buro_required
-def liste_spectacles_ics(request, tirage_id):
- tirage = get_object_or_404(Tirage, id=tirage_id)
- spectacles = tirage.spectacle_set.order_by("date").all()
- for spectacle in spectacles:
- spectacle.dtend = spectacle.date + timedelta(seconds=7200)
- return render(request, "liste_spectacles.ics",
- {"spectacles": spectacles, "tirage": tirage},
- content_type="text/calendar")
-
-
@buro_required
def send_rappel(request, spectacle_id):
show = get_object_or_404(Spectacle, id=spectacle_id)
diff --git a/cof/urls.py b/cof/urls.py
index ee9d8457..79177170 100644
--- a/cof/urls.py
+++ b/cof/urls.py
@@ -13,7 +13,7 @@ from django.views.generic.base import TemplateView
import autocomplete_light
from gestioncof.urls import export_patterns, petitcours_patterns, \
- surveys_patterns, events_patterns
+ surveys_patterns, events_patterns, calendar_patterns
autocomplete_light.autodiscover()
admin.autodiscover()
@@ -32,6 +32,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 170ebcd0..15db25ce 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -10,10 +10,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):
@@ -333,3 +336,21 @@ 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):
+ subscribe_to_events = forms.BooleanField(
+ initial=True,
+ label="Événements du COF.")
+ subscribe_to_my_shows = forms.BooleanField(
+ initial=True,
+ label="Les spectacles pour lesquels j'ai obtenu une place.")
+ other_shows = forms.ModelMultipleChoiceField(
+ label="Spectacles supplémentaires.",
+ queryset=Spectacle.objects.filter(tirage__active=True),
+ widget=forms.CheckboxSelectMultiple)
+
+ class Meta:
+ model = CalendarSubscription
+ fields = ['subscribe_to_events', 'subscribe_to_my_shows',
+ 'other_shows']
diff --git a/gestioncof/migrations/0005_add_calendar.py b/gestioncof/migrations/0005_add_calendar.py
new file mode 100644
index 00000000..5d894f13
--- /dev/null
+++ b/gestioncof/migrations/0005_add_calendar.py
@@ -0,0 +1,50 @@
+# -*- 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)),
+ ('token', models.UUIDField()),
+ ('subscribe_to_events', models.BooleanField(default=True)),
+ ('subscribe_to_my_shows', models.BooleanField(default=True)),
+ ('other_shows', models.ManyToManyField(to='bda.Spectacle')),
+ ('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),
+ ),
+ 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 7df0fb00..95837a3a 100644
--- a/gestioncof/models.py
+++ b/gestioncof/models.py
@@ -13,6 +13,8 @@ from django.db.models.signals import post_save
from gestioncof.petits_cours_models import choices_length
+from bda.models import Spectacle
+
OCCUPATION_CHOICES = (
('exterieur', _("Extérieur")),
('1A', _("1A")),
@@ -113,8 +115,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/")
@@ -262,3 +264,15 @@ class Clipper(models.Model):
def __str__(self):
return "Clipper %s" % self.username
+
+
+@python_2_unicode_compatible
+class CalendarSubscription(models.Model):
+ token = models.UUIDField()
+ user = models.OneToOneField(User)
+ other_shows = models.ManyToManyField(Spectacle)
+ subscribe_to_events = models.BooleanField(default=True)
+ subscribe_to_my_shows = models.BooleanField(default=True)
+
+ def __str__(self):
+ return "Calendrier de %s" % self.user.get_full_name()
diff --git a/gestioncof/templates/calendar_subscription.html b/gestioncof/templates/calendar_subscription.html
new file mode 100644
index 00000000..52f6d492
--- /dev/null
+++ b/gestioncof/templates/calendar_subscription.html
@@ -0,0 +1,43 @@
+{% extends "base_title.html" %}
+
+{% block realcontent %}
+
+Calendrier dynamique
+
+{% if success %}
+Calendrier mis à jour avec succès
+{% endif %}
+
+{% if error %}
+{{ error }}
+{% endif %}
+
+Ce formulaire vous permet de définir un calendrier dynamique compatible avec
+n'importe quel logiciel ou application d'agenda. Vous pouvez choisir de
+souscrire aux événements du COF et/ou aux spectacles BdA.
+
+
+{% if token %}
+Votre calendrier (compatible avec toutes les applications d'agenda) se trouve à
+cette adresse.
+
+
+ - Pour l'ajouter à Thunderbird (lightning), il faut copier ce lien et aller
+ dans Fichier > Nouveau > Agenda puis choisir Sur le
+ réseau et le format .ics.
+ - Avec Apple, il suffit de cliquer sur lien et d'ouvrir le fichier avec
+ l'application calendrier.
+ - Google Agenda permet d'importer le fichier au format .ics depuis le menu
+ Préférences.
+
+{% endif %}
+
+
+
+
+
+{% endblock %}
diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html
index f1983992..bcec273c 100644
--- a/gestioncof/templates/home.html
+++ b/gestioncof/templates/home.html
@@ -50,7 +50,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 5cea2f8e..bf4089db 100644
--- a/gestioncof/urls.py
+++ b/gestioncof/urls.py
@@ -45,3 +45,9 @@ 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'^(?P[a-z0-9-]+)/calendar.ics$',
+ 'gestioncof.views.calendar_ics')
+]
diff --git a/gestioncof/views.py b/gestioncof/views.py
index 2d5f0487..378cb44d 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -5,6 +5,9 @@ from __future__ import print_function
from __future__ import unicode_literals
import unicodecsv
+import uuid
+from datetime import timedelta
+from icalendar import Calendar, Event as Vevent
from django.shortcuts import redirect, get_object_or_404, render
from django.http import Http404, HttpResponse
@@ -17,15 +20,16 @@ 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
+from bda.models import Tirage, Spectacle
@login_required
@@ -633,3 +637,61 @@ 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):
+ try:
+ instance = CalendarSubscription.objects.get(user=request.user)
+ except CalendarSubscription.DoesNotExist:
+ instance = None
+ if request.method == 'POST':
+ form = CalendarForm(request.POST, instance=instance)
+ if form.is_valid():
+ subscription = form.save(commit=False)
+ if instance is None:
+ subscription.user = request.user
+ subscription.token = uuid.uuid4()
+ subscription.save()
+ form.save_m2m()
+ return render(request, "calendar_subscription.html",
+ {'form': form,
+ 'success': True,
+ 'token': str(subscription.token)})
+ else:
+ return render(request, "calendar_subscription.html",
+ {'form': form, 'error': "Formulaire incorrect"})
+ else:
+ return render(request, "calendar_subscription.html",
+ {'form': CalendarForm(instance=instance),
+ 'token': instance.token if instance else None})
+
+
+def calendar_ics(request, token):
+ subscription = get_object_or_404(CalendarSubscription, token=token)
+ shows = subscription.other_shows.all()
+ if subscription.subscribe_to_my_shows:
+ shows |= Spectacle.objects.filter(
+ attribues__participant__user=subscription.user,
+ tirage__active=True)
+ shows = shows.distinct()
+ vcal = Calendar()
+ for show in shows:
+ 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.subscribe_to_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 43d30dc9..b6f8cc6f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,4 +10,5 @@ Pillow==2.9.0
simplejson==3.8.2
six==1.10.0
unicodecsv==0.14.1
+icalendar==3.10
django-bootstrap-form==3.2.1