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/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)" 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..8dd81df7 --- /dev/null +++ b/events/tests/test_views.py @@ -0,0 +1,59 @@ +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/urls.py b/events/urls.py new file mode 100644 index 00000000..df7a8c70 --- /dev/null +++ b/events/urls.py @@ -0,0 +1,12 @@ +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..6f49cdb7 100644 --- a/events/views.py +++ b/events/views.py @@ -0,0 +1,25 @@ +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", raise_exception=True) +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