From 5e7086514ab7c1eae25ea4d2580fc90157ca4364 Mon Sep 17 00:00:00 2001
From: ThomasBidot <77505438+ThomasBidot@users.noreply.github.com>
Date: Fri, 12 Feb 2021 19:24:51 +0100
Subject: [PATCH] Sortie thomas (#32)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Updated student admin filters (#28)

* Password reset feature (#8)

* Add Django Rest auth module

* Try to make the send reset password email work

* Modified template mail for reset

* Add Django Rest auth module

* Try to make the send reset password email work

* Modified template mail for reset

* test

* Added utf-8 support to exported csv and switched delimiter from , to ; in admin interface

* Disabled emails while in dev

* Added multi selection filter in admin

* Fixed mail settings

* Added year field to Tutor serializer

* Fixed year updated before registration form filled

* commit for automatic deploy

* Testing CI

* Added filtering in admin for registration validation

* Added filter to student admin

Co-authored-by: chiahetcho <44137047+chiahetcho@users.noreply.github.com>
Co-authored-by: florimondmanca <florimond.manca@gmail.com>
Co-authored-by: Arthur Guédon <arthur.guedon@student-cs.fr>
Co-authored-by: Arthur Guédon <60623551+arthurgdn@users.noreply.github.com>

* Ajout filtre sur register

* Ajout filtre participation

* Ajout de 3 filtres par etablissements

* Ajout 3 filtres pour etablissement V2

Co-authored-by: Seon82 <46298009+Seon82@users.noreply.github.com>
Co-authored-by: chiahetcho <44137047+chiahetcho@users.noreply.github.com>
Co-authored-by: florimondmanca <florimond.manca@gmail.com>
Co-authored-by: Arthur Guédon <arthur.guedon@student-cs.fr>
Co-authored-by: Arthur Guédon <60623551+arthurgdn@users.noreply.github.com>
Co-authored-by: Bidot-Naude Thomas <thomas.bidotnaude@student-cs.fr>
Co-authored-by: Witeden <58004019+Witeden@users.noreply.github.com>
---
 oser_backend/settings/common.py        |  4 +-
 projects/MultiSelectFieldListFilter.py | 68 ++++++++++++++++++++++++++
 projects/admin.py                      | 30 ++++++++++--
 register/MultiSelectFieldListFilter.py | 68 ++++++++++++++++++++++++++
 register/admin.py                      | 23 ++++++++-
 visits/admin.py                        | 25 +++++++++-
 6 files changed, 209 insertions(+), 9 deletions(-)
 create mode 100644 projects/MultiSelectFieldListFilter.py
 create mode 100644 register/MultiSelectFieldListFilter.py

diff --git a/oser_backend/settings/common.py b/oser_backend/settings/common.py
index 7d62a94..84ccf82 100644
--- a/oser_backend/settings/common.py
+++ b/oser_backend/settings/common.py
@@ -255,10 +255,10 @@ EMAIL_PORT = 587
 EMAIL_USE_TLS = True
 
 # Toggle sandbox mode (when running in DEBUG mode)
-SENDGRID_SANDBOX_MODE_IN_DEBUG=False
+SENDGRID_SANDBOX_MODE_IN_DEBUG = False
 
 # echo to stdout or any other file-like object that is passed to the backend via the stream kwarg.
-SENDGRID_ECHO_TO_STDOUT=True
+SENDGRID_ECHO_TO_STDOUT = True
 
 # Mails app config
 
diff --git a/projects/MultiSelectFieldListFilter.py b/projects/MultiSelectFieldListFilter.py
new file mode 100644
index 0000000..28bbb89
--- /dev/null
+++ b/projects/MultiSelectFieldListFilter.py
@@ -0,0 +1,68 @@
+from django.contrib import admin
+from django.contrib.admin.utils import reverse_field_path
+from django.utils.translation import gettext_lazy as _
+
+
+class MultiSelectFieldListFilter(admin.FieldListFilter):
+    def __init__(self, field, request, params, model, model_admin, field_path):
+        self.lookup_kwarg = field_path + '__in'
+        self.lookup_kwarg_isnull = field_path + '__isnull'
+
+        super().__init__(field, request, params, model, model_admin, field_path)
+
+        self.lookup_val = self.used_parameters.get(self.lookup_kwarg, [])
+        if len(self.lookup_val) == 1 and self.lookup_val[0] == '':
+            self.lookup_val = []
+        self.lookup_val_isnull = self.used_parameters.get(
+            self.lookup_kwarg_isnull)
+
+        self.empty_value_display = model_admin.get_empty_value_display()
+        parent_model, reverse_path = reverse_field_path(model, field_path)
+        # Obey parent ModelAdmin queryset when deciding which options to show
+        if model == parent_model:
+            queryset = model_admin.get_queryset(request)
+        else:
+            queryset = parent_model._default_manager.all()
+        self.lookup_choices = queryset.distinct().order_by(
+            field.name).values_list(field.name, flat=True)
+
+    def expected_parameters(self):
+        return [self.lookup_kwarg, self.lookup_kwarg_isnull]
+
+    def choices(self, changelist):
+        yield {
+            'selected': not self.lookup_val and self.lookup_val_isnull is None,
+            'query_string': changelist.get_query_string(remove=[self.lookup_kwarg, self.lookup_kwarg_isnull]),
+            'display': _('All'),
+        }
+        include_none = False
+        for val in self.lookup_choices:
+            if val is None:
+                include_none = True
+                continue
+            val = str(val)
+
+            if val in self.lookup_val:
+                values = [v for v in self.lookup_val if v != val]
+            else:
+                values = self.lookup_val + [val]
+
+            if values:
+                yield {
+                    'selected': val in self.lookup_val,
+                    'query_string': changelist.get_query_string({self.lookup_kwarg: ','.join(values)}, [self.lookup_kwarg_isnull]),
+                    'display': val,
+                }
+            else:
+                yield {
+                    'selected': val in self.lookup_val,
+                    'query_string': changelist.get_query_string(remove=[self.lookup_kwarg]),
+                    'display': val,
+                }
+
+        if include_none:
+            yield {
+                'selected': bool(self.lookup_val_isnull),
+                'query_string': changelist.get_query_string({self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]),
+                'display': self.empty_value_display,
+            }
diff --git a/projects/admin.py b/projects/admin.py
index 5a65a46..e43c198 100644
--- a/projects/admin.py
+++ b/projects/admin.py
@@ -5,6 +5,8 @@ from django.contrib import admin
 from dynamicforms.views import download_multiple_forms_entries
 from dynamicforms.models import Form
 from .models import Edition, Participation, Project, EditionForm
+from django.contrib.admin import SimpleListFilter
+from profiles.models import Student
 
 
 @admin.register(Project)
@@ -37,7 +39,26 @@ class OrganizersInline(admin.TabularInline):
     extra = 0
 
 
-@admin.register(Edition)
+class SchoolFilter(admin.SimpleListFilter):
+    title = 'établissement'
+    parameter_name = 'profiles__school'
+
+    def lookups(self, request, model_admin):
+        list_of_school = []
+        query = Student.objects.values_list(
+            "school", flat=True).distinct()
+        for school in query:
+            list_of_school.append((school, school))
+        return list_of_school
+
+    def queryset(self, request, queryset):
+        if self.value():
+            emails = Student.objects.filter(
+                school=self.value()).values_list("user__email", flat=True)
+            return queryset.filter(user__email__in=emails)
+
+
+@ admin.register(Edition)
 class EditionAdmin(admin.ModelAdmin):
     """Admin panel for editions."""
 
@@ -73,7 +94,7 @@ class EditionAdmin(admin.ModelAdmin):
     num_cancelled.short_description = 'Annulés'
 
 
-@admin.register(EditionForm)
+@ admin.register(EditionForm)
 class EditionFormAdmin(admin.ModelAdmin):
     """Admin panel for edition forms."""
 
@@ -81,11 +102,12 @@ class EditionFormAdmin(admin.ModelAdmin):
     list_filter = ('edition', 'deadline',)
 
 
-@admin.register(Participation)
+@ admin.register(Participation)
 class ParticipationAdmin(admin.ModelAdmin):
     """Participation admin panel."""
 
     list_display = ('user', 'edition', 'submitted', 'state')
-    list_filter = ('edition', 'submitted', 'state',)
+    list_filter = (SchoolFilter,
+                   'edition', 'submitted', 'state',)
     readonly_fields = ('submitted',)
     search_fields = ('user__first_name', 'user__last_name', 'user__email',)
diff --git a/register/MultiSelectFieldListFilter.py b/register/MultiSelectFieldListFilter.py
new file mode 100644
index 0000000..28bbb89
--- /dev/null
+++ b/register/MultiSelectFieldListFilter.py
@@ -0,0 +1,68 @@
+from django.contrib import admin
+from django.contrib.admin.utils import reverse_field_path
+from django.utils.translation import gettext_lazy as _
+
+
+class MultiSelectFieldListFilter(admin.FieldListFilter):
+    def __init__(self, field, request, params, model, model_admin, field_path):
+        self.lookup_kwarg = field_path + '__in'
+        self.lookup_kwarg_isnull = field_path + '__isnull'
+
+        super().__init__(field, request, params, model, model_admin, field_path)
+
+        self.lookup_val = self.used_parameters.get(self.lookup_kwarg, [])
+        if len(self.lookup_val) == 1 and self.lookup_val[0] == '':
+            self.lookup_val = []
+        self.lookup_val_isnull = self.used_parameters.get(
+            self.lookup_kwarg_isnull)
+
+        self.empty_value_display = model_admin.get_empty_value_display()
+        parent_model, reverse_path = reverse_field_path(model, field_path)
+        # Obey parent ModelAdmin queryset when deciding which options to show
+        if model == parent_model:
+            queryset = model_admin.get_queryset(request)
+        else:
+            queryset = parent_model._default_manager.all()
+        self.lookup_choices = queryset.distinct().order_by(
+            field.name).values_list(field.name, flat=True)
+
+    def expected_parameters(self):
+        return [self.lookup_kwarg, self.lookup_kwarg_isnull]
+
+    def choices(self, changelist):
+        yield {
+            'selected': not self.lookup_val and self.lookup_val_isnull is None,
+            'query_string': changelist.get_query_string(remove=[self.lookup_kwarg, self.lookup_kwarg_isnull]),
+            'display': _('All'),
+        }
+        include_none = False
+        for val in self.lookup_choices:
+            if val is None:
+                include_none = True
+                continue
+            val = str(val)
+
+            if val in self.lookup_val:
+                values = [v for v in self.lookup_val if v != val]
+            else:
+                values = self.lookup_val + [val]
+
+            if values:
+                yield {
+                    'selected': val in self.lookup_val,
+                    'query_string': changelist.get_query_string({self.lookup_kwarg: ','.join(values)}, [self.lookup_kwarg_isnull]),
+                    'display': val,
+                }
+            else:
+                yield {
+                    'selected': val in self.lookup_val,
+                    'query_string': changelist.get_query_string(remove=[self.lookup_kwarg]),
+                    'display': val,
+                }
+
+        if include_none:
+            yield {
+                'selected': bool(self.lookup_val_isnull),
+                'query_string': changelist.get_query_string({self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]),
+                'display': self.empty_value_display,
+            }
diff --git a/register/admin.py b/register/admin.py
index f18b03b..95a4138 100644
--- a/register/admin.py
+++ b/register/admin.py
@@ -2,6 +2,26 @@
 
 from django.contrib import admin
 from .models import Registration
+from profiles.models import Student
+
+
+class SchoolFilter(admin.SimpleListFilter):
+    title = 'établissement'
+    parameter_name = 'profiles__school'
+
+    def lookups(self, request, model_admin):
+        list_of_school = []
+        query = Student.objects.values_list(
+            "school", flat=True).distinct()
+        for school in query:
+            list_of_school.append((school, school))
+        return list_of_school
+
+    def queryset(self, request, queryset):
+        if self.value():
+            emails = Student.objects.filter(
+                school=self.value()).values_list("user__email", flat=True)
+            return queryset.filter(email__in=emails)
 
 
 @admin.register(Registration)
@@ -10,4 +30,5 @@ class RegistrationAdmin(admin.ModelAdmin):
 
     list_display = ('last_name', 'first_name', 'submitted')
     readonly_fields = ('submitted',)
-    list_filter = ('submitted', 'validated')
+    list_filter = (SchoolFilter,
+                   'submitted', 'validated')
diff --git a/visits/admin.py b/visits/admin.py
index 7056c6d..a7b6392 100644
--- a/visits/admin.py
+++ b/visits/admin.py
@@ -13,6 +13,25 @@ from profiles.models import Student
 # Register your models here.
 
 
+class SchoolFilter(admin.SimpleListFilter):
+    title = 'établissement'
+    parameter_name = 'profiles__school'
+
+    def lookups(self, request, model_admin):
+        list_of_school = []
+        query = Student.objects.values_list(
+            "school", flat=True).distinct()
+        for school in query:
+            list_of_school.append((school, school))
+        return list_of_school
+
+    def queryset(self, request, queryset):
+        if self.value():
+            emails = Student.objects.filter(
+                school=self.value()).values_list("user__email", flat=True)
+            return queryset.filter(user__email__in=emails)
+
+
 class RegistrationsOpenFilter(admin.SimpleListFilter):
     """Custom filter to filter visits by their registration openness.
 
@@ -133,8 +152,10 @@ reject_selected_participations.short_description = (
 class ParticipationAdmin(admin.ModelAdmin):
     """Admin panel for visit participations."""
 
-    list_display = ('submitted', 'visit', 'user_link', 'school', 'accepted', 'present')
-    list_filter = ('submitted', 'accepted', 'present')
+    list_display = ('submitted', 'visit', 'user_link', 'accepted', 'present')
+    list_filter = (SchoolFilter, 'submitted', 'accepted', 'present')
+
+
     actions = [accept_selected_participations, reject_selected_participations]
 
     def user_link(self, participation: Participation):
-- 
GitLab