diff --git a/mesads/api/views.py b/mesads/api/views.py
index e2b6b5a..54cfc78 100644
--- a/mesads/api/views.py
+++ b/mesads/api/views.py
@@ -60,7 +60,6 @@ def get(self, request):
geojson = shp.__geo_interface__
for feature in geojson["features"]:
insee_code = feature["properties"]["code_insee"]
- print(insee_code)
feature["properties"]["ads_count"] = stats.get(insee_code, {}).get(
"ads_count", 0
)
diff --git a/mesads/app/urls.py b/mesads/app/urls.py
index e25bbd3..b2fbf7e 100644
--- a/mesads/app/urls.py
+++ b/mesads/app/urls.py
@@ -28,9 +28,19 @@
),
path(
"registre_ads/admin_gestion",
- ads_manager_administrator_required(views.ADSManagerAdminView.as_view()),
+ ads_manager_administrator_required(views.ADSManagerAdminIndexView.as_view()),
name="app.ads-manager-admin.index",
),
+ path(
+ "registre_ads/admin_gestion/",
+ ads_manager_administrator_required(views.ADSManagerAdminDetailsView.as_view()),
+ name="app.ads-manager-admin.details",
+ ),
+ path(
+ "registre_ads/admin_gestion//changements",
+ ads_manager_administrator_required(views.ADSManagerAdminUpdatesView.as_view()),
+ name="app.ads-manager-admin.updates",
+ ),
path(
"registre_ads/gestion",
login_required(views.ADSManagerRequestView.as_view()),
diff --git a/mesads/app/views/__init__.py b/mesads/app/views/__init__.py
index cb6174c..9bd3ad7 100644
--- a/mesads/app/views/__init__.py
+++ b/mesads/app/views/__init__.py
@@ -13,10 +13,15 @@
ads_manager_decree_view,
ADSManagerAutocompleteView,
)
-from .ads_manager_admin import PrefectureExportView, ADSManagerExportView # noqa: F401
+from .ads_manager_admin import ( # noqa: F401
+ PrefectureExportView,
+ ADSManagerExportView,
+ ADSManagerAdminIndexView,
+ ADSManagerAdminDetailsView,
+ ADSManagerAdminUpdatesView,
+)
from .ads_manager_request import ( # noqa: F401
ADSManagerRequestView,
- ADSManagerAdminView,
)
from .dashboards import DashboardsView, DashboardsDetailView # noqa: F401
from .public import ( # noqa: F401
@@ -34,6 +39,9 @@ class ADSRegisterView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
if self.request.user.is_staff:
return reverse("app.dashboards.list")
- if len(self.request.user.adsmanageradministrator_set.all()):
- return reverse("app.ads-manager-admin.index")
+ ads_manager_administrators = self.request.user.adsmanageradministrator_set.all()
+ if len(ads_manager_administrators):
+ return reverse(
+ "app.ads-manager-admin.index",
+ )
return reverse("app.ads-manager.index")
diff --git a/mesads/app/views/ads_manager_admin.py b/mesads/app/views/ads_manager_admin.py
index ace36f5..1518498 100644
--- a/mesads/app/views/ads_manager_admin.py
+++ b/mesads/app/views/ads_manager_admin.py
@@ -1,14 +1,140 @@
-from django.shortcuts import get_object_or_404
+from django.conf import settings
+from django.core.exceptions import SuspiciousOperation
+from django.core.mail import send_mail
+from django.db import connection
+from django.shortcuts import get_object_or_404, redirect
+from django.template.loader import render_to_string
+from django.urls import reverse
from django.utils.text import slugify
-from django.views.generic import View
+from django.views.generic import RedirectView, View, TemplateView
+
+from reversion.views import RevisionMixin
from ..models import (
+ ADS,
+ ADSManagerAdministrator,
+ ADSManagerRequest,
ADSManager,
)
from .export import ADSExporter
+class ADSManagerAdminIndexView(RedirectView):
+ def get_redirect_url(self, *args, **kwargs):
+ administrators = ADSManagerAdministrator.objects.filter(
+ users__in=[self.request.user]
+ )
+ if len(administrators):
+ return reverse(
+ "app.ads-manager-admin.details",
+ kwargs={"prefecture_id": administrators[0].prefecture.id},
+ )
+ return reverse("app.ads-manager.index")
+
+
+class ADSManagerAdminDetailsView(RevisionMixin, TemplateView):
+ """This view is used by ADSManagerAdministrators to validate
+ ADSManagerRequests and list changes made by ADSManagers."""
+
+ template_name = "pages/ads_register/ads_manager_admin.html"
+
+ def get_context_data(self, **kwargs):
+ """Populate context with the list of ADSManagerRequest current user can accept."""
+ ctx = super().get_context_data(**kwargs)
+
+ query = (
+ ADSManagerRequest.objects.select_related(
+ "ads_manager",
+ "ads_manager__administrator",
+ "user",
+ )
+ .prefetch_related(
+ "ads_manager__content_type",
+ "ads_manager__content_object",
+ )
+ .filter(ads_manager__administrator=self.kwargs["ads_manager_administrator"])
+ )
+
+ if self.request.GET.get("sort") == "name":
+ ctx["sort"] = "name"
+ ctx["ads_manager_requests"] = query.order_by(
+ "ads_manager__administrator",
+ "ads_manager__commune__libelle",
+ "ads_manager__epci__name",
+ "ads_manager__prefecture__libelle",
+ )
+ else:
+ ctx["ads_manager_requests"] = query.order_by(
+ "ads_manager__administrator",
+ "-created_at",
+ )
+ return ctx
+
+ def post(self, request, **kwargs):
+ request_id = request.POST.get("request_id")
+ action = request.POST.get("action")
+
+ if action not in ("accept", "deny"):
+ raise SuspiciousOperation("Invalid action")
+
+ ads_manager_request = get_object_or_404(ADSManagerRequest, id=request_id)
+
+ # Make sure current user can accept this request
+ get_object_or_404(
+ ADSManagerAdministrator,
+ users__in=[request.user],
+ adsmanager=ads_manager_request.ads_manager,
+ )
+
+ if action == "accept":
+ ads_manager_request.accepted = True
+ else:
+ ads_manager_request.accepted = False
+ ads_manager_request.save()
+
+ # Send notification to user
+ email_subject = render_to_string(
+ "pages/email_ads_manager_request_result_subject.txt",
+ {
+ "ads_manager_request": ads_manager_request,
+ },
+ request=request,
+ ).strip()
+ email_content = render_to_string(
+ "pages/email_ads_manager_request_result_content.txt",
+ {
+ "request": request,
+ "ads_manager_request": ads_manager_request,
+ },
+ request=request,
+ )
+ email_content_html = render_to_string(
+ "pages/email_ads_manager_request_result_content.mjml",
+ {
+ "request": request,
+ "ads_manager_request": ads_manager_request,
+ },
+ request=request,
+ )
+ send_mail(
+ email_subject,
+ email_content,
+ settings.MESADS_CONTACT_EMAIL,
+ [ads_manager_request.user.email],
+ fail_silently=True,
+ html_message=email_content_html,
+ )
+ return redirect(
+ reverse(
+ "app.ads-manager-admin.details",
+ kwargs={
+ "prefecture_id": ads_manager_request.ads_manager.administrator.prefecture.id
+ },
+ )
+ )
+
+
class ADSManagerExportView(View, ADSExporter):
def get(self, request, manager_id):
self.ads_manager = get_object_or_404(ADSManager, id=manager_id)
@@ -73,3 +199,71 @@ def add_sheets(self, workbook):
),
)
sheet.autofit()
+
+
+class ADSManagerAdminUpdatesView(TemplateView):
+ template_name = "pages/ads_register/ads_manager_admin_updates.html"
+
+ def get_updates(self, cursor):
+ # You might be wondering why we didn't implement pagination and why we
+ # limit to 100 results.
+ # Long story short, it's because we are using a raw query and pagination
+ # needs to be handled manually, and I've spent way too much time on this
+ # already.
+ # Alternatively we could use the django ORM instead of a raw query, but
+ # good luck with that.
+ cursor.execute(
+ """
+ SELECT
+ ads.id AS id,
+ adsmanager.id,
+ CASE
+ WHEN COUNT(revision.id) = 0 THEN NULL
+ ELSE COALESCE(JSON_AGG(JSON_BUILD_OBJECT(
+ 'user_id', revision.user_id,
+ 'user_email', "user".email,
+ 'modification_date', revision.date_created
+ ) ORDER BY revision.date_created DESC), '[]'::json)
+ END as updates
+ FROM app_ads AS ads
+ LEFT JOIN app_adsmanager AS adsmanager
+ ON adsmanager.id = ads.ads_manager_id
+ LEFT JOIN app_adsmanageradministrator AS adsmanageradministrator
+ ON adsmanager.administrator_id = adsmanageradministrator.id
+ LEFT JOIN reversion_version AS version
+ ON (version.serialized_data::json -> 0 ->> 'model') = 'app.ads'
+ AND (version.serialized_data::json -> 0 ->> 'pk')::bigint = ads.id
+ LEFT JOIN reversion_revision AS revision
+ ON version.revision_id = revision.id
+ LEFT JOIN users_user AS "user"
+ ON "user".id = revision.user_id
+ WHERE
+ adsmanageradministrator.id = %s
+ GROUP BY ads.id, adsmanager.id
+ ORDER BY ads.last_update DESC
+ LIMIT 100
+ """,
+ (self.kwargs["ads_manager_administrator"].id,),
+ )
+ updates = cursor.fetchall()
+
+ # Load objects
+ ads_objects = ADS.objects.filter(id__in=[row[0] for row in updates])
+ ads_dict = {obj.id: obj for obj in ads_objects}
+
+ ads_managers = ADSManager.objects.filter(id__in=[row[1] for row in updates])
+ ads_managers_dict = {obj.id: obj for obj in ads_managers}
+ return [
+ {
+ "ads": ads_dict[update[0]],
+ "ads_manager": ads_managers_dict[update[1]],
+ "history_entries": update[2],
+ }
+ for update in updates
+ ]
+
+ def get_context_data(self, *args, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ with connection.cursor() as cursor:
+ ctx["updates"] = self.get_updates(cursor)
+ return ctx
diff --git a/mesads/app/views/ads_manager_request.py b/mesads/app/views/ads_manager_request.py
index 4850ba1..ac7d6d8 100644
--- a/mesads/app/views/ads_manager_request.py
+++ b/mesads/app/views/ads_manager_request.py
@@ -1,17 +1,12 @@
from django.conf import settings
from django.contrib import messages
-from django.core.exceptions import SuspiciousOperation
from django.core.mail import send_mail
from django.db.models import Count
from django.db import transaction
-from django.shortcuts import get_object_or_404, redirect
from django.template.loader import render_to_string
-from django.urls import reverse, reverse_lazy
-from django.views.generic import TemplateView
+from django.urls import reverse_lazy
from django.views.generic.edit import FormView
-from reversion.views import RevisionMixin
-
from ..forms import (
ADSManagerForm,
)
@@ -21,96 +16,6 @@
)
-class ADSManagerAdminView(RevisionMixin, TemplateView):
- """This view is used by ADSManagerAdministrators to validate ADSManagerRequests."""
-
- template_name = "pages/ads_register/ads_manager_admin.html"
-
- def get_context_data(self, **kwargs):
- """Populate context with the list of ADSManagerRequest current user can accept."""
- ctx = super().get_context_data(**kwargs)
- query = (
- ADSManagerRequest.objects.select_related(
- "ads_manager__administrator",
- "ads_manager__administrator__prefecture",
- "ads_manager__content_type",
- "user",
- )
- .prefetch_related("ads_manager__content_object")
- .filter(ads_manager__administrator__users__in=[self.request.user])
- )
- if self.request.GET.get("sort") == "name":
- ctx["sort"] = "name"
- ctx["ads_manager_requests"] = query.order_by(
- "ads_manager__administrator",
- "ads_manager__commune__libelle",
- "ads_manager__epci__name",
- "ads_manager__prefecture__libelle",
- )
- else:
- ctx["ads_manager_requests"] = query.order_by(
- "ads_manager__administrator",
- "-created_at",
- )
- return ctx
-
- def post(self, request):
- request_id = request.POST.get("request_id")
- action = request.POST.get("action")
-
- if action not in ("accept", "deny"):
- raise SuspiciousOperation("Invalid action")
-
- ads_manager_request = get_object_or_404(ADSManagerRequest, id=request_id)
-
- # Make sure current user can accept this request
- get_object_or_404(
- ADSManagerAdministrator,
- users__in=[request.user],
- adsmanager=ads_manager_request.ads_manager,
- )
-
- if action == "accept":
- ads_manager_request.accepted = True
- else:
- ads_manager_request.accepted = False
- ads_manager_request.save()
-
- # Send notification to user
- email_subject = render_to_string(
- "pages/email_ads_manager_request_result_subject.txt",
- {
- "ads_manager_request": ads_manager_request,
- },
- request=request,
- ).strip()
- email_content = render_to_string(
- "pages/email_ads_manager_request_result_content.txt",
- {
- "request": request,
- "ads_manager_request": ads_manager_request,
- },
- request=request,
- )
- email_content_html = render_to_string(
- "pages/email_ads_manager_request_result_content.mjml",
- {
- "request": request,
- "ads_manager_request": ads_manager_request,
- },
- request=request,
- )
- send_mail(
- email_subject,
- email_content,
- settings.MESADS_CONTACT_EMAIL,
- [ads_manager_request.user.email],
- fail_silently=True,
- html_message=email_content_html,
- )
- return redirect(reverse("app.ads-manager-admin.index"))
-
-
class ADSManagerRequestView(FormView):
template_name = "pages/ads_register/ads_manager_request.html"
form_class = ADSManagerForm
diff --git a/mesads/app/views/test_ads_manager_admin.py b/mesads/app/views/test_ads_manager_admin.py
index ac1a5cf..453ce37 100644
--- a/mesads/app/views/test_ads_manager_admin.py
+++ b/mesads/app/views/test_ads_manager_admin.py
@@ -1,11 +1,98 @@
from datetime import datetime
-from ..models import (
- ADS,
-)
+from django.core import mail
+
+from ..models import ADS, ADSManager, ADSManagerRequest
from ..unittest import ClientTestCase
+class TestADSManagerAdminView(ClientTestCase):
+ def setUp(self):
+ super().setUp()
+ self.ads_manager_request = ADSManagerRequest.objects.create(
+ user=self.create_user().obj,
+ ads_manager=self.ads_manager_city35,
+ accepted=None,
+ )
+
+ def test_permissions(self):
+ for client_name, client, expected_status in (
+ ("admin", self.admin_client, 200),
+ ("anonymous", self.anonymous_client, 302),
+ ("auth", self.auth_client, 404),
+ ("ads_manager 35", self.ads_manager_city35_client, 404),
+ ("ads_manager_admin 35", self.ads_manager_administrator_35_client, 200),
+ ):
+ with self.subTest(client_name=client_name, expected_status=expected_status):
+ resp = client.get(
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}"
+ )
+ self.assertEqual(resp.status_code, expected_status)
+
+ def test_invalid_action(self):
+ resp = self.ads_manager_administrator_35_client.post(
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}",
+ {"action": "xxx", "request_id": 1},
+ )
+ self.assertEqual(resp.status_code, 400)
+
+ def test_invalid_request_id(self):
+ resp = self.ads_manager_administrator_35_client.post(
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}",
+ {"action": "accept", "request_id": 12342},
+ )
+ self.assertEqual(resp.status_code, 404)
+
+ def test_accept(self):
+ self.assertEqual(len(mail.outbox), 0)
+
+ resp = self.ads_manager_administrator_35_client.post(
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}",
+ {"action": "accept", "request_id": self.ads_manager_request.id},
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ resp.url,
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}",
+ )
+ self.ads_manager_request.refresh_from_db()
+ self.assertTrue(self.ads_manager_request.accepted)
+ self.assertEqual(len(mail.outbox), 1)
+
+ def test_deny(self):
+ self.assertEqual(len(mail.outbox), 0)
+
+ resp = self.ads_manager_administrator_35_client.post(
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}",
+ {"action": "deny", "request_id": self.ads_manager_request.id},
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ resp.url,
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}",
+ )
+ self.ads_manager_request.refresh_from_db()
+ self.assertFalse(self.ads_manager_request.accepted)
+ self.assertEqual(len(mail.outbox), 1)
+
+ def test_sort(self):
+ for ads_manager in ADSManager.objects.all():
+ ADSManagerRequest.objects.create(
+ user=self.create_user().obj,
+ ads_manager=ads_manager,
+ accepted=None,
+ )
+ resp = self.ads_manager_administrator_35_client.get(
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}",
+ )
+ self.assertEqual(resp.status_code, 200)
+
+ resp = self.ads_manager_administrator_35_client.get(
+ f"/registre_ads/admin_gestion/{self.ads_manager_administrator_35.prefecture_id}?sort=name",
+ )
+ self.assertEqual(resp.status_code, 200)
+
+
class TestExportPrefecture(ClientTestCase):
def test_permissions(self):
for client_name, client, expected_status in (
diff --git a/mesads/app/views/test_ads_manager_request.py b/mesads/app/views/test_ads_manager_request.py
index 789949c..a0e216f 100644
--- a/mesads/app/views/test_ads_manager_request.py
+++ b/mesads/app/views/test_ads_manager_request.py
@@ -4,90 +4,12 @@
from mesads.fradm.models import EPCI, Prefecture
from ..models import (
- ADSManager,
ADSManagerRequest,
Notification,
)
from ..unittest import ClientTestCase
-class TestADSManagerAdminView(ClientTestCase):
- def setUp(self):
- super().setUp()
- self.ads_manager_request = ADSManagerRequest.objects.create(
- user=self.create_user().obj,
- ads_manager=self.ads_manager_city35,
- accepted=None,
- )
-
- def test_permissions(self):
- for client_name, client, expected_status in (
- ("admin", self.admin_client, 200),
- ("anonymous", self.anonymous_client, 302),
- ("auth", self.auth_client, 404),
- ("ads_manager 35", self.ads_manager_city35_client, 404),
- ("ads_manager_admin 35", self.ads_manager_administrator_35_client, 200),
- ):
- with self.subTest(client_name=client_name, expected_status=expected_status):
- resp = client.get("/registre_ads/admin_gestion")
- self.assertEqual(resp.status_code, expected_status)
-
- def test_invalid_action(self):
- resp = self.ads_manager_administrator_35_client.post(
- "/registre_ads/admin_gestion", {"action": "xxx", "request_id": 1}
- )
- self.assertEqual(resp.status_code, 400)
-
- def test_invalid_request_id(self):
- resp = self.ads_manager_administrator_35_client.post(
- "/registre_ads/admin_gestion", {"action": "accept", "request_id": 12342}
- )
- self.assertEqual(resp.status_code, 404)
-
- def test_accept(self):
- self.assertEqual(len(mail.outbox), 0)
-
- resp = self.ads_manager_administrator_35_client.post(
- "/registre_ads/admin_gestion",
- {"action": "accept", "request_id": self.ads_manager_request.id},
- )
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(resp.url, "/registre_ads/admin_gestion")
- self.ads_manager_request.refresh_from_db()
- self.assertTrue(self.ads_manager_request.accepted)
- self.assertEqual(len(mail.outbox), 1)
-
- def test_deny(self):
- self.assertEqual(len(mail.outbox), 0)
-
- resp = self.ads_manager_administrator_35_client.post(
- "/registre_ads/admin_gestion",
- {"action": "deny", "request_id": self.ads_manager_request.id},
- )
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(resp.url, "/registre_ads/admin_gestion")
- self.ads_manager_request.refresh_from_db()
- self.assertFalse(self.ads_manager_request.accepted)
- self.assertEqual(len(mail.outbox), 1)
-
- def test_sort(self):
- for ads_manager in ADSManager.objects.all():
- ADSManagerRequest.objects.create(
- user=self.create_user().obj,
- ads_manager=ads_manager,
- accepted=None,
- )
- resp = self.ads_manager_administrator_35_client.get(
- "/registre_ads/admin_gestion",
- )
- self.assertEqual(resp.status_code, 200)
-
- resp = self.ads_manager_administrator_35_client.get(
- "/registre_ads/admin_gestion?sort=name",
- )
- self.assertEqual(resp.status_code, 200)
-
-
class TestADSManagerRequestView(ClientTestCase):
def setUp(self):
super().setUp()
diff --git a/mesads/html_metadata.yml b/mesads/html_metadata.yml
index b3b6ef3..4fffbf0 100644
--- a/mesads/html_metadata.yml
+++ b/mesads/html_metadata.yml
@@ -107,6 +107,9 @@ urls:
description: "Découvrez en détail le déploiement et l'utilisation du registre ADS dans {{ object.prefecture.display_fulltext }}."
app.ads-manager-admin.index:
+ missing: true # Redirect view
+
+ app.ads-manager-admin.details:
title: "Gestion et contrôle des accès des instructeurs ADS"
description: "Gérez les demandes d'accès au registre ADS en acceptant ou refusant les instructeurs de votre département."
diff --git a/mesads/templates/django/pages/email_ads_manager_request_administrator_content.mjml b/mesads/templates/django/pages/email_ads_manager_request_administrator_content.mjml
index bd1575d..1a0b50a 100644
--- a/mesads/templates/django/pages/email_ads_manager_request_administrator_content.mjml
+++ b/mesads/templates/django/pages/email_ads_manager_request_administrator_content.mjml
@@ -16,7 +16,7 @@
-
+
Aller sur Mes ADS pour répondre à la demande
diff --git a/mesads/templates/django/pages/email_ads_manager_request_administrator_content.txt b/mesads/templates/django/pages/email_ads_manager_request_administrator_content.txt
index fcfba28..d8e00e6 100644
--- a/mesads/templates/django/pages/email_ads_manager_request_administrator_content.txt
+++ b/mesads/templates/django/pages/email_ads_manager_request_administrator_content.txt
@@ -5,7 +5,7 @@ L'utilisateur {{ user.email }} souhaite obtenir la permission de gérer les auto
En tant qu'administrateur, vous avez le pouvoir d'accepter ou de refuser d'accéder à cette demande.
-Rendez-vous sur MesADS sur {{ request.scheme }}://{{ request.get_host }}{% url 'app.ads-manager-admin.index' %} pour répondre à la demande.
+Rendez-vous sur MesADS sur {{ request.scheme }}://{{ request.get_host }}{% url 'app.ads-manager-admin.details' prefecture_id=ads_manager.administrator.prefecture_id %} pour répondre à la demande.
Attention ! Avant d'accepter une demande, assurez-vous que le demandeur travaille bien pour l'administration renseignée. N'hésitez pas à lui envoyer un email ou un appel téléphonique pour demander confirmation.
{% endblock %}
\ No newline at end of file
diff --git a/mesads/templates/webpack/pages/ads_register/ads.html b/mesads/templates/webpack/pages/ads_register/ads.html
index b3a5438..deded35 100644
--- a/mesads/templates/webpack/pages/ads_register/ads.html
+++ b/mesads/templates/webpack/pages/ads_register/ads.html
@@ -3,7 +3,7 @@
{% block main %}