diff --git a/mesads/app/views.py b/mesads/app/views.py
deleted file mode 100644
index e75a324..0000000
--- a/mesads/app/views.py
+++ /dev/null
@@ -1,1388 +0,0 @@
-from datetime import date, datetime, timedelta
-import collections
-import json
-
-from docxtpl import DocxTemplate
-
-from django.conf import settings
-from django.contrib import messages
-from django.contrib.postgres.aggregates import ArrayAgg
-from django.contrib.staticfiles import finders
-from django.core.exceptions import SuspiciousOperation
-from django.core.mail import send_mail
-from django.db import IntegrityError, transaction
-from django.db.models import Count, Q, Sum, Value
-from django.db.models.functions import Coalesce, Replace, TruncMonth
-from django.http import HttpResponse, HttpResponseRedirect
-from django.shortcuts import get_object_or_404, redirect, render
-from django.template.defaultfilters import date as date_template_filter
-from django.template.loader import render_to_string
-from django.urls import reverse, reverse_lazy
-from django.utils import timezone
-from django.utils.text import slugify
-from django.views.generic import RedirectView, TemplateView, UpdateView, View
-from django.views.generic.detail import DetailView
-from django.views.generic.edit import CreateView, DeleteView, FormView, ProcessFormView
-from django.views.generic.list import ListView
-
-import xlsxwriter
-
-from formtools.wizard.views import CookieWizardView
-
-from reversion.views import RevisionMixin
-
-from mesads.fradm.models import EPCI
-
-from .forms import (
- ADSDecreeForm1,
- ADSDecreeForm2,
- ADSDecreeForm3,
- ADSDecreeForm4,
- ADSForm,
- ADSLegalFileFormSet,
- ADSManagerDecreeFormSet,
- ADSManagerEditForm,
- ADSManagerForm,
- ADSSearchForm,
- ADSUserFormSet,
-)
-from .models import (
- ADS,
- ADSLegalFile,
- ADSManager,
- ADSManagerAdministrator,
- ADSManagerRequest,
- ADSUser,
-)
-from .reversion_diff import ModelHistory
-
-
-class HTTP500View(TemplateView):
- """The default HTTP/500 handler can't access to context processors and does
- not have access to the variable MESADS_CONTACT_EMAIL.
- """
-
- template_name = "500.html"
-
- def dispatch(self, request, *args, **kwargs):
- """The base class TemplateView only accepts GET requests. By overriding
- dispatch, we return the error page for any other method."""
- return super().get(request, *args, **kwargs)
-
-
-class HomepageView(TemplateView):
- template_name = "pages/homepage.html"
-
-
-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")
- return reverse("app.ads-manager.index")
-
-
-class ADSManagerAdminView(RevisionMixin, TemplateView):
- 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
- success_url = reverse_lazy("app.ads-manager.index")
-
- def get_context_data(self, **kwargs):
- """Expose the list of ADSManagerAdministrators for which current user
- is configured.
-
- It is also accessible through user.adsmanageradministrator_set.all, but
- we need to prefetch ads_managers__content_object to reduce the number
- of SQL queries generated.
- """
- ctx = super().get_context_data(**kwargs)
- ctx["user_ads_manager_requests"] = (
- ADSManagerRequest.objects.filter(user=self.request.user)
- .annotate(ads_count=Count("ads_manager__ads"))
- .all()
- )
-
- ctx["ads_managers_administrators"] = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(users=self.request.user)
- .all()
- )
- return ctx
-
- def form_valid(self, form):
- _, created = ADSManagerRequest.objects.get_or_create(
- user=self.request.user,
- ads_manager=form.cleaned_data["ads_manager"],
- )
-
- # Request already exists
- if not created:
- messages.warning(
- self.request,
- self.get_message_for_existing_request(form.cleaned_data["ads_manager"]),
- )
- # Send notifications to administrators.
- else:
- messages.success(
- self.request,
- self.get_message_for_new_request(form.cleaned_data["ads_manager"]),
- )
- email_subject = render_to_string(
- "pages/email_ads_manager_request_administrator_subject.txt",
- {
- "user": self.request.user,
- },
- request=self.request,
- ).strip()
- email_content = render_to_string(
- "pages/email_ads_manager_request_administrator_content.txt",
- {
- "request": self.request,
- "ads_manager": form.cleaned_data["ads_manager"],
- "user": self.request.user,
- },
- request=self.request,
- )
- email_content_html = render_to_string(
- "pages/email_ads_manager_request_administrator_content.mjml",
- {
- "request": self.request,
- "ads_manager": form.cleaned_data["ads_manager"],
- "user": self.request.user,
- },
- request=self.request,
- )
-
- if form.cleaned_data["ads_manager"].administrator:
- for administrator_user in form.cleaned_data[
- "ads_manager"
- ].administrator.users.all():
- send_mail(
- email_subject,
- email_content,
- settings.MESADS_CONTACT_EMAIL,
- [administrator_user],
- fail_silently=True,
- html_message=email_content_html,
- )
-
- return super().form_valid(form)
-
- def get_message_for_existing_request(self, ads_manager):
- if not ads_manager.administrator:
- return """
- Vous avez déjà effectué une demande pour gérer les ADS de %(administration)s, et notre équipe va y répondre dans les plus brefs délais.
-
- Si vous n'avez eu aucun retour depuis plusieurs jours, n'hésitez pas à contacter notre équipe par email à %(email)s ou via notre module de tchat.
- """ % {
- "email": settings.MESADS_CONTACT_EMAIL,
- "administration": ads_manager.content_object.display_fulltext(),
- }
- return """
- Vous avez déjà effectué une demande pour gérer les ADS de %(administration)s. Cette demande a été envoyée à %(prefecture)s qui devrait y répondre rapidement.
-
- Si vous n'avez eu aucun retour depuis plusieurs jours, n'hésitez pas à nous signaler le problème par email à %(email)s ou via notre module de tchat.
-
- Nous pourrons alors valider votre demande manuellement.
- """ % {
- "administration": ads_manager.content_object.display_fulltext(),
- "prefecture": ads_manager.administrator.prefecture.display_fulltext(),
- "email": settings.MESADS_CONTACT_EMAIL,
- }
-
- def get_message_for_new_request(self, ads_manager):
- # Request for EPCI or prefectures
- if not ads_manager.administrator:
- return """
- Votre demande vient d’être envoyée à notre équipe. Vous recevrez une confirmation de validation de votre
- accès par mail.
-
- En cas de difficulté ou si vous n’obtenez pas de validation de votre demande vous pouvez contacter par email à
- %(email)s ou via notre module de tchat.
-
- Vous pouvez également demander un accès pour la gestion des ADS d’une autre collectivité.
- """ % {
- "email": settings.MESADS_CONTACT_EMAIL
- }
-
- return """
- Votre demande vient d’être envoyée à %(prefecture)s. Vous recevrez une confirmation de validation de votre
- accès par mail.
-
- En cas de difficulté ou si vous n’obtenez pas de validation de votre demande vous pouvez
- contacter par email à %(email)s ou via notre module de tchat.
-
- Vous pouvez également demander un accès pour la gestion des ADS d’une autre collectivité.
- """ % {
- "prefecture": ads_manager.administrator.prefecture.display_fulltext(),
- "email": settings.MESADS_CONTACT_EMAIL,
- }
-
-
-class ADSManagerView(ListView, ProcessFormView):
- template_name = "pages/ads_register/ads_manager.html"
- model = ADS
- paginate_by = 50
-
- def get(self, request, *args, **kwargs):
- self.search_form = ADSSearchForm(request.GET)
- return ListView.get(self, request, *args, **kwargs)
-
- def get_ads_manager(self):
- return ADSManager.objects.get(id=self.kwargs["manager_id"])
-
- def get_form(self):
- if self.request.method == "POST":
- return ADSManagerEditForm(
- instance=self.get_ads_manager(), data=self.request.POST
- )
- return ADSManagerEditForm(instance=self.get_ads_manager())
-
- def form_valid(self, form):
- form.save()
- return redirect("app.ads-manager.detail", manager_id=self.kwargs["manager_id"])
-
- def form_invalid(self, form):
- return self.get(self.request, *self.args, **self.kwargs)
-
- def get_queryset(self):
- qs = super().get_queryset()
- qs = qs.filter(ads_manager__id=self.kwargs["manager_id"])
-
- if self.search_form.is_valid():
- if self.search_form.cleaned_data["accepted_cpam"] is not None:
- qs = qs.filter(
- accepted_cpam=self.search_form.cleaned_data["accepted_cpam"]
- )
-
- q = self.search_form.cleaned_data["q"]
- if q:
- qs = qs.annotate(
- clean_immatriculation_plate=Replace(
- "immatriculation_plate", Value("-"), Value("")
- )
- )
-
- qs = qs.filter(
- Q(owner_siret__icontains=q)
- | Q(adsuser__name__icontains=q)
- | Q(adsuser__siret__icontains=q)
- | Q(owner_name__icontains=q)
- | Q(clean_immatriculation_plate__icontains=q)
- | Q(epci_commune__libelle__icontains=q)
- | Q(number__icontains=q)
- )
-
- # Add ordering on the number. CAST is necessary in the case the ADS number is not an integer.
- qs_ordered = qs.extra(
- select={
- "ads_number_as_int": "CAST(substring(number FROM '^[0-9]+') AS NUMERIC)"
- }
- )
-
- # First, order by number if it is an integer, then by string.
- return qs_ordered.annotate(c=Count("id")).order_by(
- "ads_number_as_int", "number"
- )
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["search_form"] = self.search_form
-
- # search_defined is a boolean, set to True of any of the search form
- # parameter is defined.
- ctx["search_defined"] = any(
- (v is not None and v != "" for v in self.search_form.cleaned_data.values())
- )
-
- ctx["edit_form"] = self.get_form()
- ctx["ads_manager"] = ctx["edit_form"].instance
- return ctx
-
-
-class ADSView(RevisionMixin, UpdateView):
- template_name = "pages/ads_register/ads.html"
- form_class = ADSForm
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
-
- # If manager_id is the primary key of an EPCI, initialize ADSForm with
- # the parameter "epci" which is required to setup autocompletion for the
- # field ADS.epci_commune. This field is not displayed if the manager is
- # a Prefecture or a Commune.
- ads_manager = get_object_or_404(ADSManager, id=self.kwargs["manager_id"])
- if ads_manager.content_type.model_class() is EPCI:
- kwargs["epci"] = ads_manager.content_object
-
- return kwargs
-
- def get_success_url(self):
- return reverse(
- "app.ads.detail",
- kwargs={
- "manager_id": self.kwargs["manager_id"],
- "ads_id": self.kwargs["ads_id"],
- },
- )
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["ads_manager"] = ADSManager.objects.get(id=self.kwargs["manager_id"])
- ctx["ads_users_formset"] = self.ads_users_formset
- ctx["ads_legal_files_formset"] = self.ads_legal_files_formset
- return ctx
-
- def get_object(self, queryset=None):
- ads = get_object_or_404(ADS, id=self.kwargs["ads_id"])
-
- if self.request.POST and self.request.POST.get(
- ADSUserFormSet().management_form["TOTAL_FORMS"].html_name
- ):
- self.ads_users_formset = ADSUserFormSet(self.request.POST, instance=ads)
- else:
- self.ads_users_formset = ADSUserFormSet(instance=ads)
- # Always display at least a form
- if not ads.adsuser_set.count():
- self.ads_users_formset.extra = 1
-
- if self.request.POST and self.request.POST.get(
- ADSLegalFileFormSet().management_form["TOTAL_FORMS"].html_name
- ):
- self.ads_legal_files_formset = ADSLegalFileFormSet(
- self.request.POST, self.request.FILES, instance=ads
- )
- else:
- self.ads_legal_files_formset = ADSLegalFileFormSet(instance=ads)
-
- return ads
-
- def form_invalid(self, form):
- messages.error(
- self.request,
- "Le formulaire contient des erreurs. Veuillez les corriger avant de soumettre à nouveau.",
- )
- return super().form_invalid(form)
-
- @transaction.atomic
- def form_valid(self, form):
- html_name_ads_users_formset = self.ads_users_formset.management_form[
- "TOTAL_FORMS"
- ].html_name
- if (
- self.request.POST.get(html_name_ads_users_formset) is not None
- and not self.ads_users_formset.is_valid()
- ):
- return self.form_invalid(form)
-
- html_name_ads_legal_files_formset = (
- self.ads_legal_files_formset.management_form["TOTAL_FORMS"].html_name
- )
- if (
- self.request.POST.get(html_name_ads_legal_files_formset) is not None
- and not self.ads_legal_files_formset.is_valid()
- ):
- return self.form_invalid(form)
-
- self.object = form.save(check=False)
- self.ads_users_formset.instance = self.object
- self.ads_legal_files_formset.instance = self.object
-
- if not self.request.POST.get(html_name_ads_users_formset):
- ADSUser.objects.filter(ads=self.object).delete()
- else:
- try:
- with transaction.atomic():
- self.ads_users_formset.save()
- except IntegrityError:
- errmsg = [
- c
- for c in ADSUser._meta.constraints
- if c.name == "only_one_titulaire_exploitant"
- ][0].violation_error_message
- self.ads_users_formset.non_form_errors().append(errmsg)
- resp = self.form_invalid(form)
- # Revert the transaction: we don't want to save the ADS if we can't save the users.
- transaction.set_rollback(True)
- return resp
-
- if not self.request.POST.get(html_name_ads_legal_files_formset):
- ADSLegalFile.objects.filter(ads=self.object).delete()
- else:
- self.ads_legal_files_formset.instance = self.object
- self.ads_legal_files_formset.save()
-
- self.object.run_checks()
-
- messages.success(self.request, "Les modifications ont été enregistrées.")
- return HttpResponseRedirect(self.get_success_url())
-
-
-def ads_manager_decree_view(request, manager_id):
- """Decree limiting the number of ADS for an ADSManager."""
- ads_manager = get_object_or_404(ADSManager, id=manager_id)
-
- if request.method == "POST":
- formset = ADSManagerDecreeFormSet(
- request.POST, request.FILES, instance=ads_manager
- )
- if formset.is_valid():
- formset.save()
- messages.success(request, "Les modifications ont été enregistrées.")
- return redirect("app.ads-manager.decree.detail", manager_id=manager_id)
- else:
- formset = ADSManagerDecreeFormSet(instance=ads_manager)
-
- return render(
- request,
- "pages/ads_register/ads_manager_decree.html",
- context={
- "ads_manager": ads_manager,
- "formset": formset,
- },
- )
-
-
-class ADSDeleteView(DeleteView):
- template_name = "pages/ads_register/ads_confirm_delete.html"
- model = ADS
- pk_url_kwarg = "ads_id"
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["ads_manager"] = ADSManager.objects.get(id=self.kwargs["manager_id"])
- return ctx
-
- def get_success_url(self):
- return reverse(
- "app.ads-manager.detail",
- kwargs={
- "manager_id": self.kwargs["manager_id"],
- },
- )
-
-
-class ADSCreateView(ADSView, CreateView):
- def dispatch(self, request, manager_id):
- """If the ADSManager has the flag no_ads_declared to True, it is
- impossible to create ADS for it."""
- get_object_or_404(ADSManager, id=manager_id, no_ads_declared=False)
-
- html_name_ads_users_formset = (
- ADSUserFormSet().management_form["TOTAL_FORMS"].html_name
- )
- if self.request.POST.get(html_name_ads_users_formset):
- self.ads_users_formset = ADSUserFormSet(self.request.POST)
- else:
- self.ads_users_formset = ADSUserFormSet()
- self.ads_users_formset.extra = 1
-
- html_name_ads_legal_files_formset = (
- ADSLegalFileFormSet().management_form["TOTAL_FORMS"].html_name
- )
- if self.request.POST.get(html_name_ads_legal_files_formset):
- self.ads_legal_files_formset = ADSLegalFileFormSet(
- self.request.POST, self.request.FILES
- )
- else:
- self.ads_legal_files_formset = ADSLegalFileFormSet()
- return super().dispatch(request, manager_id)
-
- def get_object(self, queryset=None):
- return None
-
- def get_success_url(self):
- return reverse(
- "app.ads.detail",
- kwargs={"manager_id": self.kwargs["manager_id"], "ads_id": self.object.id},
- )
-
- def form_valid(self, form):
- ads_manager = ADSManager.objects.get(id=self.kwargs["manager_id"])
- form.instance.ads_manager = ads_manager
-
- # CreateView doesn't call validate_constraints(). The try/catch below
- # attemps to save the object. If IntegrityError is returned from
- # database, we return a custom error message for "number".
- try:
- with transaction.atomic():
- return super().form_valid(form)
- except IntegrityError:
- form.add_error("number", ADS.UNIQUE_ERROR_MSG)
- return super().form_invalid(form)
-
-
-class ADSExporter:
- """Generic class to export a list of ADS in an Excel file."""
-
- def get_filename(self):
- raise NotImplementedError
-
- def get_queryset(self):
- return (
- ADS.objects.select_related(
- "ads_manager__administrator__prefecture",
- )
- .prefetch_related(
- "ads_manager__content_object",
- )
- .annotate(
- ads_users_status=ArrayAgg("adsuser__status"),
- ads_users_names=ArrayAgg("adsuser__name"),
- ads_users_sirets=ArrayAgg("adsuser__siret"),
- ads_users_licenses=ArrayAgg("adsuser__license_number"),
- )
- )
-
- def display_bool(self, value):
- if value is None:
- return ""
- return "oui" if value else "non"
-
- def display_date(self, value):
- if not value:
- return ""
- return value.strftime("%d/%m/%Y")
-
- def generate(self):
- response = HttpResponse(
- content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- headers={
- "Content-Disposition": f'attachment; filename="{self.get_filename()}"'
- },
- )
- workbook = xlsxwriter.Workbook(response)
- self.add_sheets(workbook)
- workbook.close()
- return response
-
- def add_sheets(self, workbook):
- """Override this method to add more sheets to the workbook."""
- self.ads_list_sheet(workbook)
-
- def ads_list_sheet(self, workbook):
- bold_format = workbook.add_format({"bold": True})
- sheet = workbook.add_worksheet("ADS enregistrées")
- headers = (
- "Type d'administration",
- "Administration",
- "Numéro de l'ADS",
- "ADS actuellement exploitée ?",
- "Date de création de l'ADS",
- "Date du dernier renouvellement de l'ADS",
- "Date d'attribution de l'ADS au titulaire actuel",
- "Véhicule conventionné CPAM ?",
- "Plaque d'immatriculation du véhicule",
- "Le véhicule est-il un véhicule électrique/hybride ?",
- "Véhicule compatible PMR ?",
- "Titulaire de l'ADS",
- "SIRET du titulaire de l'ADS",
- "Téléphone fixe du titulaire de l'ADS",
- "Téléphone mobile du titulaire de l'ADS",
- "Email du titulaire de l'ADS",
- )
- # If one of the ADS in the list has, let's say, 4 drivers, driver_headers
- # will be appended 4 times to headers.
- driver_headers = (
- "Statut du %s conducteur",
- "Nom du %s conducteur",
- "SIRET du %s conducteur",
- "Numéro de la carte professionnelle du %s conducteur",
- )
- # Counts the maximum number of drivers in the list of ADS..
- max_drivers = 0
-
- # Applying bold format to headers
- sheet.set_row(0, None, bold_format)
-
- for idx, ads in enumerate(self.get_queryset()):
- # Append driver headers to headers if the current ADS has more drivers
- # than the previous ones.
- while max_drivers < len(ads.ads_users_status):
- for h in driver_headers:
- headers += (
- h % ("1er" if max_drivers == 0 else "%se" % (max_drivers + 1)),
- )
- max_drivers += 1
-
- info = (
- ads.ads_manager.content_object.type_name(),
- ads.ads_manager.content_object.text(),
- ads.number,
- self.display_bool(ads.ads_in_use),
- self.display_date(ads.ads_creation_date),
- self.display_date(ads.ads_renew_date),
- self.display_date(ads.attribution_date),
- self.display_bool(ads.accepted_cpam),
- ads.immatriculation_plate,
- self.display_bool(ads.eco_vehicle),
- self.display_bool(ads.vehicle_compatible_pmr),
- ads.owner_name,
- ads.owner_siret,
- ads.owner_phone,
- ads.owner_mobile,
- ads.owner_email,
- )
- for nth, status in enumerate(ads.ads_users_status):
- # ads_users_status, ads_users_names, ads_users_sirets and
- # ads_users_licenses have the same length.
- info += (
- dict(ADSUser.status.field.choices).get(
- ads.ads_users_status[nth], ""
- ),
- ads.ads_users_names[nth],
- ads.ads_users_sirets[nth],
- ads.ads_users_licenses[nth],
- )
- sheet.write_row(idx + 1, 0, info)
-
- # Write headers, now that we know the maximum number of drivers.
- sheet.write_row(0, 0, headers)
- sheet.autofit()
-
-
-class ADSManagerExportView(View, ADSExporter):
- def get(self, request, manager_id):
- self.ads_manager = get_object_or_404(ADSManager, id=manager_id)
- return self.generate()
-
- def get_filename(self):
- administration = self.ads_manager.content_object.display_text()
- return slugify(f"ADS {administration}") + ".xlsx"
-
- def get_queryset(self):
- qs = super().get_queryset()
- return qs.filter(ads_manager=self.ads_manager)
-
-
-class PrefectureExportView(View, ADSExporter):
- def get(self, request, ads_manager_administrator):
- self.ads_manager_administrator = ads_manager_administrator
- return self.generate()
-
- def get_filename(self):
- return f"ADS_prefecture_{self.ads_manager_administrator.prefecture.numero}.xlsx"
-
- def get_queryset(self):
- qs = super().get_queryset()
- return qs.filter(ads_manager__administrator=self.ads_manager_administrator)
-
- def add_sheets(self, workbook):
- super().add_sheets(workbook)
- sheet = workbook.add_worksheet("Gestionnaires ADS")
- sheet.write_row(
- 0,
- 0,
- (
- "Nom de l'administration",
- "Nombre d'ADS",
- "Statut de la gestion des ADS",
- ),
- )
- # Applying bold format to headers
- bold_format = workbook.add_format({"bold": True})
- sheet.set_row(0, None, bold_format)
-
- for idx, ads_manager in enumerate(
- self.ads_manager_administrator.adsmanager_set.all()
- ):
- status = ""
- if ads_manager.no_ads_declared:
- status = "L'administration a déclaré ne gérer aucune ADS"
- elif ads_manager.epci_delegate:
- status = (
- "La gestion des ADS est déléguée à %s"
- % ads_manager.epci_delegate.display_fulltext()
- )
-
- sheet.write_row(
- idx + 1,
- 0,
- (
- ads_manager.content_object.display_text(),
- ads_manager.ads_set.count() or "",
- status,
- ),
- )
- sheet.autofit()
-
-
-class DashboardsView(TemplateView):
- template_name = "pages/ads_register/dashboards_list.html"
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["stats"], ctx["stats_total"] = self.get_stats()
- return ctx
-
- def get_stats(self):
- """This function returns a tuple of two values:
-
- * stats: a list of dictionaries containing the following keys:
- - obj: the ADSManagerAdministrator instance
- - ads: a dictionary where keys represent the period (now, 3 months
- ago, 6 months ago, 12 months ago), and values are the number of ADS
- for this ADSManagerAdministrator
- - users: a dictionary where keys represent the period (now, 3 months
- ago, 6 months ago, 12 months ago), and values are the number of
- accounts who can create ADS for this ADSManagerAdministrator
-
- >>> [
- ... obj:
- ... 'ads': {
- ... 'now':
- ... '3_months':
- ... '6_months':
- ... '12_months':
- ... },
- ... 'users': {
- ... 'now':
- ... '3_months':
- ... '6_months':
- ... '12_months':
- ... }
- ... ]
-
- * stats_total: a dictionary containing the keys 'ads' and 'users', and
- the values are dictionaries where keys represent the period, and
- values are the total number of ADS and users.
-
- >>> {
- ... 'ads': {
- ... 'now': ,
- ... '3_months': ,
- ... '6_months': ,
- ... '12_months': ,
- ... },
- ... 'users': { ... }
- ... }
- """
- now = timezone.now()
-
- stats = collections.defaultdict(lambda: {"obj": None, "ads": {}, "users": {}})
-
- stats_total = {
- "ads": {},
- "users": {},
- }
-
- ads_query_now = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .annotate(ads_count=Count("adsmanager__ads"))
- .filter(ads_count__gt=0)
- )
-
- # All ADSManagerAdministrator, with the count of ADS with at least one of the contact fields filled.
- ads_with_info_query_now = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .annotate(
- ads_count=Count(
- "adsmanager__ads",
- filter=~Q(adsmanager__ads__owner_email="")
- | ~Q(adsmanager__ads__owner_mobile="")
- | ~Q(adsmanager__ads__owner_phone=""),
- )
- )
- .filter(ads_count__gt=0)
- )
-
- ads_query_3_months = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(adsmanager__ads__creation_date__lte=now - timedelta(weeks=4 * 3))
- .annotate(ads_count=Count("adsmanager__ads"))
- )
-
- ads_query_6_months = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(adsmanager__ads__creation_date__lte=now - timedelta(weeks=4 * 6))
- .annotate(ads_count=Count("adsmanager__ads"))
- )
-
- ads_query_12_months = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(adsmanager__ads__creation_date__lte=now - timedelta(weeks=4 * 12))
- .annotate(ads_count=Count("adsmanager__ads"))
- )
-
- for label, query in (
- ("now", ads_query_now),
- ("with_info_now", ads_with_info_query_now),
- ("3_months", ads_query_3_months),
- ("6_months", ads_query_6_months),
- ("12_months", ads_query_12_months),
- ):
- for row in query:
- stats[row.prefecture.id]["obj"] = row
- stats[row.prefecture.id]["ads"][label] = row.ads_count
-
- stats_total["ads"][label] = query.aggregate(
- total=Coalesce(Sum("ads_count"), 0)
- )["total"]
-
- users_query_now = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(adsmanager__adsmanagerrequest__accepted=True)
- .annotate(users_count=Count("id"))
- )
-
- users_query_3_months = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(
- adsmanager__adsmanagerrequest__accepted=True,
- adsmanager__adsmanagerrequest__created_at__lte=now
- - timedelta(weeks=4 * 3),
- )
- .annotate(users_count=Count("id"))
- )
-
- users_query_6_months = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(
- adsmanager__adsmanagerrequest__accepted=True,
- adsmanager__adsmanagerrequest__created_at__lte=now
- - timedelta(weeks=4 * 6),
- )
- .annotate(users_count=Count("id"))
- )
-
- users_query_12_months = (
- ADSManagerAdministrator.objects.select_related("prefecture")
- .filter(
- adsmanager__adsmanagerrequest__accepted=True,
- adsmanager__adsmanagerrequest__created_at__lte=now
- - timedelta(weeks=4 * 12),
- )
- .annotate(users_count=Count("id"))
- )
-
- for label, query in (
- ("now", users_query_now),
- ("3_months", users_query_3_months),
- ("6_months", users_query_6_months),
- ("12_months", users_query_12_months),
- ):
- for row in query.all():
- stats[row.prefecture.id]["obj"] = row
- stats[row.prefecture.id]["users"][label] = row.users_count
-
- stats_total["users"][label] = query.aggregate(
- total=Coalesce(Sum("users_count"), 0)
- )["total"]
-
- return (
- # Transform dict to an ordered list
- sorted(list(stats.values()), key=lambda stat: stat["obj"].id),
- stats_total,
- )
-
-
-class DashboardsDetailView(DetailView):
- template_name = "pages/ads_register/dashboards_detail.html"
- model = ADSManagerAdministrator
- pk_url_kwarg = "ads_manager_administrator_id"
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["stats"] = self.get_stats()
- return ctx
-
- def get_stats(self):
- stats = {}
-
- stats = collections.defaultdict(lambda: {"obj": None, "ads": {}, "users": {}})
-
- now = timezone.now()
-
- ads_query_now = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(administrator=self.object)
- .annotate(ads_count=Count("ads"))
- .filter(ads_count__gt=0)
- )
-
- # All ADSManager, with the count of ADS with at least one of the contact fields filled.
- ads_with_info_query_now = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(administrator=self.object)
- .annotate(
- ads_count=Count(
- "ads",
- filter=~Q(ads__owner_email="")
- | ~Q(ads__owner_mobile="")
- | ~Q(ads__owner_phone=""),
- )
- )
- .filter(ads_count__gt=0)
- )
-
- ads_query_3_months = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(
- administrator=self.object,
- ads__creation_date__lte=now - timedelta(weeks=4 * 3),
- )
- .annotate(ads_count=Count("ads"))
- )
-
- ads_query_6_months = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(
- administrator=self.object,
- ads__creation_date__lte=now - timedelta(weeks=4 * 6),
- )
- .annotate(ads_count=Count("ads"))
- )
-
- ads_query_12_months = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(
- administrator=self.object,
- ads__creation_date__lte=now - timedelta(weeks=4 * 12),
- )
- .annotate(ads_count=Count("ads"))
- )
-
- for label, query in (
- ("now", ads_query_now),
- ("with_info_now", ads_with_info_query_now),
- ("3_months", ads_query_3_months),
- ("6_months", ads_query_6_months),
- ("12_months", ads_query_12_months),
- ):
- for row in query:
- stats[row.id]["obj"] = row
- stats[row.id]["ads"][label] = row.ads_count
-
- users_query_now = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(administrator=self.object, adsmanagerrequest__accepted=True)
- .annotate(users_count=Count("id"))
- )
-
- users_query_3_months = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(
- administrator=self.object,
- adsmanagerrequest__accepted=True,
- adsmanagerrequest__created_at__lte=now - timedelta(weeks=4 * 3),
- )
- .annotate(users_count=Count("id"))
- )
-
- users_query_6_months = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(
- administrator=self.object,
- adsmanagerrequest__accepted=True,
- adsmanagerrequest__created_at__lte=now - timedelta(weeks=4 * 6),
- )
- .annotate(users_count=Count("id"))
- )
-
- users_query_12_months = (
- ADSManager.objects.prefetch_related("content_type", "content_object")
- .filter(
- administrator=self.object,
- adsmanagerrequest__accepted=True,
- adsmanagerrequest__created_at__lte=now - timedelta(weeks=4 * 12),
- )
- .annotate(users_count=Count("id"))
- )
-
- for label, query in (
- ("now", users_query_now),
- ("3_months", users_query_3_months),
- ("6_months", users_query_6_months),
- ("12_months", users_query_12_months),
- ):
- for row in query.all():
- stats[row.id]["obj"] = row
- stats[row.id]["users"][label] = row.users_count
-
- return sorted(list(stats.values()), key=lambda stat: stat["obj"].id)
-
-
-class FAQView(TemplateView):
- template_name = "pages/faq.html"
-
-
-class CustomCookieWizardView(CookieWizardView):
- def get_prefix(self, request, *args, **kwargs):
- """By default, WizardView uses the class name as a prefix. If the user
- opens several tabs at the same time, the storage is shared and weird
- behavior can happen.
-
- For example:
-
- * tab 1: from the page to create a decree for an ADS, go to second step
- * tab 2: from the page to create a decree for another ADS, go to second step
- * tab 2: refresh the page to go back to first step
- * tab 1: go to third step
-
- If the prefix is shared, an error will be raised because the form data
- have been deleted when the user refreshed the page in tab 2.
-
- We append the URL parameters to the prefix to avoid this issue most of
- the time. It is not perfect, but it is better than nothing. If the two
- tabs edit the same object, the prefix will be the same and the issue
- will still happen.
- """
- prefix = super().get_prefix(request, *args, **kwargs)
- suffix = "_".join(str(kwargs[key]) for key in sorted(kwargs.keys()))
- return f"{prefix}_{suffix}"
-
- def render_next_step(self, form, **kwargs):
- """The base class implementation of render_next_step has a bug, with the following scenario.
-
- Imagine a wizard with 3 steps.
-
- 1. The user is at step 1, selects a field from a list, then goes to step 2
- 2. The step 2 renders a select field with choices computed from the data
- of step 1. The user selects a value, and goes to step 3.
- 3. The user goes back to step 2, then goes back to step 1.
- 4. Finally, the user selects another value than the first time, and goes
- to step 2.
-
- Since the choices of the select field are computed from the data of step
- 1, the choice previously select and stored refers to an invalid choice.
-
- To fix this issue, we delete the stored data of the next step before
- going to it.
- """
- if self.steps.next in self.storage.data[self.storage.step_data_key]:
- del self.storage.data[self.storage.step_data_key][self.steps.next]
- return super().render_next_step(form, **kwargs)
-
- def render_done(self, form, **kwargs):
- """The custom method render_done is called at the final step of the
- wizard. The base class resets the storage, which prevents to edit the
- form to regenerate the decree. We override the method to prevent the
- storage from being reset."""
- storage_reset = self.storage.reset
- self.storage.reset = lambda: None
- resp = super().render_done(form, **kwargs)
- self.storage.reset = storage_reset
- return resp
-
-
-class ADSHistoryView(DetailView):
- template_name = "pages/ads_register/ads_history.html"
- model = ADS
- pk_url_kwarg = "ads_id"
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["history"] = ModelHistory(
- self.object,
- ignore_fields=[
- ADS._meta.get_field("ads_manager"),
- ADS._meta.get_field("creation_date"),
- ADS._meta.get_field("last_update"),
- ADSLegalFile._meta.get_field("ads"),
- ADSLegalFile._meta.get_field("creation_date"),
- ADSUser._meta.get_field("ads"),
- ],
- )
- return ctx
-
-
-class ADSDecreeView(CustomCookieWizardView):
- """Decree for ADS creation."""
-
- template_name = "pages/ads_register/ads_decree.html"
- form_list = (
- ADSDecreeForm1,
- ADSDecreeForm2,
- ADSDecreeForm3,
- ADSDecreeForm4,
- )
-
- def get_form_kwargs(self, step=None):
- """Instantiate ADSDecreeForm1 with the value of the previous form, to
- set the correct choices of the select field."""
- ret = super().get_form_kwargs(step=step)
- if step in ("1", "2"):
- return {"is_old_ads": self.get_cleaned_data_for_step("0").get("is_old_ads")}
- return ret
-
- def get_form_initial(self, step):
- """Set fields defaults."""
- ret = super().get_form_initial(step)
- ads = self.get_ads()
-
- if step == "0":
- ret.update(
- {
- "is_old_ads": ads.ads_creation_date
- and ads.ads_creation_date <= date(2014, 10, 1),
- }
- )
- elif step == "2":
- ads_user = ads.adsuser_set.first()
-
- now = datetime.now()
- try:
- today_in_5_years = now.replace(year=now.year + 5)
- except ValueError: # 29th February
- today_in_5_years = now + timedelta(days=365 * 5)
-
- ret.update(
- {
- "decree_creation_date": now.strftime("%Y-%m-%d"),
- "decree_commune": ads.ads_manager.content_object.libelle,
- "ads_owner": ads.owner_name,
- # By default, we only display the first ADSUser. If there
- # are more, user can edit the .docx generated manually.
- "tenant_ads_user": ads_user.name if ads_user else "",
- # New ADS have a validity of 5 years
- "ads_end_date": today_in_5_years.strftime("%Y-%m-%d"),
- "ads_number": ads.number,
- "immatriculation_plate": ads.immatriculation_plate,
- }
- )
- return ret
-
- def get_ads(self):
- return get_object_or_404(
- ADS, id=self.kwargs["ads_id"], ads_manager_id=self.kwargs["manager_id"]
- )
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["ads"] = self.get_ads()
- return ctx
-
- def done(self, form_list, **kwargs):
- path = finders.find("template-arrete-municipal.docx")
- decree = DocxTemplate(path)
-
- cleaned_data = self.get_all_cleaned_data()
-
- # DocxTemplate uses jinja2 to render the template. To render dates, we
- # could use {{ date.strftime(...)}} but the month would be in English.
- # Use the django date template filter to use correct format.
- cleaned_data.update(
- {
- k + "_str": date_template_filter(v, "d F Y")
- for k, v in cleaned_data.items()
- if isinstance(v, date)
- }
- )
-
- # Prefix the commune name with "la commune d'" or "la commune de "
- decree_commune = cleaned_data["decree_commune"]
- cleaned_data["decree_commune_fulltext"] = (
- "d'%s" % decree_commune
- if decree_commune[:1] in ("aeiouy")
- else "de %s" % decree_commune
- )
-
- decree.render(cleaned_data)
-
- response = HttpResponse(
- content_type="application/vnd.openxmlformats",
- headers={"Content-Disposition": 'attachment; filename="decret.docx"'},
- )
-
- decree.save(response)
- return response
-
-
-class StatsView(TemplateView):
- template_name = "pages/stats.html"
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
-
- ctx["ads_count"] = ADS.objects.count()
-
- ads_count_by_month = (
- ADS.objects.annotate(month=TruncMonth("creation_date"))
- .values("month")
- .annotate(count=Count("id"))
- .order_by("month")
- )
- ctx["ads_count_by_month"] = json.dumps(
- dict(
- ((row["month"].isoformat(), row["count"]) for row in ads_count_by_month)
- )
- )
-
- ctx["ads_manager_requests_count"] = ADSManagerRequest.objects.filter(
- accepted=True
- ).count()
-
- ads_manager_requests_by_month = (
- ADSManagerRequest.objects.filter(accepted=True)
- .annotate(month=TruncMonth("created_at"))
- .values("month")
- .annotate(count=Count("id"))
- .order_by("month")
- )
- ctx["ads_manager_requests_by_month"] = json.dumps(
- dict(
- (
- (row["month"].isoformat(), row["count"])
- for row in ads_manager_requests_by_month
- )
- )
- )
-
- ctx["ads_managers_count"] = (
- ADSManager.objects.annotate(ads_count=Count("ads"))
- .filter(ads_count__gt=0)
- .count()
- )
-
- return ctx
-
-
-class ReglementationView(TemplateView):
- template_name = "pages/reglementation.html"
- entries = [
- {
- "title": "Principes généraux",
- "articles": [
- {
- "title": "Le rôle des collectivités",
- "template": "pages/reglementation/principes_generaux/role_collectivites.html",
- },
- {
- "title": "Qu'est-ce qu'une ADS ?",
- "template": "pages/reglementation/principes_generaux/qu_est_ce_qu_une_ads.html",
- },
- {
- "title": "Qui délivre les ADS ?",
- "template": "pages/reglementation/principes_generaux/qui_delivre_ads.html",
- },
- ],
- },
- {
- "title": "Délivrance d'une ADS",
- "articles": [
- {
- "menu_title": "Arrêté délimitant le nombre d'ADS",
- "title": "Étape 1 : l'arrêté délimitant le nombre d'ADS",
- "template": "pages/reglementation/delivrance_ads/arrete_delimitant_ads.html",
- },
- {
- "menu_title": "Attribution de l'ADS",
- "title": "Étape 2 : l'attribution de l'ADS",
- "template": "pages/reglementation/delivrance_ads/attribution_ads.html",
- },
- {
- "menu_title": "L'arrêté municipal",
- "title": "Étape 3 : la notification de l'arrêté",
- "template": "pages/reglementation/delivrance_ads/notification_arrete.html",
- },
- {
- "menu_title": "Retrait d'une ADS",
- "title": "Étape 4 : le retrait d'une ADS",
- "template": "pages/reglementation/delivrance_ads/retrait_ads.html",
- },
- ],
- },
- {
- "title": "Registre des taxis relais",
- "articles": [
- {
- "title": "Qu'est-ce qu'un taxi relais ?",
- "template": "pages/reglementation/relais/definition.html",
- },
- ],
- },
- ]
-
- def get_context_data(self, **kwargs):
- ctx = super().get_context_data(**kwargs)
- ctx["entries"] = self.entries
- return ctx
diff --git a/mesads/app/views/__init__.py b/mesads/app/views/__init__.py
new file mode 100644
index 0000000..f1e08ca
--- /dev/null
+++ b/mesads/app/views/__init__.py
@@ -0,0 +1,35 @@
+from django.urls import reverse
+from django.views.generic import RedirectView
+
+from .ads import ( # noqa: F401
+ ADSDecreeView,
+ ADSHistoryView,
+ ADSCreateView,
+ ADSDeleteView,
+ ADSView,
+)
+from .ads_manager import ADSManagerView, ads_manager_decree_view # noqa: F401
+from .ads_manager_admin import PrefectureExportView, ADSManagerExportView # noqa: F401
+from .ads_manager_request import ( # noqa: F401
+ ADSManagerRequestView,
+ ADSManagerAdminView,
+)
+from .dashboards import DashboardsView, DashboardsDetailView # noqa: F401
+from .public import ( # noqa: F401
+ FAQView,
+ StatsView,
+ ReglementationView,
+ HTTP500View,
+ HomepageView,
+)
+
+
+class ADSRegisterView(RedirectView):
+ """Redirect to the appropriate dashboard depending on the user's role."""
+
+ 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")
+ return reverse("app.ads-manager.index")
diff --git a/mesads/app/views/ads.py b/mesads/app/views/ads.py
new file mode 100644
index 0000000..904e6e3
--- /dev/null
+++ b/mesads/app/views/ads.py
@@ -0,0 +1,406 @@
+from datetime import date, datetime, timedelta
+
+from docxtpl import DocxTemplate
+
+from django.contrib import messages
+from django.contrib.staticfiles import finders
+from django.db import IntegrityError, transaction
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import get_object_or_404
+from django.template.defaultfilters import date as date_template_filter
+from django.urls import reverse
+from django.views.generic import UpdateView
+from django.views.generic.detail import DetailView
+from django.views.generic.edit import CreateView, DeleteView
+
+from formtools.wizard.views import CookieWizardView
+
+from reversion.views import RevisionMixin
+
+from mesads.fradm.models import EPCI
+
+from ..forms import (
+ ADSDecreeForm1,
+ ADSDecreeForm2,
+ ADSDecreeForm3,
+ ADSDecreeForm4,
+ ADSForm,
+ ADSLegalFileFormSet,
+ ADSUserFormSet,
+)
+from ..models import (
+ ADS,
+ ADSLegalFile,
+ ADSManager,
+ ADSUser,
+)
+from ..reversion_diff import ModelHistory
+
+
+class ADSView(RevisionMixin, UpdateView):
+ template_name = "pages/ads_register/ads.html"
+ form_class = ADSForm
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+
+ # If manager_id is the primary key of an EPCI, initialize ADSForm with
+ # the parameter "epci" which is required to setup autocompletion for the
+ # field ADS.epci_commune. This field is not displayed if the manager is
+ # a Prefecture or a Commune.
+ ads_manager = get_object_or_404(ADSManager, id=self.kwargs["manager_id"])
+ if ads_manager.content_type.model_class() is EPCI:
+ kwargs["epci"] = ads_manager.content_object
+
+ return kwargs
+
+ def get_success_url(self):
+ return reverse(
+ "app.ads.detail",
+ kwargs={
+ "manager_id": self.kwargs["manager_id"],
+ "ads_id": self.kwargs["ads_id"],
+ },
+ )
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["ads_manager"] = ADSManager.objects.get(id=self.kwargs["manager_id"])
+ ctx["ads_users_formset"] = self.ads_users_formset
+ ctx["ads_legal_files_formset"] = self.ads_legal_files_formset
+ return ctx
+
+ def get_object(self, queryset=None):
+ ads = get_object_or_404(ADS, id=self.kwargs["ads_id"])
+
+ if self.request.POST and self.request.POST.get(
+ ADSUserFormSet().management_form["TOTAL_FORMS"].html_name
+ ):
+ self.ads_users_formset = ADSUserFormSet(self.request.POST, instance=ads)
+ else:
+ self.ads_users_formset = ADSUserFormSet(instance=ads)
+ # Always display at least a form
+ if not ads.adsuser_set.count():
+ self.ads_users_formset.extra = 1
+
+ if self.request.POST and self.request.POST.get(
+ ADSLegalFileFormSet().management_form["TOTAL_FORMS"].html_name
+ ):
+ self.ads_legal_files_formset = ADSLegalFileFormSet(
+ self.request.POST, self.request.FILES, instance=ads
+ )
+ else:
+ self.ads_legal_files_formset = ADSLegalFileFormSet(instance=ads)
+
+ return ads
+
+ def form_invalid(self, form):
+ messages.error(
+ self.request,
+ "Le formulaire contient des erreurs. Veuillez les corriger avant de soumettre à nouveau.",
+ )
+ return super().form_invalid(form)
+
+ @transaction.atomic
+ def form_valid(self, form):
+ html_name_ads_users_formset = self.ads_users_formset.management_form[
+ "TOTAL_FORMS"
+ ].html_name
+ if (
+ self.request.POST.get(html_name_ads_users_formset) is not None
+ and not self.ads_users_formset.is_valid()
+ ):
+ return self.form_invalid(form)
+
+ html_name_ads_legal_files_formset = (
+ self.ads_legal_files_formset.management_form["TOTAL_FORMS"].html_name
+ )
+ if (
+ self.request.POST.get(html_name_ads_legal_files_formset) is not None
+ and not self.ads_legal_files_formset.is_valid()
+ ):
+ return self.form_invalid(form)
+
+ self.object = form.save(check=False)
+ self.ads_users_formset.instance = self.object
+ self.ads_legal_files_formset.instance = self.object
+
+ if not self.request.POST.get(html_name_ads_users_formset):
+ ADSUser.objects.filter(ads=self.object).delete()
+ else:
+ try:
+ with transaction.atomic():
+ self.ads_users_formset.save()
+ except IntegrityError:
+ errmsg = [
+ c
+ for c in ADSUser._meta.constraints
+ if c.name == "only_one_titulaire_exploitant"
+ ][0].violation_error_message
+ self.ads_users_formset.non_form_errors().append(errmsg)
+ resp = self.form_invalid(form)
+ # Revert the transaction: we don't want to save the ADS if we can't save the users.
+ transaction.set_rollback(True)
+ return resp
+
+ if not self.request.POST.get(html_name_ads_legal_files_formset):
+ ADSLegalFile.objects.filter(ads=self.object).delete()
+ else:
+ self.ads_legal_files_formset.instance = self.object
+ self.ads_legal_files_formset.save()
+
+ self.object.run_checks()
+
+ messages.success(self.request, "Les modifications ont été enregistrées.")
+ return HttpResponseRedirect(self.get_success_url())
+
+
+class ADSDeleteView(DeleteView):
+ template_name = "pages/ads_register/ads_confirm_delete.html"
+ model = ADS
+ pk_url_kwarg = "ads_id"
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["ads_manager"] = ADSManager.objects.get(id=self.kwargs["manager_id"])
+ return ctx
+
+ def get_success_url(self):
+ return reverse(
+ "app.ads-manager.detail",
+ kwargs={
+ "manager_id": self.kwargs["manager_id"],
+ },
+ )
+
+
+class ADSCreateView(ADSView, CreateView):
+ def dispatch(self, request, manager_id):
+ """If the ADSManager has the flag no_ads_declared to True, it is
+ impossible to create ADS for it."""
+ get_object_or_404(ADSManager, id=manager_id, no_ads_declared=False)
+
+ html_name_ads_users_formset = (
+ ADSUserFormSet().management_form["TOTAL_FORMS"].html_name
+ )
+ if self.request.POST.get(html_name_ads_users_formset):
+ self.ads_users_formset = ADSUserFormSet(self.request.POST)
+ else:
+ self.ads_users_formset = ADSUserFormSet()
+ self.ads_users_formset.extra = 1
+
+ html_name_ads_legal_files_formset = (
+ ADSLegalFileFormSet().management_form["TOTAL_FORMS"].html_name
+ )
+ if self.request.POST.get(html_name_ads_legal_files_formset):
+ self.ads_legal_files_formset = ADSLegalFileFormSet(
+ self.request.POST, self.request.FILES
+ )
+ else:
+ self.ads_legal_files_formset = ADSLegalFileFormSet()
+ return super().dispatch(request, manager_id)
+
+ def get_object(self, queryset=None):
+ return None
+
+ def get_success_url(self):
+ return reverse(
+ "app.ads.detail",
+ kwargs={"manager_id": self.kwargs["manager_id"], "ads_id": self.object.id},
+ )
+
+ def form_valid(self, form):
+ ads_manager = ADSManager.objects.get(id=self.kwargs["manager_id"])
+ form.instance.ads_manager = ads_manager
+
+ # CreateView doesn't call validate_constraints(). The try/catch below
+ # attemps to save the object. If IntegrityError is returned from
+ # database, we return a custom error message for "number".
+ try:
+ with transaction.atomic():
+ return super().form_valid(form)
+ except IntegrityError:
+ form.add_error("number", ADS.UNIQUE_ERROR_MSG)
+ return super().form_invalid(form)
+
+
+class CustomCookieWizardView(CookieWizardView):
+ def get_prefix(self, request, *args, **kwargs):
+ """By default, WizardView uses the class name as a prefix. If the user
+ opens several tabs at the same time, the storage is shared and weird
+ behavior can happen.
+
+ For example:
+
+ * tab 1: from the page to create a decree for an ADS, go to second step
+ * tab 2: from the page to create a decree for another ADS, go to second step
+ * tab 2: refresh the page to go back to first step
+ * tab 1: go to third step
+
+ If the prefix is shared, an error will be raised because the form data
+ have been deleted when the user refreshed the page in tab 2.
+
+ We append the URL parameters to the prefix to avoid this issue most of
+ the time. It is not perfect, but it is better than nothing. If the two
+ tabs edit the same object, the prefix will be the same and the issue
+ will still happen.
+ """
+ prefix = super().get_prefix(request, *args, **kwargs)
+ suffix = "_".join(str(kwargs[key]) for key in sorted(kwargs.keys()))
+ return f"{prefix}_{suffix}"
+
+ def render_next_step(self, form, **kwargs):
+ """The base class implementation of render_next_step has a bug, with the following scenario.
+
+ Imagine a wizard with 3 steps.
+
+ 1. The user is at step 1, selects a field from a list, then goes to step 2
+ 2. The step 2 renders a select field with choices computed from the data
+ of step 1. The user selects a value, and goes to step 3.
+ 3. The user goes back to step 2, then goes back to step 1.
+ 4. Finally, the user selects another value than the first time, and goes
+ to step 2.
+
+ Since the choices of the select field are computed from the data of step
+ 1, the choice previously select and stored refers to an invalid choice.
+
+ To fix this issue, we delete the stored data of the next step before
+ going to it.
+ """
+ if self.steps.next in self.storage.data[self.storage.step_data_key]:
+ del self.storage.data[self.storage.step_data_key][self.steps.next]
+ return super().render_next_step(form, **kwargs)
+
+ def render_done(self, form, **kwargs):
+ """The custom method render_done is called at the final step of the
+ wizard. The base class resets the storage, which prevents to edit the
+ form to regenerate the decree. We override the method to prevent the
+ storage from being reset."""
+ storage_reset = self.storage.reset
+ self.storage.reset = lambda: None
+ resp = super().render_done(form, **kwargs)
+ self.storage.reset = storage_reset
+ return resp
+
+
+class ADSDecreeView(CustomCookieWizardView):
+ """Decree for ADS creation."""
+
+ template_name = "pages/ads_register/ads_decree.html"
+ form_list = (
+ ADSDecreeForm1,
+ ADSDecreeForm2,
+ ADSDecreeForm3,
+ ADSDecreeForm4,
+ )
+
+ def get_form_kwargs(self, step=None):
+ """Instantiate ADSDecreeForm1 with the value of the previous form, to
+ set the correct choices of the select field."""
+ ret = super().get_form_kwargs(step=step)
+ if step in ("1", "2"):
+ return {"is_old_ads": self.get_cleaned_data_for_step("0").get("is_old_ads")}
+ return ret
+
+ def get_form_initial(self, step):
+ """Set fields defaults."""
+ ret = super().get_form_initial(step)
+ ads = self.get_ads()
+
+ if step == "0":
+ ret.update(
+ {
+ "is_old_ads": ads.ads_creation_date
+ and ads.ads_creation_date <= date(2014, 10, 1),
+ }
+ )
+ elif step == "2":
+ ads_user = ads.adsuser_set.first()
+
+ now = datetime.now()
+ try:
+ today_in_5_years = now.replace(year=now.year + 5)
+ except ValueError: # 29th February
+ today_in_5_years = now + timedelta(days=365 * 5)
+
+ ret.update(
+ {
+ "decree_creation_date": now.strftime("%Y-%m-%d"),
+ "decree_commune": ads.ads_manager.content_object.libelle,
+ "ads_owner": ads.owner_name,
+ # By default, we only display the first ADSUser. If there
+ # are more, user can edit the .docx generated manually.
+ "tenant_ads_user": ads_user.name if ads_user else "",
+ # New ADS have a validity of 5 years
+ "ads_end_date": today_in_5_years.strftime("%Y-%m-%d"),
+ "ads_number": ads.number,
+ "immatriculation_plate": ads.immatriculation_plate,
+ }
+ )
+ return ret
+
+ def get_ads(self):
+ return get_object_or_404(
+ ADS, id=self.kwargs["ads_id"], ads_manager_id=self.kwargs["manager_id"]
+ )
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["ads"] = self.get_ads()
+ return ctx
+
+ def done(self, form_list, **kwargs):
+ path = finders.find("template-arrete-municipal.docx")
+ decree = DocxTemplate(path)
+
+ cleaned_data = self.get_all_cleaned_data()
+
+ # DocxTemplate uses jinja2 to render the template. To render dates, we
+ # could use {{ date.strftime(...)}} but the month would be in English.
+ # Use the django date template filter to use correct format.
+ cleaned_data.update(
+ {
+ k + "_str": date_template_filter(v, "d F Y")
+ for k, v in cleaned_data.items()
+ if isinstance(v, date)
+ }
+ )
+
+ # Prefix the commune name with "la commune d'" or "la commune de "
+ decree_commune = cleaned_data["decree_commune"]
+ cleaned_data["decree_commune_fulltext"] = (
+ "d'%s" % decree_commune
+ if decree_commune[:1] in ("aeiouy")
+ else "de %s" % decree_commune
+ )
+
+ decree.render(cleaned_data)
+
+ response = HttpResponse(
+ content_type="application/vnd.openxmlformats",
+ headers={"Content-Disposition": 'attachment; filename="decret.docx"'},
+ )
+
+ decree.save(response)
+ return response
+
+
+class ADSHistoryView(DetailView):
+ template_name = "pages/ads_register/ads_history.html"
+ model = ADS
+ pk_url_kwarg = "ads_id"
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["history"] = ModelHistory(
+ self.object,
+ ignore_fields=[
+ ADS._meta.get_field("ads_manager"),
+ ADS._meta.get_field("creation_date"),
+ ADS._meta.get_field("last_update"),
+ ADSLegalFile._meta.get_field("ads"),
+ ADSLegalFile._meta.get_field("creation_date"),
+ ADSUser._meta.get_field("ads"),
+ ],
+ )
+ return ctx
diff --git a/mesads/app/views/ads_manager.py b/mesads/app/views/ads_manager.py
new file mode 100644
index 0000000..eb01fe8
--- /dev/null
+++ b/mesads/app/views/ads_manager.py
@@ -0,0 +1,122 @@
+from django.contrib import messages
+from django.db.models import Count, Q, Value
+from django.db.models.functions import Replace
+from django.shortcuts import get_object_or_404, redirect, render
+from django.views.generic.edit import ProcessFormView
+from django.views.generic.list import ListView
+
+from ..forms import (
+ ADSManagerDecreeFormSet,
+ ADSManagerEditForm,
+ ADSSearchForm,
+)
+from ..models import (
+ ADS,
+ ADSManager,
+)
+
+
+class ADSManagerView(ListView, ProcessFormView):
+ template_name = "pages/ads_register/ads_manager.html"
+ model = ADS
+ paginate_by = 50
+
+ def get(self, request, *args, **kwargs):
+ self.search_form = ADSSearchForm(request.GET)
+ return ListView.get(self, request, *args, **kwargs)
+
+ def get_ads_manager(self):
+ return ADSManager.objects.get(id=self.kwargs["manager_id"])
+
+ def get_form(self):
+ if self.request.method == "POST":
+ return ADSManagerEditForm(
+ instance=self.get_ads_manager(), data=self.request.POST
+ )
+ return ADSManagerEditForm(instance=self.get_ads_manager())
+
+ def form_valid(self, form):
+ form.save()
+ return redirect("app.ads-manager.detail", manager_id=self.kwargs["manager_id"])
+
+ def form_invalid(self, form):
+ return self.get(self.request, *self.args, **self.kwargs)
+
+ def get_queryset(self):
+ qs = super().get_queryset()
+ qs = qs.filter(ads_manager__id=self.kwargs["manager_id"])
+
+ if self.search_form.is_valid():
+ if self.search_form.cleaned_data["accepted_cpam"] is not None:
+ qs = qs.filter(
+ accepted_cpam=self.search_form.cleaned_data["accepted_cpam"]
+ )
+
+ q = self.search_form.cleaned_data["q"]
+ if q:
+ qs = qs.annotate(
+ clean_immatriculation_plate=Replace(
+ "immatriculation_plate", Value("-"), Value("")
+ )
+ )
+
+ qs = qs.filter(
+ Q(owner_siret__icontains=q)
+ | Q(adsuser__name__icontains=q)
+ | Q(adsuser__siret__icontains=q)
+ | Q(owner_name__icontains=q)
+ | Q(clean_immatriculation_plate__icontains=q)
+ | Q(epci_commune__libelle__icontains=q)
+ | Q(number__icontains=q)
+ )
+
+ # Add ordering on the number. CAST is necessary in the case the ADS number is not an integer.
+ qs_ordered = qs.extra(
+ select={
+ "ads_number_as_int": "CAST(substring(number FROM '^[0-9]+') AS NUMERIC)"
+ }
+ )
+
+ # First, order by number if it is an integer, then by string.
+ return qs_ordered.annotate(c=Count("id")).order_by(
+ "ads_number_as_int", "number"
+ )
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["search_form"] = self.search_form
+
+ # search_defined is a boolean, set to True of any of the search form
+ # parameter is defined.
+ ctx["search_defined"] = any(
+ (v is not None and v != "" for v in self.search_form.cleaned_data.values())
+ )
+
+ ctx["edit_form"] = self.get_form()
+ ctx["ads_manager"] = ctx["edit_form"].instance
+ return ctx
+
+
+def ads_manager_decree_view(request, manager_id):
+ """Decree limiting the number of ADS for an ADSManager."""
+ ads_manager = get_object_or_404(ADSManager, id=manager_id)
+
+ if request.method == "POST":
+ formset = ADSManagerDecreeFormSet(
+ request.POST, request.FILES, instance=ads_manager
+ )
+ if formset.is_valid():
+ formset.save()
+ messages.success(request, "Les modifications ont été enregistrées.")
+ return redirect("app.ads-manager.decree.detail", manager_id=manager_id)
+ else:
+ formset = ADSManagerDecreeFormSet(instance=ads_manager)
+
+ return render(
+ request,
+ "pages/ads_register/ads_manager_decree.html",
+ context={
+ "ads_manager": ads_manager,
+ "formset": formset,
+ },
+ )
diff --git a/mesads/app/views/ads_manager_admin.py b/mesads/app/views/ads_manager_admin.py
new file mode 100644
index 0000000..427cc37
--- /dev/null
+++ b/mesads/app/views/ads_manager_admin.py
@@ -0,0 +1,75 @@
+from django.shortcuts import get_object_or_404
+from django.utils.text import slugify
+from django.views.generic import View
+
+from ..models import (
+ ADSManager,
+)
+
+from .export import ADSExporter
+
+
+class ADSManagerExportView(View, ADSExporter):
+ def get(self, request, manager_id):
+ self.ads_manager = get_object_or_404(ADSManager, id=manager_id)
+ return self.generate()
+
+ def get_filename(self):
+ administration = self.ads_manager.content_object.display_text()
+ return slugify(f"ADS {administration}") + ".xlsx"
+
+ def get_queryset(self):
+ qs = super().get_queryset()
+ return qs.filter(ads_manager=self.ads_manager)
+
+
+class PrefectureExportView(View, ADSExporter):
+ def get(self, request, ads_manager_administrator):
+ self.ads_manager_administrator = ads_manager_administrator
+ return self.generate()
+
+ def get_filename(self):
+ return f"ADS_prefecture_{self.ads_manager_administrator.prefecture.numero}.xlsx"
+
+ def get_queryset(self):
+ qs = super().get_queryset()
+ return qs.filter(ads_manager__administrator=self.ads_manager_administrator)
+
+ def add_sheets(self, workbook):
+ super().add_sheets(workbook)
+ sheet = workbook.add_worksheet("Gestionnaires ADS")
+ sheet.write_row(
+ 0,
+ 0,
+ (
+ "Nom de l'administration",
+ "Nombre d'ADS",
+ "Statut de la gestion des ADS",
+ ),
+ )
+ # Applying bold format to headers
+ bold_format = workbook.add_format({"bold": True})
+ sheet.set_row(0, None, bold_format)
+
+ for idx, ads_manager in enumerate(
+ self.ads_manager_administrator.adsmanager_set.all()
+ ):
+ status = ""
+ if ads_manager.no_ads_declared:
+ status = "L'administration a déclaré ne gérer aucune ADS"
+ elif ads_manager.epci_delegate:
+ status = (
+ "La gestion des ADS est déléguée à %s"
+ % ads_manager.epci_delegate.display_fulltext()
+ )
+
+ sheet.write_row(
+ idx + 1,
+ 0,
+ (
+ ads_manager.content_object.display_text(),
+ ads_manager.ads_set.count() or "",
+ status,
+ ),
+ )
+ sheet.autofit()
diff --git a/mesads/app/views/ads_manager_request.py b/mesads/app/views/ads_manager_request.py
new file mode 100644
index 0000000..8823207
--- /dev/null
+++ b/mesads/app/views/ads_manager_request.py
@@ -0,0 +1,247 @@
+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.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.views.generic.edit import FormView
+
+from reversion.views import RevisionMixin
+
+from ..forms import (
+ ADSManagerForm,
+)
+from ..models import (
+ ADSManagerAdministrator,
+ ADSManagerRequest,
+)
+
+
+class ADSManagerAdminView(RevisionMixin, TemplateView):
+ """This view is used by ADSManagerAdministartors 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
+ success_url = reverse_lazy("app.ads-manager.index")
+
+ def get_context_data(self, **kwargs):
+ """Expose the list of ADSManagerAdministrators for which current user
+ is configured.
+
+ It is also accessible through user.adsmanageradministrator_set.all, but
+ we need to prefetch ads_managers__content_object to reduce the number
+ of SQL queries generated.
+ """
+ ctx = super().get_context_data(**kwargs)
+ ctx["user_ads_manager_requests"] = (
+ ADSManagerRequest.objects.filter(user=self.request.user)
+ .annotate(ads_count=Count("ads_manager__ads"))
+ .all()
+ )
+
+ ctx["ads_managers_administrators"] = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(users=self.request.user)
+ .all()
+ )
+ return ctx
+
+ def form_valid(self, form):
+ _, created = ADSManagerRequest.objects.get_or_create(
+ user=self.request.user,
+ ads_manager=form.cleaned_data["ads_manager"],
+ )
+
+ # Request already exists
+ if not created:
+ messages.warning(
+ self.request,
+ self.get_message_for_existing_request(form.cleaned_data["ads_manager"]),
+ )
+ # Send notifications to administrators.
+ else:
+ messages.success(
+ self.request,
+ self.get_message_for_new_request(form.cleaned_data["ads_manager"]),
+ )
+ email_subject = render_to_string(
+ "pages/email_ads_manager_request_administrator_subject.txt",
+ {
+ "user": self.request.user,
+ },
+ request=self.request,
+ ).strip()
+ email_content = render_to_string(
+ "pages/email_ads_manager_request_administrator_content.txt",
+ {
+ "request": self.request,
+ "ads_manager": form.cleaned_data["ads_manager"],
+ "user": self.request.user,
+ },
+ request=self.request,
+ )
+ email_content_html = render_to_string(
+ "pages/email_ads_manager_request_administrator_content.mjml",
+ {
+ "request": self.request,
+ "ads_manager": form.cleaned_data["ads_manager"],
+ "user": self.request.user,
+ },
+ request=self.request,
+ )
+
+ if form.cleaned_data["ads_manager"].administrator:
+ for administrator_user in form.cleaned_data[
+ "ads_manager"
+ ].administrator.users.all():
+ send_mail(
+ email_subject,
+ email_content,
+ settings.MESADS_CONTACT_EMAIL,
+ [administrator_user],
+ fail_silently=True,
+ html_message=email_content_html,
+ )
+
+ return super().form_valid(form)
+
+ def get_message_for_existing_request(self, ads_manager):
+ if not ads_manager.administrator:
+ return """
+ Vous avez déjà effectué une demande pour gérer les ADS de %(administration)s, et notre équipe va y répondre dans les plus brefs délais.
+
+ Si vous n'avez eu aucun retour depuis plusieurs jours, n'hésitez pas à contacter notre équipe par email à %(email)s ou via notre module de tchat.
+ """ % {
+ "email": settings.MESADS_CONTACT_EMAIL,
+ "administration": ads_manager.content_object.display_fulltext(),
+ }
+ return """
+ Vous avez déjà effectué une demande pour gérer les ADS de %(administration)s. Cette demande a été envoyée à %(prefecture)s qui devrait y répondre rapidement.
+
+ Si vous n'avez eu aucun retour depuis plusieurs jours, n'hésitez pas à nous signaler le problème par email à %(email)s ou via notre module de tchat.
+
+ Nous pourrons alors valider votre demande manuellement.
+ """ % {
+ "administration": ads_manager.content_object.display_fulltext(),
+ "prefecture": ads_manager.administrator.prefecture.display_fulltext(),
+ "email": settings.MESADS_CONTACT_EMAIL,
+ }
+
+ def get_message_for_new_request(self, ads_manager):
+ # Request for EPCI or prefectures
+ if not ads_manager.administrator:
+ return """
+ Votre demande vient d’être envoyée à notre équipe. Vous recevrez une confirmation de validation de votre
+ accès par mail.
+
+ En cas de difficulté ou si vous n’obtenez pas de validation de votre demande vous pouvez contacter par email à
+ %(email)s ou via notre module de tchat.
+
+ Vous pouvez également demander un accès pour la gestion des ADS d’une autre collectivité.
+ """ % {
+ "email": settings.MESADS_CONTACT_EMAIL
+ }
+
+ return """
+ Votre demande vient d’être envoyée à %(prefecture)s. Vous recevrez une confirmation de validation de votre
+ accès par mail.
+
+ En cas de difficulté ou si vous n’obtenez pas de validation de votre demande vous pouvez
+ contacter par email à %(email)s ou via notre module de tchat.
+
+ Vous pouvez également demander un accès pour la gestion des ADS d’une autre collectivité.
+ """ % {
+ "prefecture": ads_manager.administrator.prefecture.display_fulltext(),
+ "email": settings.MESADS_CONTACT_EMAIL,
+ }
diff --git a/mesads/app/views/dashboards.py b/mesads/app/views/dashboards.py
new file mode 100644
index 0000000..e5f1ddc
--- /dev/null
+++ b/mesads/app/views/dashboards.py
@@ -0,0 +1,308 @@
+from datetime import timedelta
+import collections
+
+from django.db.models import Count, Q, Sum
+from django.db.models.functions import Coalesce
+from django.utils import timezone
+from django.views.generic import TemplateView
+from django.views.generic.detail import DetailView
+
+from ..models import (
+ ADSManager,
+ ADSManagerAdministrator,
+)
+
+
+class DashboardsView(TemplateView):
+ template_name = "pages/ads_register/dashboards_list.html"
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["stats"], ctx["stats_total"] = self.get_stats()
+ return ctx
+
+ def get_stats(self):
+ """This function returns a tuple of two values:
+
+ * stats: a list of dictionaries containing the following keys:
+ - obj: the ADSManagerAdministrator instance
+ - ads: a dictionary where keys represent the period (now, 3 months
+ ago, 6 months ago, 12 months ago), and values are the number of ADS
+ for this ADSManagerAdministrator
+ - users: a dictionary where keys represent the period (now, 3 months
+ ago, 6 months ago, 12 months ago), and values are the number of
+ accounts who can create ADS for this ADSManagerAdministrator
+
+ >>> [
+ ... obj:
+ ... 'ads': {
+ ... 'now':
+ ... '3_months':
+ ... '6_months':
+ ... '12_months':
+ ... },
+ ... 'users': {
+ ... 'now':
+ ... '3_months':
+ ... '6_months':
+ ... '12_months':
+ ... }
+ ... ]
+
+ * stats_total: a dictionary containing the keys 'ads' and 'users', and
+ the values are dictionaries where keys represent the period, and
+ values are the total number of ADS and users.
+
+ >>> {
+ ... 'ads': {
+ ... 'now': ,
+ ... '3_months': ,
+ ... '6_months': ,
+ ... '12_months': ,
+ ... },
+ ... 'users': { ... }
+ ... }
+ """
+ now = timezone.now()
+
+ stats = collections.defaultdict(lambda: {"obj": None, "ads": {}, "users": {}})
+
+ stats_total = {
+ "ads": {},
+ "users": {},
+ }
+
+ ads_query_now = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .annotate(ads_count=Count("adsmanager__ads"))
+ .filter(ads_count__gt=0)
+ )
+
+ # All ADSManagerAdministrator, with the count of ADS with at least one of the contact fields filled.
+ ads_with_info_query_now = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .annotate(
+ ads_count=Count(
+ "adsmanager__ads",
+ filter=~Q(adsmanager__ads__owner_email="")
+ | ~Q(adsmanager__ads__owner_mobile="")
+ | ~Q(adsmanager__ads__owner_phone=""),
+ )
+ )
+ .filter(ads_count__gt=0)
+ )
+
+ ads_query_3_months = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(adsmanager__ads__creation_date__lte=now - timedelta(weeks=4 * 3))
+ .annotate(ads_count=Count("adsmanager__ads"))
+ )
+
+ ads_query_6_months = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(adsmanager__ads__creation_date__lte=now - timedelta(weeks=4 * 6))
+ .annotate(ads_count=Count("adsmanager__ads"))
+ )
+
+ ads_query_12_months = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(adsmanager__ads__creation_date__lte=now - timedelta(weeks=4 * 12))
+ .annotate(ads_count=Count("adsmanager__ads"))
+ )
+
+ for label, query in (
+ ("now", ads_query_now),
+ ("with_info_now", ads_with_info_query_now),
+ ("3_months", ads_query_3_months),
+ ("6_months", ads_query_6_months),
+ ("12_months", ads_query_12_months),
+ ):
+ for row in query:
+ stats[row.prefecture.id]["obj"] = row
+ stats[row.prefecture.id]["ads"][label] = row.ads_count
+
+ stats_total["ads"][label] = query.aggregate(
+ total=Coalesce(Sum("ads_count"), 0)
+ )["total"]
+
+ users_query_now = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(adsmanager__adsmanagerrequest__accepted=True)
+ .annotate(users_count=Count("id"))
+ )
+
+ users_query_3_months = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(
+ adsmanager__adsmanagerrequest__accepted=True,
+ adsmanager__adsmanagerrequest__created_at__lte=now
+ - timedelta(weeks=4 * 3),
+ )
+ .annotate(users_count=Count("id"))
+ )
+
+ users_query_6_months = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(
+ adsmanager__adsmanagerrequest__accepted=True,
+ adsmanager__adsmanagerrequest__created_at__lte=now
+ - timedelta(weeks=4 * 6),
+ )
+ .annotate(users_count=Count("id"))
+ )
+
+ users_query_12_months = (
+ ADSManagerAdministrator.objects.select_related("prefecture")
+ .filter(
+ adsmanager__adsmanagerrequest__accepted=True,
+ adsmanager__adsmanagerrequest__created_at__lte=now
+ - timedelta(weeks=4 * 12),
+ )
+ .annotate(users_count=Count("id"))
+ )
+
+ for label, query in (
+ ("now", users_query_now),
+ ("3_months", users_query_3_months),
+ ("6_months", users_query_6_months),
+ ("12_months", users_query_12_months),
+ ):
+ for row in query.all():
+ stats[row.prefecture.id]["obj"] = row
+ stats[row.prefecture.id]["users"][label] = row.users_count
+
+ stats_total["users"][label] = query.aggregate(
+ total=Coalesce(Sum("users_count"), 0)
+ )["total"]
+
+ return (
+ # Transform dict to an ordered list
+ sorted(list(stats.values()), key=lambda stat: stat["obj"].id),
+ stats_total,
+ )
+
+
+class DashboardsDetailView(DetailView):
+ template_name = "pages/ads_register/dashboards_detail.html"
+ model = ADSManagerAdministrator
+ pk_url_kwarg = "ads_manager_administrator_id"
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["stats"] = self.get_stats()
+ return ctx
+
+ def get_stats(self):
+ stats = {}
+
+ stats = collections.defaultdict(lambda: {"obj": None, "ads": {}, "users": {}})
+
+ now = timezone.now()
+
+ ads_query_now = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(administrator=self.object)
+ .annotate(ads_count=Count("ads"))
+ .filter(ads_count__gt=0)
+ )
+
+ # All ADSManager, with the count of ADS with at least one of the contact fields filled.
+ ads_with_info_query_now = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(administrator=self.object)
+ .annotate(
+ ads_count=Count(
+ "ads",
+ filter=~Q(ads__owner_email="")
+ | ~Q(ads__owner_mobile="")
+ | ~Q(ads__owner_phone=""),
+ )
+ )
+ .filter(ads_count__gt=0)
+ )
+
+ ads_query_3_months = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(
+ administrator=self.object,
+ ads__creation_date__lte=now - timedelta(weeks=4 * 3),
+ )
+ .annotate(ads_count=Count("ads"))
+ )
+
+ ads_query_6_months = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(
+ administrator=self.object,
+ ads__creation_date__lte=now - timedelta(weeks=4 * 6),
+ )
+ .annotate(ads_count=Count("ads"))
+ )
+
+ ads_query_12_months = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(
+ administrator=self.object,
+ ads__creation_date__lte=now - timedelta(weeks=4 * 12),
+ )
+ .annotate(ads_count=Count("ads"))
+ )
+
+ for label, query in (
+ ("now", ads_query_now),
+ ("with_info_now", ads_with_info_query_now),
+ ("3_months", ads_query_3_months),
+ ("6_months", ads_query_6_months),
+ ("12_months", ads_query_12_months),
+ ):
+ for row in query:
+ stats[row.id]["obj"] = row
+ stats[row.id]["ads"][label] = row.ads_count
+
+ users_query_now = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(administrator=self.object, adsmanagerrequest__accepted=True)
+ .annotate(users_count=Count("id"))
+ )
+
+ users_query_3_months = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(
+ administrator=self.object,
+ adsmanagerrequest__accepted=True,
+ adsmanagerrequest__created_at__lte=now - timedelta(weeks=4 * 3),
+ )
+ .annotate(users_count=Count("id"))
+ )
+
+ users_query_6_months = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(
+ administrator=self.object,
+ adsmanagerrequest__accepted=True,
+ adsmanagerrequest__created_at__lte=now - timedelta(weeks=4 * 6),
+ )
+ .annotate(users_count=Count("id"))
+ )
+
+ users_query_12_months = (
+ ADSManager.objects.prefetch_related("content_type", "content_object")
+ .filter(
+ administrator=self.object,
+ adsmanagerrequest__accepted=True,
+ adsmanagerrequest__created_at__lte=now - timedelta(weeks=4 * 12),
+ )
+ .annotate(users_count=Count("id"))
+ )
+
+ for label, query in (
+ ("now", users_query_now),
+ ("3_months", users_query_3_months),
+ ("6_months", users_query_6_months),
+ ("12_months", users_query_12_months),
+ ):
+ for row in query.all():
+ stats[row.id]["obj"] = row
+ stats[row.id]["users"][label] = row.users_count
+
+ return sorted(list(stats.values()), key=lambda stat: stat["obj"].id)
diff --git a/mesads/app/views/export.py b/mesads/app/views/export.py
new file mode 100644
index 0000000..8d17d87
--- /dev/null
+++ b/mesads/app/views/export.py
@@ -0,0 +1,138 @@
+from django.contrib.postgres.aggregates import ArrayAgg
+from django.http import HttpResponse
+
+import xlsxwriter
+
+from ..models import (
+ ADS,
+ ADSUser,
+)
+
+
+class ADSExporter:
+ """Generic class to export a list of ADS in an Excel file."""
+
+ def get_filename(self):
+ raise NotImplementedError
+
+ def get_queryset(self):
+ return (
+ ADS.objects.select_related(
+ "ads_manager__administrator__prefecture",
+ )
+ .prefetch_related(
+ "ads_manager__content_object",
+ )
+ .annotate(
+ ads_users_status=ArrayAgg("adsuser__status"),
+ ads_users_names=ArrayAgg("adsuser__name"),
+ ads_users_sirets=ArrayAgg("adsuser__siret"),
+ ads_users_licenses=ArrayAgg("adsuser__license_number"),
+ )
+ )
+
+ def display_bool(self, value):
+ if value is None:
+ return ""
+ return "oui" if value else "non"
+
+ def display_date(self, value):
+ if not value:
+ return ""
+ return value.strftime("%d/%m/%Y")
+
+ def generate(self):
+ response = HttpResponse(
+ content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ headers={
+ "Content-Disposition": f'attachment; filename="{self.get_filename()}"'
+ },
+ )
+ workbook = xlsxwriter.Workbook(response)
+ self.add_sheets(workbook)
+ workbook.close()
+ return response
+
+ def add_sheets(self, workbook):
+ """Override this method to add more sheets to the workbook."""
+ self.ads_list_sheet(workbook)
+
+ def ads_list_sheet(self, workbook):
+ bold_format = workbook.add_format({"bold": True})
+ sheet = workbook.add_worksheet("ADS enregistrées")
+ headers = (
+ "Type d'administration",
+ "Administration",
+ "Numéro de l'ADS",
+ "ADS actuellement exploitée ?",
+ "Date de création de l'ADS",
+ "Date du dernier renouvellement de l'ADS",
+ "Date d'attribution de l'ADS au titulaire actuel",
+ "Véhicule conventionné CPAM ?",
+ "Plaque d'immatriculation du véhicule",
+ "Le véhicule est-il un véhicule électrique/hybride ?",
+ "Véhicule compatible PMR ?",
+ "Titulaire de l'ADS",
+ "SIRET du titulaire de l'ADS",
+ "Téléphone fixe du titulaire de l'ADS",
+ "Téléphone mobile du titulaire de l'ADS",
+ "Email du titulaire de l'ADS",
+ )
+ # If one of the ADS in the list has, let's say, 4 drivers, driver_headers
+ # will be appended 4 times to headers.
+ driver_headers = (
+ "Statut du %s conducteur",
+ "Nom du %s conducteur",
+ "SIRET du %s conducteur",
+ "Numéro de la carte professionnelle du %s conducteur",
+ )
+ # Counts the maximum number of drivers in the list of ADS..
+ max_drivers = 0
+
+ # Applying bold format to headers
+ sheet.set_row(0, None, bold_format)
+
+ for idx, ads in enumerate(self.get_queryset()):
+ # Append driver headers to headers if the current ADS has more drivers
+ # than the previous ones.
+ while max_drivers < len(ads.ads_users_status):
+ for h in driver_headers:
+ headers += (
+ h % ("1er" if max_drivers == 0 else "%se" % (max_drivers + 1)),
+ )
+ max_drivers += 1
+
+ info = (
+ ads.ads_manager.content_object.type_name(),
+ ads.ads_manager.content_object.text(),
+ ads.number,
+ self.display_bool(ads.ads_in_use),
+ self.display_date(ads.ads_creation_date),
+ self.display_date(ads.ads_renew_date),
+ self.display_date(ads.attribution_date),
+ self.display_bool(ads.accepted_cpam),
+ ads.immatriculation_plate,
+ self.display_bool(ads.eco_vehicle),
+ self.display_bool(ads.vehicle_compatible_pmr),
+ ads.owner_name,
+ ads.owner_siret,
+ ads.owner_phone,
+ ads.owner_mobile,
+ ads.owner_email,
+ )
+ for nth, status in enumerate(ads.ads_users_status):
+ # ads_users_status, ads_users_names, ads_users_sirets and
+ # ads_users_licenses have the same length.
+ info += (
+ dict(ADSUser.status.field.choices).get(
+ ads.ads_users_status[nth], ""
+ ),
+ ads.ads_users_names[nth],
+ ads.ads_users_sirets[nth],
+ ads.ads_users_licenses[nth],
+ )
+ sheet.write_row(idx + 1, 0, info)
+
+ # Write headers, now that we know the maximum number of drivers.
+ sheet.write_row(0, 0, headers)
+ sheet.autofit()
diff --git a/mesads/app/views/public.py b/mesads/app/views/public.py
new file mode 100644
index 0000000..4ef01d4
--- /dev/null
+++ b/mesads/app/views/public.py
@@ -0,0 +1,143 @@
+import json
+
+from django.db.models import Count
+from django.db.models.functions import TruncMonth
+from django.views.generic import TemplateView
+
+from ..models import (
+ ADS,
+ ADSManager,
+ ADSManagerRequest,
+)
+
+
+class HTTP500View(TemplateView):
+ """The default HTTP/500 handler can't access to context processors and does
+ not have access to the variable MESADS_CONTACT_EMAIL.
+ """
+
+ template_name = "500.html"
+
+ def dispatch(self, request, *args, **kwargs):
+ """The base class TemplateView only accepts GET requests. By overriding
+ dispatch, we return the error page for any other method."""
+ return super().get(request, *args, **kwargs)
+
+
+class HomepageView(TemplateView):
+ template_name = "pages/homepage.html"
+
+
+class FAQView(TemplateView):
+ template_name = "pages/faq.html"
+
+
+class StatsView(TemplateView):
+ template_name = "pages/stats.html"
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+
+ ctx["ads_count"] = ADS.objects.count()
+
+ ads_count_by_month = (
+ ADS.objects.annotate(month=TruncMonth("creation_date"))
+ .values("month")
+ .annotate(count=Count("id"))
+ .order_by("month")
+ )
+ ctx["ads_count_by_month"] = json.dumps(
+ dict(
+ ((row["month"].isoformat(), row["count"]) for row in ads_count_by_month)
+ )
+ )
+
+ ctx["ads_manager_requests_count"] = ADSManagerRequest.objects.filter(
+ accepted=True
+ ).count()
+
+ ads_manager_requests_by_month = (
+ ADSManagerRequest.objects.filter(accepted=True)
+ .annotate(month=TruncMonth("created_at"))
+ .values("month")
+ .annotate(count=Count("id"))
+ .order_by("month")
+ )
+ ctx["ads_manager_requests_by_month"] = json.dumps(
+ dict(
+ (
+ (row["month"].isoformat(), row["count"])
+ for row in ads_manager_requests_by_month
+ )
+ )
+ )
+
+ ctx["ads_managers_count"] = (
+ ADSManager.objects.annotate(ads_count=Count("ads"))
+ .filter(ads_count__gt=0)
+ .count()
+ )
+
+ return ctx
+
+
+class ReglementationView(TemplateView):
+ template_name = "pages/reglementation.html"
+ entries = [
+ {
+ "title": "Principes généraux",
+ "articles": [
+ {
+ "title": "Le rôle des collectivités",
+ "template": "pages/reglementation/principes_generaux/role_collectivites.html",
+ },
+ {
+ "title": "Qu'est-ce qu'une ADS ?",
+ "template": "pages/reglementation/principes_generaux/qu_est_ce_qu_une_ads.html",
+ },
+ {
+ "title": "Qui délivre les ADS ?",
+ "template": "pages/reglementation/principes_generaux/qui_delivre_ads.html",
+ },
+ ],
+ },
+ {
+ "title": "Délivrance d'une ADS",
+ "articles": [
+ {
+ "menu_title": "Arrêté délimitant le nombre d'ADS",
+ "title": "Étape 1 : l'arrêté délimitant le nombre d'ADS",
+ "template": "pages/reglementation/delivrance_ads/arrete_delimitant_ads.html",
+ },
+ {
+ "menu_title": "Attribution de l'ADS",
+ "title": "Étape 2 : l'attribution de l'ADS",
+ "template": "pages/reglementation/delivrance_ads/attribution_ads.html",
+ },
+ {
+ "menu_title": "L'arrêté municipal",
+ "title": "Étape 3 : la notification de l'arrêté",
+ "template": "pages/reglementation/delivrance_ads/notification_arrete.html",
+ },
+ {
+ "menu_title": "Retrait d'une ADS",
+ "title": "Étape 4 : le retrait d'une ADS",
+ "template": "pages/reglementation/delivrance_ads/retrait_ads.html",
+ },
+ ],
+ },
+ {
+ "title": "Registre des taxis relais",
+ "articles": [
+ {
+ "title": "Qu'est-ce qu'un taxi relais ?",
+ "template": "pages/reglementation/relais/definition.html",
+ },
+ ],
+ },
+ ]
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ ctx["entries"] = self.entries
+ return ctx
diff --git a/mesads/app/test_views.py b/mesads/app/views/test_ads.py
similarity index 54%
rename from mesads/app/test_views.py
rename to mesads/app/views/test_ads.py
index 812645e..d920006 100644
--- a/mesads/app/test_views.py
+++ b/mesads/app/views/test_ads.py
@@ -1,456 +1,20 @@
-from datetime import datetime, timedelta
import re
-from django.contrib import messages
-from django.contrib.contenttypes.models import ContentType
-from django.core import mail
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.models import Q
-from django.test import RequestFactory
-from django.utils import timezone
from freezegun import freeze_time
-from mesads.app.views import HTTP500View
-from mesads.fradm.models import Commune, EPCI, Prefecture
+from mesads.fradm.models import Commune, EPCI
-from .models import (
+from ..models import (
ADS,
ADSLegalFile,
ADSManager,
- ADSManagerRequest,
ADSUser,
)
-from .unittest import ClientTestCase
-from .views import DashboardsView, DashboardsDetailView
-
-
-class TestHTTP500View(ClientTestCase):
- def test_500(self):
- request = RequestFactory().get("/500")
- response = HTTP500View.as_view()(request)
- self.assertEqual(response.status_code, 200)
-
- # POST requests should be allowed
- request = RequestFactory().post("/500")
- response = HTTP500View.as_view()(request)
- self.assertEqual(response.status_code, 200)
-
-
-class TestHomepageView(ClientTestCase):
- def test_200(self):
- resp = self.anonymous_client.get("/")
- self.assertEqual(resp.status_code, 200)
-
-
-class TestProfileADSManagerAdministratorView(ClientTestCase):
- def test_200(self):
- resp = self.anonymous_client.get("/prefecture")
- self.assertEqual(resp.status_code, 200)
-
-
-class TestProfileADSManagerView(ClientTestCase):
- def test_200(self):
- resp = self.anonymous_client.get("/gestionnaire_ads")
- self.assertEqual(resp.status_code, 200)
-
-
-class TestProfileDriverView(ClientTestCase):
- def test_200(self):
- resp = self.anonymous_client.get("/chauffeur")
- self.assertEqual(resp.status_code, 200)
-
-
-class TestADSRegisterView(ClientTestCase):
- def test_redirection(self):
- for client_name, client, expected_status, redirect_url in (
- (
- "anonymous",
- self.anonymous_client,
- 302,
- "/auth/login/?next=/registre_ads/",
- ),
- ("auth", self.auth_client, 302, "/registre_ads/gestion"),
- (
- "ads_manager 35",
- self.ads_manager_city35_client,
- 302,
- "/registre_ads/gestion",
- ),
- (
- "ads_manager_admin 35",
- self.ads_manager_administrator_35_client,
- 302,
- "/registre_ads/admin_gestion",
- ),
- ("admin", self.admin_client, 302, "/registre_ads/dashboards"),
- ):
- with self.subTest(
- client_name=client_name,
- expected_status=expected_status,
- redirect_url=redirect_url,
- ):
- resp = client.get("/registre_ads/")
- self.assertEqual(resp.status_code, expected_status)
- self.assertEqual(resp.url, redirect_url)
-
-
-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()
- self.initial_ads_managers_count = ADSManagerRequest.objects.count()
-
- def test_permissions(self):
- for client_name, client, expected_status in (
- ("anonymous", self.anonymous_client, 302),
- ("auth", self.auth_client, 200),
- ("ads_manager 35", self.ads_manager_city35_client, 200),
- ("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/gestion")
- self.assertEqual(resp.status_code, expected_status)
-
- def test_create_request_invalid_id(self):
- """Provide the id of a non-existing object."""
- resp = self.auth_client.post("/registre_ads/gestion", {"commune": 9999})
- self.assertEqual(len(resp.context["form"].errors["__all__"]), 1)
-
- def test_create_request_commune(self):
- resp = self.auth_client.post(
- "/registre_ads/gestion", {"commune": self.commune_melesse.id}
- )
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(
- ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
- )
-
- # Make sure django message is in the next request
- resp = self.auth_client.get("/registre_ads/gestion")
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(len(resp.context["messages"]), 1)
- self.assertEqual(list(resp.context["messages"])[0].level, messages.SUCCESS)
-
- # If there is a ADSManagerAdministrator related to the commune, an email is sent for each member.
- # The base class ClientTestCase configures Melesse to be managed by the ADSManagerAdministrator entry of
- # l'Ille-et-Vilaine.
- self.assertEqual(len(mail.outbox), 1)
-
- #
- # If we send the same request, a warning message is displayed and no email is sent.
- #
- resp = self.auth_client.post(
- "/registre_ads/gestion", {"commune": self.commune_melesse.id}
- )
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(
- ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
- )
-
- # Check warning message
- resp = self.auth_client.get("/registre_ads/gestion")
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(len(resp.context["messages"]), 1)
- self.assertEqual(list(resp.context["messages"])[0].level, messages.WARNING)
- # No new email
- self.assertEqual(len(mail.outbox), 1)
-
- def test_create_request_epci(self):
- epci = EPCI.objects.first()
- resp = self.auth_client.post("/registre_ads/gestion", {"epci": epci.id})
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(
- ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
- )
-
- # Make sure django message is in the next request
- resp = self.auth_client.get("/registre_ads/gestion")
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(len(resp.context["messages"]), 1)
- self.assertEqual(list(resp.context["messages"])[0].level, messages.SUCCESS)
-
- #
- # If we send the same request, no object is created and a warning message is displayed.
- #
- resp = self.auth_client.post("/registre_ads/gestion", {"epci": epci.id})
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(
- ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
- )
-
- resp = self.auth_client.get("/registre_ads/gestion")
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(len(resp.context["messages"]), 1)
- self.assertEqual(list(resp.context["messages"])[0].level, messages.WARNING)
-
- def test_create_request_prefecture(self):
- prefecture = Prefecture.objects.first()
- resp = self.auth_client.post(
- "/registre_ads/gestion", {"prefecture": prefecture.id}
- )
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(
- ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
- )
-
- # Make sure django message is in the next request
- resp = self.auth_client.get("/registre_ads/gestion")
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(len(resp.context["messages"]), 1)
- self.assertEqual(list(resp.context["messages"])[0].level, messages.SUCCESS)
-
- #
- # If we send the same request, no object is created and a warning message is displayed.
- #
- resp = self.auth_client.post(
- "/registre_ads/gestion", {"prefecture": prefecture.id}
- )
- self.assertEqual(resp.status_code, 302)
- self.assertEqual(
- ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
- )
-
- # Make sure django message is in the next request
- resp = self.auth_client.get("/registre_ads/gestion")
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(len(resp.context["messages"]), 1)
- self.assertEqual(list(resp.context["messages"])[0].level, messages.WARNING)
-
-
-class TestADSManagerView(ClientTestCase):
- def test_permissions(self):
- for client_name, client, expected_status in (
- ("anonymous", self.anonymous_client, 302),
- ("auth", self.auth_client, 404),
- ("ads_manager 35", self.ads_manager_city35_client, 200),
- ("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/gestion/{self.ads_manager_city35.id}/"
- )
- self.assertEqual(resp.status_code, expected_status)
-
- def test_get_404(self):
- resp = self.ads_manager_city35_client.get("/registre_ads/gestion/99999/")
- self.assertEqual(resp.status_code, 404)
-
- def test_get(self):
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/"
- )
- self.assertEqual(self.ads_manager_city35, resp.context["ads_manager"])
-
- prefecture = Prefecture.objects.filter(numero="35").get()
- ads_manager = ADSManager.objects.filter(
- content_type=ContentType.objects.get_for_model(prefecture),
- object_id=prefecture.id,
- ).get()
-
- resp = self.ads_manager_administrator_35_client.get(
- f"/registre_ads/gestion/{ads_manager.id}/"
- )
- self.assertEqual(
- self.ads_manager_administrator_35.prefecture,
- resp.context["ads_manager"].content_object,
- )
-
- def test_filters(self):
- """Test filtering"""
- # ADS 1
- ads1 = ADS.objects.create(
- number="FILTER1",
- ads_manager=self.ads_manager_city35,
- immatriculation_plate="imm4tri-cul4tion",
- accepted_cpam=True,
- ads_in_use=True,
- )
- # ADS 2
- ads2 = ADS.objects.create(
- number="FILTER2",
- ads_manager=self.ads_manager_city35,
- owner_name="Bob Dylan",
- accepted_cpam=False,
- ads_in_use=True,
- )
- # ADS 3
- ads3 = ADS.objects.create(
- number="FILTER3",
- ads_manager=self.ads_manager_city35,
- owner_siret="12312312312312",
- ads_in_use=True,
- )
- ADSUser.objects.create(ads=ads3, name="Henri super", siret="11111111111111")
- # ADS 4
- ads4 = ADS.objects.create(
- number="FILTER4", ads_manager=self.ads_manager_city35, ads_in_use=True
- )
- ADSUser.objects.create(
- ads=ads4, name="Matthieu pas super", siret="22222222222222"
- )
-
- # Immatriculatin plate, returns first ADS
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=imm4tricul4tion"
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads1])
-
- # Owner firstname/lastname, returns second ADS
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=bob dyla"
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads2])
-
- # Owner SIRET, return third ADS
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=123123123"
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads3])
-
- # User SIRET, return ADS 4
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=22222222222222"
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads4])
-
- # User name, return ADS 3
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=Henri SUPER"
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads3])
-
- # CPAM accepted true, return ads 1
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?accepted_cpam=True"
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads1])
-
- # CPAM accepted false, return ads 2
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?accepted_cpam=False"
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads2])
-
- # CPAM accepted any, and no filters, return all
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=&accepted_cpam="
- )
- self.assertEqual(list(resp.context["ads_list"].all()), [ads1, ads2, ads3, ads4])
-
- def test_post_ok(self):
- # Set the flag "no_ads_declared" for an administration that has no ADS
- self.assertFalse(self.ads_manager_city35.no_ads_declared)
- resp = self.ads_manager_city35_client.post(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/",
- {
- "no_ads_declared": "on",
- },
- )
- self.assertEqual(resp.status_code, 302)
- self.ads_manager_city35.refresh_from_db()
- self.assertTrue(self.ads_manager_city35.no_ads_declared)
-
- # Remove the flag
- resp = self.ads_manager_city35_client.post(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/",
- )
- self.assertEqual(resp.status_code, 302)
- self.ads_manager_city35.refresh_from_db()
- self.assertFalse(self.ads_manager_city35.no_ads_declared)
-
- def test_post_error(self):
- # Set the flag "no_ads_declared" for an administration which has ADS registered is impossible
- self.assertFalse(self.ads_manager_city35.no_ads_declared)
- ADS.objects.create(
- number="12346", ads_manager=self.ads_manager_city35, ads_in_use=True
- )
- resp = self.ads_manager_city35_client.post(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/",
- {
- "no_ads_declared": "on",
- },
- )
- self.assertEqual(resp.status_code, 200)
- self.ads_manager_city35.refresh_from_db()
- self.assertFalse(self.ads_manager_city35.no_ads_declared)
+from ..unittest import ClientTestCase
class TestADSView(ClientTestCase):
@@ -1173,406 +737,6 @@ def test_create_with_legal_files(self):
self.assertEqual(legal_files[1].file.read(), b"Second file")
-class TestExportPrefecture(ClientTestCase):
- 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/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
- )
- self.assertEqual(resp.status_code, expected_status)
-
- def test_get_404(self):
- resp = self.ads_manager_administrator_35_client.get(
- "/registre_ads/prefectures/9999/export"
- )
- self.assertEqual(resp.status_code, 404)
-
- def test_export(self):
- ADS.objects.create(
- number="1",
- ads_manager=self.ads_manager_city35,
- accepted_cpam=True,
- ads_in_use=True,
- )
- ADS.objects.create(
- number="2",
- ads_manager=self.ads_manager_city35,
- ads_in_use=True,
- )
- ADS.objects.create(
- number="3",
- ads_manager=self.ads_manager_city35,
- ads_creation_date=datetime.now().date(),
- ads_in_use=True,
- )
-
- resp = self.ads_manager_administrator_35_client.get(
- f"/registre_ads/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
- )
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(
- resp.headers["Content-Type"],
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
-
- def test_export_no_ads_declared(self):
- """Similar to test_export, but with no_ads_declared for the ADSManager."""
- self.ads_manager_city35.no_ads_declared = True
- self.ads_manager_city35.save()
-
- ADS.objects.create(
- number="1",
- ads_manager=self.ads_manager_city35,
- accepted_cpam=True,
- ads_in_use=True,
- )
- resp = self.ads_manager_administrator_35_client.get(
- f"/registre_ads/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
- )
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(
- resp.headers["Content-Type"],
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
-
- def test_export_epci_delegate(self):
- """Similar to test_export, but with epci_delegate for the ADSManager."""
- self.ads_manager_city35.epci_delegate = self.fixtures_epci[0]
- self.ads_manager_city35.save()
-
- ADS.objects.create(
- number="1",
- ads_manager=self.ads_manager_city35,
- accepted_cpam=True,
- ads_in_use=True,
- )
- resp = self.ads_manager_administrator_35_client.get(
- f"/registre_ads/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
- )
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(
- resp.headers["Content-Type"],
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
-
-
-class TestExportADSManager(ClientTestCase):
- 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, 200),
- ("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/gestion/{self.ads_manager_city35.id}/export"
- )
- self.assertEqual(resp.status_code, expected_status)
-
- def test_export(self):
- ADS.objects.create(
- number="1",
- ads_manager=self.ads_manager_city35,
- accepted_cpam=True,
- ads_in_use=True,
- )
- ADS.objects.create(
- number="2",
- ads_manager=self.ads_manager_city35,
- ads_in_use=True,
- )
- ADS.objects.create(
- number="3",
- ads_manager=self.ads_manager_city35,
- ads_creation_date=datetime.now().date(),
- ads_in_use=True,
- )
-
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/export"
- )
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(
- resp.headers["Content-Type"],
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
-
-
-class TestDashboardsViews(ClientTestCase):
- """Test DashboardsView and DashboardsDetailView"""
-
- def setUp(self):
- super().setUp()
- request = RequestFactory().get("/dashboards")
- self.dashboards_view = DashboardsView()
- self.dashboards_view.setup(request)
-
- request = RequestFactory().get(
- f"/registre_ads/dashboards/{self.ads_manager_administrator_35.id}"
- )
- self.dashboards_detail_view = DashboardsDetailView(
- object=self.ads_manager_administrator_35
- )
- self.dashboards_detail_view.setup(request)
-
- def test_permissions(self):
- for client_name, client, expected_status in (
- ("anonymous", self.anonymous_client, 302),
- ("auth", self.auth_client, 302),
- ("ads_manager 35", self.ads_manager_city35_client, 302),
- ("ads_manager_admin 35", self.ads_manager_administrator_35_client, 302),
- ("admin", self.admin_client, 200),
- ):
- with self.subTest(client_name=client_name, expected_status=expected_status):
- resp = client.get("/registre_ads/dashboards")
- self.assertEqual(resp.status_code, expected_status)
-
- resp = client.get(
- f"/registre_ads/dashboards/{self.ads_manager_administrator_35.id}/"
- )
- self.assertEqual(resp.status_code, expected_status)
-
- def test_stats_default(self):
- # The base class ClientTestCase creates ads_manager_administrator for
- # departement 35, and configures an ADSManager for the city fo Melesse.
- stats = [
- {
- "obj": self.ads_manager_administrator_35,
- "ads": {},
- "users": {
- "now": 1,
- },
- }
- ]
- stats_total = {
- "ads": {
- "now": 0,
- "with_info_now": 0,
- "3_months": 0,
- "6_months": 0,
- "12_months": 0,
- },
- "users": {
- "now": 1,
- "3_months": 0,
- "6_months": 0,
- "12_months": 0,
- },
- }
- self.assertEqual((stats, stats_total), self.dashboards_view.get_stats())
-
- self.assertEqual(
- [
- {
- "obj": self.ads_manager_city35,
- "ads": {},
- "users": {
- "now": 1,
- },
- }
- ],
- self.dashboards_detail_view.get_stats(),
- )
-
- def test_stats_for_several_ads(self):
- # Create several ADS for the city of Melesse
- now = timezone.now()
- for idx, creation_date in enumerate(
- [
- now - timedelta(days=365 * 2), # 2 years old ADS
- now - timedelta(days=300), # > 6 && < 12 months old
- now - timedelta(days=120), # > 3 && < 6 months old
- now - timedelta(days=1), # yesterday
- ]
- ):
- ads = ADS.objects.create(
- number=str(idx), ads_manager=self.ads_manager_city35, ads_in_use=True
- )
- ads.creation_date = creation_date
- ads.save()
-
- stats = [
- {
- "obj": self.ads_manager_administrator_35,
- "ads": {
- "now": 4,
- "3_months": 3,
- "6_months": 2,
- "12_months": 1,
- },
- "users": {
- "now": 1,
- },
- }
- ]
- stats_total = {
- "ads": {
- "now": 4,
- "with_info_now": 0,
- "3_months": 3,
- "6_months": 2,
- "12_months": 1,
- },
- "users": {
- "now": 1,
- "3_months": 0,
- "6_months": 0,
- "12_months": 0,
- },
- }
-
- self.assertEqual((stats, stats_total), self.dashboards_view.get_stats())
-
- self.assertEqual(
- [
- {
- "obj": self.ads_manager_city35,
- "ads": {
- "now": 4,
- "3_months": 3,
- "6_months": 2,
- "12_months": 1,
- },
- "users": {
- "now": 1,
- },
- }
- ],
- self.dashboards_detail_view.get_stats(),
- )
-
- def test_stats_for_several_ads_managers(self):
- now = timezone.now()
- # Give administration permissions for several users to Melesse.
- for creation_date in [
- now - timedelta(days=365 * 2), # 2 years old ADS
- now - timedelta(days=300), # > 6 && < 12 months old
- now - timedelta(days=120), # > 3 && < 6 months old
- now - timedelta(days=1), # yesterday
- ]:
- user = self.create_user().obj
- ads_manager_request = ADSManagerRequest.objects.create(
- user=user,
- ads_manager=self.ads_manager_city35,
- accepted=True,
- )
- ads_manager_request.created_at = creation_date
- ads_manager_request.save()
-
- stats = [
- {
- "obj": self.ads_manager_administrator_35,
- "ads": {},
- "users": {
- "now": 5,
- "3_months": 3,
- "6_months": 2,
- "12_months": 1,
- },
- }
- ]
- stats_total = {
- "ads": {
- "now": 0,
- "with_info_now": 0,
- "3_months": 0,
- "6_months": 0,
- "12_months": 0,
- },
- "users": {
- "now": 5,
- "3_months": 3,
- "6_months": 2,
- "12_months": 1,
- },
- }
-
- self.assertEqual((stats, stats_total), self.dashboards_view.get_stats())
-
- self.assertEqual(
- [
- {
- "obj": self.ads_manager_city35,
- "ads": {},
- "users": {
- "now": 5,
- "3_months": 3,
- "6_months": 2,
- "12_months": 1,
- },
- }
- ],
- self.dashboards_detail_view.get_stats(),
- )
-
-
-class TestFAQView(ClientTestCase):
- def test_get(self):
- resp = self.client.get("/faq")
- self.assertEqual(resp.status_code, 200)
-
-
-class TestADSManagerDecreeView(ClientTestCase):
- def test_permissions(self):
- for client_name, client, expected_status in (
- ("anonymous", self.anonymous_client, 302),
- ("auth", self.auth_client, 404),
- ("ads_manager 35", self.ads_manager_city35_client, 200),
- ("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/gestion/{self.ads_manager_city35.id}/arrete"
- )
- self.assertEqual(resp.status_code, expected_status)
-
- def test_get_404(self):
- resp = self.ads_manager_city35_client.get("/registre_ads/gestion/99999/arrete")
- self.assertEqual(resp.status_code, 404)
-
- def test_get(self):
- resp = self.ads_manager_city35_client.get(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/arrete"
- )
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(resp.context["ads_manager"], self.ads_manager_city35)
-
- def test_post(self):
- file1 = SimpleUploadedFile(
- name="myfile.pdf", content=b"First file", content_type="application/pdf"
- )
- file2 = SimpleUploadedFile(
- name="myfile2.pdf", content=b"Second file", content_type="application/pdf"
- )
-
- resp = self.ads_manager_city35_client.post(
- f"/registre_ads/gestion/{self.ads_manager_city35.id}/arrete",
- {
- "adsmanagerdecree_set-TOTAL_FORMS": 5,
- "adsmanagerdecree_set-INITIAL_FORMS": 0,
- "adsmanagerdecree_set-MIN_NUM_FORMS": 0,
- "adsmanagerdecree_set-MAX_NUM_FORMS": 5,
- "adsmanagerdecree_set-0-file": file1,
- "adsmanagerdecree_set-1-file": file2,
- },
- )
- self.assertEqual(resp.status_code, 302)
-
- ads_manager_decrees = self.ads_manager_city35.adsmanagerdecree_set.all()
- self.assertEqual(len(ads_manager_decrees), 2)
- self.assertEqual(ads_manager_decrees[0].file.read(), b"First file")
- self.assertEqual(ads_manager_decrees[1].file.read(), b"Second file")
-
-
class TestADSDecreeView(ClientTestCase):
def setUp(self):
super().setUp()
@@ -1764,15 +928,3 @@ def test_get(self):
f"/registre_ads/gestion/{self.ads_manager_city35.id}/ads/{self.ads.id}/history"
)
self.assertEqual(resp.status_code, 200)
-
-
-class TestStatsView(ClientTestCase):
- def test_get(self):
- resp = self.anonymous_client.get("/chiffres-cles")
- self.assertEqual(resp.status_code, 200)
-
-
-class TestReglementationView(ClientTestCase):
- def test_get(self):
- resp = self.anonymous_client.get("/reglementation")
- self.assertEqual(resp.status_code, 200)
diff --git a/mesads/app/views/test_ads_manager.py b/mesads/app/views/test_ads_manager.py
new file mode 100644
index 0000000..141c279
--- /dev/null
+++ b/mesads/app/views/test_ads_manager.py
@@ -0,0 +1,267 @@
+from datetime import datetime
+
+from django.contrib.contenttypes.models import ContentType
+from django.core.files.uploadedfile import SimpleUploadedFile
+
+from mesads.fradm.models import Prefecture
+
+from ..models import (
+ ADS,
+ ADSManager,
+ ADSUser,
+)
+from ..unittest import ClientTestCase
+
+
+class TestADSManagerView(ClientTestCase):
+ def test_permissions(self):
+ for client_name, client, expected_status in (
+ ("anonymous", self.anonymous_client, 302),
+ ("auth", self.auth_client, 404),
+ ("ads_manager 35", self.ads_manager_city35_client, 200),
+ ("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/gestion/{self.ads_manager_city35.id}/"
+ )
+ self.assertEqual(resp.status_code, expected_status)
+
+ def test_get_404(self):
+ resp = self.ads_manager_city35_client.get("/registre_ads/gestion/99999/")
+ self.assertEqual(resp.status_code, 404)
+
+ def test_get(self):
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/"
+ )
+ self.assertEqual(self.ads_manager_city35, resp.context["ads_manager"])
+
+ prefecture = Prefecture.objects.filter(numero="35").get()
+ ads_manager = ADSManager.objects.filter(
+ content_type=ContentType.objects.get_for_model(prefecture),
+ object_id=prefecture.id,
+ ).get()
+
+ resp = self.ads_manager_administrator_35_client.get(
+ f"/registre_ads/gestion/{ads_manager.id}/"
+ )
+ self.assertEqual(
+ self.ads_manager_administrator_35.prefecture,
+ resp.context["ads_manager"].content_object,
+ )
+
+ def test_filters(self):
+ """Test filtering"""
+ # ADS 1
+ ads1 = ADS.objects.create(
+ number="FILTER1",
+ ads_manager=self.ads_manager_city35,
+ immatriculation_plate="imm4tri-cul4tion",
+ accepted_cpam=True,
+ ads_in_use=True,
+ )
+ # ADS 2
+ ads2 = ADS.objects.create(
+ number="FILTER2",
+ ads_manager=self.ads_manager_city35,
+ owner_name="Bob Dylan",
+ accepted_cpam=False,
+ ads_in_use=True,
+ )
+ # ADS 3
+ ads3 = ADS.objects.create(
+ number="FILTER3",
+ ads_manager=self.ads_manager_city35,
+ owner_siret="12312312312312",
+ ads_in_use=True,
+ )
+ ADSUser.objects.create(ads=ads3, name="Henri super", siret="11111111111111")
+ # ADS 4
+ ads4 = ADS.objects.create(
+ number="FILTER4", ads_manager=self.ads_manager_city35, ads_in_use=True
+ )
+ ADSUser.objects.create(
+ ads=ads4, name="Matthieu pas super", siret="22222222222222"
+ )
+
+ # Immatriculatin plate, returns first ADS
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=imm4tricul4tion"
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads1])
+
+ # Owner firstname/lastname, returns second ADS
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=bob dyla"
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads2])
+
+ # Owner SIRET, return third ADS
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=123123123"
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads3])
+
+ # User SIRET, return ADS 4
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=22222222222222"
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads4])
+
+ # User name, return ADS 3
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=Henri SUPER"
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads3])
+
+ # CPAM accepted true, return ads 1
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?accepted_cpam=True"
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads1])
+
+ # CPAM accepted false, return ads 2
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?accepted_cpam=False"
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads2])
+
+ # CPAM accepted any, and no filters, return all
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/?q=&accepted_cpam="
+ )
+ self.assertEqual(list(resp.context["ads_list"].all()), [ads1, ads2, ads3, ads4])
+
+ def test_post_ok(self):
+ # Set the flag "no_ads_declared" for an administration that has no ADS
+ self.assertFalse(self.ads_manager_city35.no_ads_declared)
+ resp = self.ads_manager_city35_client.post(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/",
+ {
+ "no_ads_declared": "on",
+ },
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.ads_manager_city35.refresh_from_db()
+ self.assertTrue(self.ads_manager_city35.no_ads_declared)
+
+ # Remove the flag
+ resp = self.ads_manager_city35_client.post(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/",
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.ads_manager_city35.refresh_from_db()
+ self.assertFalse(self.ads_manager_city35.no_ads_declared)
+
+ def test_post_error(self):
+ # Set the flag "no_ads_declared" for an administration which has ADS registered is impossible
+ self.assertFalse(self.ads_manager_city35.no_ads_declared)
+ ADS.objects.create(
+ number="12346", ads_manager=self.ads_manager_city35, ads_in_use=True
+ )
+ resp = self.ads_manager_city35_client.post(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/",
+ {
+ "no_ads_declared": "on",
+ },
+ )
+ self.assertEqual(resp.status_code, 200)
+ self.ads_manager_city35.refresh_from_db()
+ self.assertFalse(self.ads_manager_city35.no_ads_declared)
+
+
+class TestExportADSManager(ClientTestCase):
+ 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, 200),
+ ("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/gestion/{self.ads_manager_city35.id}/export"
+ )
+ self.assertEqual(resp.status_code, expected_status)
+
+ def test_export(self):
+ ADS.objects.create(
+ number="1",
+ ads_manager=self.ads_manager_city35,
+ accepted_cpam=True,
+ ads_in_use=True,
+ )
+ ADS.objects.create(
+ number="2",
+ ads_manager=self.ads_manager_city35,
+ ads_in_use=True,
+ )
+ ADS.objects.create(
+ number="3",
+ ads_manager=self.ads_manager_city35,
+ ads_creation_date=datetime.now().date(),
+ ads_in_use=True,
+ )
+
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/export"
+ )
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(
+ resp.headers["Content-Type"],
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ )
+
+
+class TestADSManagerDecreeView(ClientTestCase):
+ def test_permissions(self):
+ for client_name, client, expected_status in (
+ ("anonymous", self.anonymous_client, 302),
+ ("auth", self.auth_client, 404),
+ ("ads_manager 35", self.ads_manager_city35_client, 200),
+ ("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/gestion/{self.ads_manager_city35.id}/arrete"
+ )
+ self.assertEqual(resp.status_code, expected_status)
+
+ def test_get_404(self):
+ resp = self.ads_manager_city35_client.get("/registre_ads/gestion/99999/arrete")
+ self.assertEqual(resp.status_code, 404)
+
+ def test_get(self):
+ resp = self.ads_manager_city35_client.get(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/arrete"
+ )
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.context["ads_manager"], self.ads_manager_city35)
+
+ def test_post(self):
+ file1 = SimpleUploadedFile(
+ name="myfile.pdf", content=b"First file", content_type="application/pdf"
+ )
+ file2 = SimpleUploadedFile(
+ name="myfile2.pdf", content=b"Second file", content_type="application/pdf"
+ )
+
+ resp = self.ads_manager_city35_client.post(
+ f"/registre_ads/gestion/{self.ads_manager_city35.id}/arrete",
+ {
+ "adsmanagerdecree_set-TOTAL_FORMS": 5,
+ "adsmanagerdecree_set-INITIAL_FORMS": 0,
+ "adsmanagerdecree_set-MIN_NUM_FORMS": 0,
+ "adsmanagerdecree_set-MAX_NUM_FORMS": 5,
+ "adsmanagerdecree_set-0-file": file1,
+ "adsmanagerdecree_set-1-file": file2,
+ },
+ )
+ self.assertEqual(resp.status_code, 302)
+
+ ads_manager_decrees = self.ads_manager_city35.adsmanagerdecree_set.all()
+ self.assertEqual(len(ads_manager_decrees), 2)
+ self.assertEqual(ads_manager_decrees[0].file.read(), b"First file")
+ self.assertEqual(ads_manager_decrees[1].file.read(), b"Second file")
diff --git a/mesads/app/views/test_ads_manager_admin.py b/mesads/app/views/test_ads_manager_admin.py
new file mode 100644
index 0000000..ac1a5cf
--- /dev/null
+++ b/mesads/app/views/test_ads_manager_admin.py
@@ -0,0 +1,96 @@
+from datetime import datetime
+
+from ..models import (
+ ADS,
+)
+from ..unittest import ClientTestCase
+
+
+class TestExportPrefecture(ClientTestCase):
+ 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/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
+ )
+ self.assertEqual(resp.status_code, expected_status)
+
+ def test_get_404(self):
+ resp = self.ads_manager_administrator_35_client.get(
+ "/registre_ads/prefectures/9999/export"
+ )
+ self.assertEqual(resp.status_code, 404)
+
+ def test_export(self):
+ ADS.objects.create(
+ number="1",
+ ads_manager=self.ads_manager_city35,
+ accepted_cpam=True,
+ ads_in_use=True,
+ )
+ ADS.objects.create(
+ number="2",
+ ads_manager=self.ads_manager_city35,
+ ads_in_use=True,
+ )
+ ADS.objects.create(
+ number="3",
+ ads_manager=self.ads_manager_city35,
+ ads_creation_date=datetime.now().date(),
+ ads_in_use=True,
+ )
+
+ resp = self.ads_manager_administrator_35_client.get(
+ f"/registre_ads/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
+ )
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(
+ resp.headers["Content-Type"],
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ )
+
+ def test_export_no_ads_declared(self):
+ """Similar to test_export, but with no_ads_declared for the ADSManager."""
+ self.ads_manager_city35.no_ads_declared = True
+ self.ads_manager_city35.save()
+
+ ADS.objects.create(
+ number="1",
+ ads_manager=self.ads_manager_city35,
+ accepted_cpam=True,
+ ads_in_use=True,
+ )
+ resp = self.ads_manager_administrator_35_client.get(
+ f"/registre_ads/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
+ )
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(
+ resp.headers["Content-Type"],
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ )
+
+ def test_export_epci_delegate(self):
+ """Similar to test_export, but with epci_delegate for the ADSManager."""
+ self.ads_manager_city35.epci_delegate = self.fixtures_epci[0]
+ self.ads_manager_city35.save()
+
+ ADS.objects.create(
+ number="1",
+ ads_manager=self.ads_manager_city35,
+ accepted_cpam=True,
+ ads_in_use=True,
+ )
+ resp = self.ads_manager_administrator_35_client.get(
+ f"/registre_ads/prefectures/{self.ads_manager_administrator_35.prefecture.id}/export"
+ )
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(
+ resp.headers["Content-Type"],
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ )
diff --git a/mesads/app/views/test_ads_manager_request.py b/mesads/app/views/test_ads_manager_request.py
new file mode 100644
index 0000000..77aab1f
--- /dev/null
+++ b/mesads/app/views/test_ads_manager_request.py
@@ -0,0 +1,209 @@
+from django.contrib import messages
+from django.core import mail
+
+from mesads.fradm.models import EPCI, Prefecture
+
+from ..models import (
+ 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("/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()
+ self.initial_ads_managers_count = ADSManagerRequest.objects.count()
+
+ def test_permissions(self):
+ for client_name, client, expected_status in (
+ ("anonymous", self.anonymous_client, 302),
+ ("auth", self.auth_client, 200),
+ ("ads_manager 35", self.ads_manager_city35_client, 200),
+ ("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/gestion")
+ self.assertEqual(resp.status_code, expected_status)
+
+ def test_create_request_invalid_id(self):
+ """Provide the id of a non-existing object."""
+ resp = self.auth_client.post("/registre_ads/gestion", {"commune": 9999})
+ self.assertEqual(len(resp.context["form"].errors["__all__"]), 1)
+
+ def test_create_request_commune(self):
+ resp = self.auth_client.post(
+ "/registre_ads/gestion", {"commune": self.commune_melesse.id}
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
+ )
+
+ # Make sure django message is in the next request
+ resp = self.auth_client.get("/registre_ads/gestion")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.context["messages"]), 1)
+ self.assertEqual(list(resp.context["messages"])[0].level, messages.SUCCESS)
+
+ # If there is a ADSManagerAdministrator related to the commune, an email is sent for each member.
+ # The base class ClientTestCase configures Melesse to be managed by the ADSManagerAdministrator entry of
+ # l'Ille-et-Vilaine.
+ self.assertEqual(len(mail.outbox), 1)
+
+ #
+ # If we send the same request, a warning message is displayed and no email is sent.
+ #
+ resp = self.auth_client.post(
+ "/registre_ads/gestion", {"commune": self.commune_melesse.id}
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
+ )
+
+ # Check warning message
+ resp = self.auth_client.get("/registre_ads/gestion")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.context["messages"]), 1)
+ self.assertEqual(list(resp.context["messages"])[0].level, messages.WARNING)
+ # No new email
+ self.assertEqual(len(mail.outbox), 1)
+
+ def test_create_request_epci(self):
+ epci = EPCI.objects.first()
+ resp = self.auth_client.post("/registre_ads/gestion", {"epci": epci.id})
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
+ )
+
+ # Make sure django message is in the next request
+ resp = self.auth_client.get("/registre_ads/gestion")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.context["messages"]), 1)
+ self.assertEqual(list(resp.context["messages"])[0].level, messages.SUCCESS)
+
+ #
+ # If we send the same request, no object is created and a warning message is displayed.
+ #
+ resp = self.auth_client.post("/registre_ads/gestion", {"epci": epci.id})
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
+ )
+
+ resp = self.auth_client.get("/registre_ads/gestion")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.context["messages"]), 1)
+ self.assertEqual(list(resp.context["messages"])[0].level, messages.WARNING)
+
+ def test_create_request_prefecture(self):
+ prefecture = Prefecture.objects.first()
+ resp = self.auth_client.post(
+ "/registre_ads/gestion", {"prefecture": prefecture.id}
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
+ )
+
+ # Make sure django message is in the next request
+ resp = self.auth_client.get("/registre_ads/gestion")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.context["messages"]), 1)
+ self.assertEqual(list(resp.context["messages"])[0].level, messages.SUCCESS)
+
+ #
+ # If we send the same request, no object is created and a warning message is displayed.
+ #
+ resp = self.auth_client.post(
+ "/registre_ads/gestion", {"prefecture": prefecture.id}
+ )
+ self.assertEqual(resp.status_code, 302)
+ self.assertEqual(
+ ADSManagerRequest.objects.count(), self.initial_ads_managers_count + 1
+ )
+
+ # Make sure django message is in the next request
+ resp = self.auth_client.get("/registre_ads/gestion")
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(len(resp.context["messages"]), 1)
+ self.assertEqual(list(resp.context["messages"])[0].level, messages.WARNING)
diff --git a/mesads/app/views/test_dashboards.py b/mesads/app/views/test_dashboards.py
new file mode 100644
index 0000000..292db48
--- /dev/null
+++ b/mesads/app/views/test_dashboards.py
@@ -0,0 +1,231 @@
+from datetime import timedelta
+
+from django.test import RequestFactory
+from django.utils import timezone
+
+from ..models import (
+ ADS,
+ ADSManagerRequest,
+)
+from ..unittest import ClientTestCase
+from ..views import DashboardsView, DashboardsDetailView
+
+
+class TestDashboardsViews(ClientTestCase):
+ """Test DashboardsView and DashboardsDetailView"""
+
+ def setUp(self):
+ super().setUp()
+ request = RequestFactory().get("/dashboards")
+ self.dashboards_view = DashboardsView()
+ self.dashboards_view.setup(request)
+
+ request = RequestFactory().get(
+ f"/registre_ads/dashboards/{self.ads_manager_administrator_35.id}"
+ )
+ self.dashboards_detail_view = DashboardsDetailView(
+ object=self.ads_manager_administrator_35
+ )
+ self.dashboards_detail_view.setup(request)
+
+ def test_permissions(self):
+ for client_name, client, expected_status in (
+ ("anonymous", self.anonymous_client, 302),
+ ("auth", self.auth_client, 302),
+ ("ads_manager 35", self.ads_manager_city35_client, 302),
+ ("ads_manager_admin 35", self.ads_manager_administrator_35_client, 302),
+ ("admin", self.admin_client, 200),
+ ):
+ with self.subTest(client_name=client_name, expected_status=expected_status):
+ resp = client.get("/registre_ads/dashboards")
+ self.assertEqual(resp.status_code, expected_status)
+
+ resp = client.get(
+ f"/registre_ads/dashboards/{self.ads_manager_administrator_35.id}/"
+ )
+ self.assertEqual(resp.status_code, expected_status)
+
+ def test_stats_default(self):
+ # The base class ClientTestCase creates ads_manager_administrator for
+ # departement 35, and configures an ADSManager for the city fo Melesse.
+ stats = [
+ {
+ "obj": self.ads_manager_administrator_35,
+ "ads": {},
+ "users": {
+ "now": 1,
+ },
+ }
+ ]
+ stats_total = {
+ "ads": {
+ "now": 0,
+ "with_info_now": 0,
+ "3_months": 0,
+ "6_months": 0,
+ "12_months": 0,
+ },
+ "users": {
+ "now": 1,
+ "3_months": 0,
+ "6_months": 0,
+ "12_months": 0,
+ },
+ }
+ self.assertEqual((stats, stats_total), self.dashboards_view.get_stats())
+
+ self.assertEqual(
+ [
+ {
+ "obj": self.ads_manager_city35,
+ "ads": {},
+ "users": {
+ "now": 1,
+ },
+ }
+ ],
+ self.dashboards_detail_view.get_stats(),
+ )
+
+ def test_stats_for_several_ads(self):
+ # Create several ADS for the city of Melesse
+ now = timezone.now()
+ for idx, creation_date in enumerate(
+ [
+ now - timedelta(days=365 * 2), # 2 years old ADS
+ now - timedelta(days=300), # > 6 && < 12 months old
+ now - timedelta(days=120), # > 3 && < 6 months old
+ now - timedelta(days=1), # yesterday
+ ]
+ ):
+ ads = ADS.objects.create(
+ number=str(idx), ads_manager=self.ads_manager_city35, ads_in_use=True
+ )
+ ads.creation_date = creation_date
+ ads.save()
+
+ stats = [
+ {
+ "obj": self.ads_manager_administrator_35,
+ "ads": {
+ "now": 4,
+ "3_months": 3,
+ "6_months": 2,
+ "12_months": 1,
+ },
+ "users": {
+ "now": 1,
+ },
+ }
+ ]
+ stats_total = {
+ "ads": {
+ "now": 4,
+ "with_info_now": 0,
+ "3_months": 3,
+ "6_months": 2,
+ "12_months": 1,
+ },
+ "users": {
+ "now": 1,
+ "3_months": 0,
+ "6_months": 0,
+ "12_months": 0,
+ },
+ }
+
+ self.assertEqual((stats, stats_total), self.dashboards_view.get_stats())
+
+ self.assertEqual(
+ [
+ {
+ "obj": self.ads_manager_city35,
+ "ads": {
+ "now": 4,
+ "3_months": 3,
+ "6_months": 2,
+ "12_months": 1,
+ },
+ "users": {
+ "now": 1,
+ },
+ }
+ ],
+ self.dashboards_detail_view.get_stats(),
+ )
+
+ def test_stats_for_several_ads_managers(self):
+ now = timezone.now()
+ # Give administration permissions for several users to Melesse.
+ for creation_date in [
+ now - timedelta(days=365 * 2), # 2 years old ADS
+ now - timedelta(days=300), # > 6 && < 12 months old
+ now - timedelta(days=120), # > 3 && < 6 months old
+ now - timedelta(days=1), # yesterday
+ ]:
+ user = self.create_user().obj
+ ads_manager_request = ADSManagerRequest.objects.create(
+ user=user,
+ ads_manager=self.ads_manager_city35,
+ accepted=True,
+ )
+ ads_manager_request.created_at = creation_date
+ ads_manager_request.save()
+
+ stats = [
+ {
+ "obj": self.ads_manager_administrator_35,
+ "ads": {},
+ "users": {
+ "now": 5,
+ "3_months": 3,
+ "6_months": 2,
+ "12_months": 1,
+ },
+ }
+ ]
+ stats_total = {
+ "ads": {
+ "now": 0,
+ "with_info_now": 0,
+ "3_months": 0,
+ "6_months": 0,
+ "12_months": 0,
+ },
+ "users": {
+ "now": 5,
+ "3_months": 3,
+ "6_months": 2,
+ "12_months": 1,
+ },
+ }
+
+ self.assertEqual((stats, stats_total), self.dashboards_view.get_stats())
+
+ self.assertEqual(
+ [
+ {
+ "obj": self.ads_manager_city35,
+ "ads": {},
+ "users": {
+ "now": 5,
+ "3_months": 3,
+ "6_months": 2,
+ "12_months": 1,
+ },
+ }
+ ],
+ self.dashboards_detail_view.get_stats(),
+ )
+
+
+class TestStatsView(ClientTestCase):
+ def test_get(self):
+ resp = self.anonymous_client.get("/chiffres-cles")
+ self.assertEqual(resp.status_code, 200)
+
+
+class TestReglementationView(ClientTestCase):
+ def test_get(self):
+ resp = self.anonymous_client.get("/reglementation")
+ self.assertEqual(resp.status_code, 200)
diff --git a/mesads/app/views/test_public.py b/mesads/app/views/test_public.py
new file mode 100644
index 0000000..c2e0a65
--- /dev/null
+++ b/mesads/app/views/test_public.py
@@ -0,0 +1,81 @@
+from django.test import RequestFactory
+
+from mesads.app.views import HTTP500View
+
+from ..unittest import ClientTestCase
+
+
+class TestHTTP500View(ClientTestCase):
+ def test_500(self):
+ request = RequestFactory().get("/500")
+ response = HTTP500View.as_view()(request)
+ self.assertEqual(response.status_code, 200)
+
+ # POST requests should be allowed
+ request = RequestFactory().post("/500")
+ response = HTTP500View.as_view()(request)
+ self.assertEqual(response.status_code, 200)
+
+
+class TestHomepageView(ClientTestCase):
+ def test_200(self):
+ resp = self.anonymous_client.get("/")
+ self.assertEqual(resp.status_code, 200)
+
+
+class TestProfileADSManagerAdministratorView(ClientTestCase):
+ def test_200(self):
+ resp = self.anonymous_client.get("/prefecture")
+ self.assertEqual(resp.status_code, 200)
+
+
+class TestProfileADSManagerView(ClientTestCase):
+ def test_200(self):
+ resp = self.anonymous_client.get("/gestionnaire_ads")
+ self.assertEqual(resp.status_code, 200)
+
+
+class TestProfileDriverView(ClientTestCase):
+ def test_200(self):
+ resp = self.anonymous_client.get("/chauffeur")
+ self.assertEqual(resp.status_code, 200)
+
+
+class TestADSRegisterView(ClientTestCase):
+ def test_redirection(self):
+ for client_name, client, expected_status, redirect_url in (
+ (
+ "anonymous",
+ self.anonymous_client,
+ 302,
+ "/auth/login/?next=/registre_ads/",
+ ),
+ ("auth", self.auth_client, 302, "/registre_ads/gestion"),
+ (
+ "ads_manager 35",
+ self.ads_manager_city35_client,
+ 302,
+ "/registre_ads/gestion",
+ ),
+ (
+ "ads_manager_admin 35",
+ self.ads_manager_administrator_35_client,
+ 302,
+ "/registre_ads/admin_gestion",
+ ),
+ ("admin", self.admin_client, 302, "/registre_ads/dashboards"),
+ ):
+ with self.subTest(
+ client_name=client_name,
+ expected_status=expected_status,
+ redirect_url=redirect_url,
+ ):
+ resp = client.get("/registre_ads/")
+ self.assertEqual(resp.status_code, expected_status)
+ self.assertEqual(resp.url, redirect_url)
+
+
+class TestFAQView(ClientTestCase):
+ def test_get(self):
+ resp = self.client.get("/faq")
+ self.assertEqual(resp.status_code, 200)