diff --git a/oser_backend/settings/common.py b/oser_backend/settings/common.py
index 7d62a94f890549cc8797de087956b476057a91cc..84ccf8246385333d1cfbb3d32340090720319435 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 0000000000000000000000000000000000000000..28bbb89e6eae617f147881cdc9e5a5bce7a5a420
--- /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 5a65a46fdfe9f65c4e90bd4f5293542db2ec39bb..e43c198933a4a26170dc30281e0b5bcfc49817f1 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 0000000000000000000000000000000000000000..28bbb89e6eae617f147881cdc9e5a5bce7a5a420
--- /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 f18b03b65b8f1151972ff51fd6255696c9d3246e..95a4138df47b10c89c2dac2604b074f5fe30404a 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 7056c6da996d4be842a76c089466079d86ef9faf..a7b6392529941962e2bb2ee3c755c27efc5dff8a 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):