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