From 41a3c4c161338f63a82c8aca3d554b2515f7f85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 5 Oct 2019 15:04:35 +0200 Subject: [PATCH 1/4] add event subscriptions (models only) --- events/migrations/0002_event_subscribers.py | 22 +++++++++++++++++++++ events/models.py | 6 ++++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 events/migrations/0002_event_subscribers.py diff --git a/events/migrations/0002_event_subscribers.py b/events/migrations/0002_event_subscribers.py new file mode 100644 index 00000000..7c0c35f7 --- /dev/null +++ b/events/migrations/0002_event_subscribers.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.6 on 2019-10-05 13:03 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("events", "0001_event"), + ] + + operations = [ + migrations.AddField( + model_name="event", + name="subscribers", + field=models.ManyToManyField( + to=settings.AUTH_USER_MODEL, verbose_name="inscrit⋅e⋅s" + ), + ) + ] diff --git a/events/models.py b/events/models.py index 1fa3a96e..b2876301 100644 --- a/events/models.py +++ b/events/models.py @@ -1,6 +1,9 @@ +from django.contrib.auth import get_user_model from django.db import models from django.utils.translation import gettext_lazy as _ +User = get_user_model() + class Event(models.Model): title = models.CharField(_("titre"), max_length=200) @@ -13,6 +16,7 @@ class Event(models.Model): ) registration_open = models.BooleanField(_("inscriptions ouvertes"), default=True) old = models.BooleanField(_("archiver (événement fini)"), default=False) + subscribers = models.ManyToManyField(User, verbose_name=_("inscrit⋅e⋅s")) class Meta: verbose_name = _("événement") @@ -22,8 +26,6 @@ class Event(models.Model): return self.title -# TODO: gérer les inscriptions - # TODO: gérer les options (EventOption & EventOptionChoice de gestioncof) # par exemple: "option végé au Mega (oui / non)" From 33bc3c588280de7c3870449f5966a737ac5b2ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 5 Oct 2019 15:58:11 +0200 Subject: [PATCH 2/4] Events: simple csv participants export --- cof/urls.py | 6 ++++++ events/urls.py | 11 +++++++++++ events/views.py | 24 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 events/urls.py diff --git a/cof/urls.py b/cof/urls.py index 82d047e7..1baa2a8e 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -123,6 +123,12 @@ urlpatterns = [ path("config", gestioncof_views.ConfigUpdate.as_view(), name="config.edit"), ] +if "events" in settings.INSTALLED_APPS: + # The new event application is still in development + # → for now it is namespaced below events_v2 + # → when the old events system is out, move this above in the others apps + urlpatterns += [path("event_v2/", include("events.urls"))] + if "debug_toolbar" in settings.INSTALLED_APPS: import debug_toolbar diff --git a/events/urls.py b/events/urls.py new file mode 100644 index 00000000..84054448 --- /dev/null +++ b/events/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from events import views + +app_name = "events" +urlpatterns = [ + path( + "csv/participants/", + views.participants_csv, + name="csv-participants", + ) +] diff --git a/events/views.py b/events/views.py index e69de29b..22fe6e50 100644 --- a/events/views.py +++ b/events/views.py @@ -0,0 +1,24 @@ +import csv + +from django.contrib.auth.decorators import login_required, permission_required +from django.http import HttpResponse +from django.shortcuts import get_object_or_404 +from django.utils.text import slugify +from events.models import Event + + +@login_required +@permission_required("events.view_event") +def participants_csv(request, event_id): + event = get_object_or_404(Event, id=event_id) + + filename = "{}-participants.csv".format(slugify(event.title)) + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = 'attachment; filename="{}"'.format(filename) + + writer = csv.writer(response) + writer.writerow(["username", "email", "prénom", "nom de famille"]) + for user in event.subscribers.all(): + writer.writerow([user.username, user.email, user.first_name, user.last_name]) + + return response From a8fd04e4c08291f732ba82213b3021171e3b8087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 5 Oct 2019 16:55:05 +0200 Subject: [PATCH 3/4] test events.views.participants_csv --- events/tests/__init__.py | 0 events/tests/test_views.py | 58 ++++++++++++++++++++++++++++++++++++++ events/views.py | 2 +- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 events/tests/__init__.py create mode 100644 events/tests/test_views.py diff --git a/events/tests/__init__.py b/events/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/events/tests/test_views.py b/events/tests/test_views.py new file mode 100644 index 00000000..aa3f2fc3 --- /dev/null +++ b/events/tests/test_views.py @@ -0,0 +1,58 @@ +from unittest import mock + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission +from django.test import Client, TestCase +from django.urls import reverse +from events.models import Event + +User = get_user_model() + + +def make_user(name): + return User.objects.create_user(username=name, password=name) + + +def make_staff_user(name): + view_event_perm = Permission.objects.get_by_natural_key( + codename="view_event", app_label="events", model="event" + ) + user = make_user(name) + user.user_permissions.add(view_event_perm) + return user + + +class CSVExportTest(TestCase): + def setUp(self): + # Signals handlers on login/logout send messages. + # Due to the way the Django' test Client performs login, this raise an + # error. As workaround, we mock the Django' messages module. + patcher_messages = mock.patch("gestioncof.signals.messages") + patcher_messages.start() + self.addCleanup(patcher_messages.stop) + + self.staff = make_staff_user("staff") + self.u1 = make_user("toto") + self.u2 = make_user("titi") + self.event = Event.objects.create(title="test_event", location="somewhere") + self.event.subscribers.set([self.u1, self.u2]) + self.url = reverse("events:csv-participants", args=[self.event.id]) + + def test_get(self): + client = Client() + client.force_login(self.staff) + r = client.get(self.url) + self.assertEqual(r.status_code, 200) + + def test_anonymous(self): + client = Client() + r = client.get(self.url) + self.assertRedirects( + r, "/login?next={}".format(self.url), fetch_redirect_response=False + ) + + def test_unauthorised(self): + client = Client() + client.force_login(self.u1) + r = client.get(self.url) + self.assertEqual(r.status_code, 403) diff --git a/events/views.py b/events/views.py index 22fe6e50..ea20de6f 100644 --- a/events/views.py +++ b/events/views.py @@ -8,7 +8,7 @@ from events.models import Event @login_required -@permission_required("events.view_event") +@permission_required("events.view_event", raise_exception=True) def participants_csv(request, event_id): event = get_object_or_404(Event, id=event_id) From f5766e920743a3ce1605e13c4160c8066ac4d407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Tue, 8 Oct 2019 23:33:46 +0200 Subject: [PATCH 4/4] events: make isort happy --- events/tests/test_views.py | 1 + events/urls.py | 1 + events/views.py | 1 + 3 files changed, 3 insertions(+) diff --git a/events/tests/test_views.py b/events/tests/test_views.py index aa3f2fc3..8dd81df7 100644 --- a/events/tests/test_views.py +++ b/events/tests/test_views.py @@ -4,6 +4,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission from django.test import Client, TestCase from django.urls import reverse + from events.models import Event User = get_user_model() diff --git a/events/urls.py b/events/urls.py index 84054448..df7a8c70 100644 --- a/events/urls.py +++ b/events/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from events import views app_name = "events" diff --git a/events/views.py b/events/views.py index ea20de6f..6f49cdb7 100644 --- a/events/views.py +++ b/events/views.py @@ -4,6 +4,7 @@ from django.contrib.auth.decorators import login_required, permission_required from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.utils.text import slugify + from events.models import Event