diff --git a/biscuit/core/anonymizers.py b/biscuit/core/anonymizers.py
index 03d4f3bf3a8597a3a61a9683878eae1a9f1637ea..9a063a819a0f5dac62353c5e2a5dfd1a6ca088a3 100644
--- a/biscuit/core/anonymizers.py
+++ b/biscuit/core/anonymizers.py
@@ -18,6 +18,9 @@ class PersonAnonymizer(BaseAnonymizer):
         ('phone_number', ''),
         ('mobile_number', ''),
         ('email', faker.email),
-        ('date_of_birth', lambda **kwargs: faker.date_of_birth(minimum_age=8, maximum_age=66, **kwargs)),
-        ('photo', '')
+        (
+            'date_of_birth',
+            lambda **kwargs: faker.date_of_birth(minimum_age=8, maximum_age=66, **kwargs),
+        ),
+        ('photo', ''),
     ]
diff --git a/biscuit/core/cronjobs.py b/biscuit/core/cronjobs.py
index 229e017cf840a2eabfa9b624aa47e28cac636686..4c9d81ebb412fd986830f0bbef9dd03e196d2bb4 100644
--- a/biscuit/core/cronjobs.py
+++ b/biscuit/core/cronjobs.py
@@ -7,7 +7,9 @@ class Backup(CronJobBase):
     RUN_AT_TIMES = settings.DBBACKUP_CRON_TIMES
     RETRY_AFTER_FAILURE_MINS = 5
 
-    schedule = Schedule(run_at_times=RUN_AT_TIMES, retry_after_failure_mins=RETRY_AFTER_FAILURE_MINS)
+    schedule = Schedule(
+        run_at_times=RUN_AT_TIMES, retry_after_failure_mins=RETRY_AFTER_FAILURE_MINS
+    )
     code = 'biscuit.core.Backup'
 
     def do(self):
diff --git a/biscuit/core/decorators.py b/biscuit/core/decorators.py
index 732f120958924f683f05482bd57661cc503f2882..7c0feb30ef05ea88431bc41ef210ceecd1003921 100644
--- a/biscuit/core/decorators.py
+++ b/biscuit/core/decorators.py
@@ -3,6 +3,5 @@ from django.contrib.auth.decorators import user_passes_test
 
 
 def admin_required(function: Callable = None) -> Callable:
-    actual_decorator = user_passes_test(
-        lambda u: u.is_active and u.is_superuser)
+    actual_decorator = user_passes_test(lambda u: u.is_active and u.is_superuser)
     return actual_decorator(function)
diff --git a/biscuit/core/forms.py b/biscuit/core/forms.py
index c7e813fdec59f29777da3670fb1aeee377850b3f..119730401b574eebef1657b3700c34f16d4b4f28 100644
--- a/biscuit/core/forms.py
+++ b/biscuit/core/forms.py
@@ -10,9 +10,7 @@ class PersonAccountForm(forms.ModelForm):
     class Meta:
         model = Person
         fields = ['last_name', 'first_name', 'user']
-        widgets = {
-            'user': Select2Widget
-        }
+        widgets = {'user': Select2Widget}
 
     new_user = forms.CharField(required=False)
 
@@ -26,51 +24,74 @@ class PersonAccountForm(forms.ModelForm):
 
         if self.cleaned_data.get('new_user', None):
             if self.cleaned_data.get('user', None):
-                self.add_error('new_user', _(
-                    'You cannot set a new username when also selecting an existing user.'))
+                self.add_error(
+                    'new_user',
+                    _('You cannot set a new username when also selecting an existing user.'),
+                )
             elif User.objects.filter(username=self.cleaned_data['new_user']).exists():
                 self.add_error('new_user', _('This username is already in use.'))
             else:
-                new_user_obj = User.objects.create_user(self.cleaned_data['new_user'],
-                                                        self.instance.email,
-                                                        first_name=self.instance.first_name,
-                                                        last_name=self.instance.last_name)
+                new_user_obj = User.objects.create_user(
+                    self.cleaned_data['new_user'],
+                    self.instance.email,
+                    first_name=self.instance.first_name,
+                    last_name=self.instance.last_name,
+                )
 
                 self.cleaned_data['user'] = new_user_obj
 
 
 PersonsAccountsFormSet = forms.modelformset_factory(
-    Person, form=PersonAccountForm, max_num=0, extra=0)
+    Person, form=PersonAccountForm, max_num=0, extra=0
+)
 
 
 class EditPersonForm(forms.ModelForm):
     class Meta:
         model = Person
-        fields = ['user', 'is_active', 'first_name', 'last_name', 'additional_name', 'short_name', 'street', 'housenumber',
-                  'postal_code', 'place', 'phone_number', 'mobile_number', 'email', 'date_of_birth', 'sex', 'photo', 'photo_cropping']
-        widgets = {
-            'user': Select2Widget
-        }
+        fields = [
+            'user',
+            'is_active',
+            'first_name',
+            'last_name',
+            'additional_name',
+            'short_name',
+            'street',
+            'housenumber',
+            'postal_code',
+            'place',
+            'phone_number',
+            'mobile_number',
+            'email',
+            'date_of_birth',
+            'sex',
+            'photo',
+            'photo_cropping',
+        ]
+        widgets = {'user': Select2Widget}
 
     new_user = forms.CharField(
-        required=False,
-        label=_('New user'),
-        help_text=_('Create a new account'))
+        required=False, label=_('New user'), help_text=_('Create a new account')
+    )
 
     def clean(self) -> None:
         User = get_user_model()
 
         if self.cleaned_data.get('new_user', None):
             if self.cleaned_data.get('user', None):
-                self.add_error('new_user', _(
-                    'You cannot set a new username when also selecting an existing user.'))
+                self.add_error(
+                    'new_user',
+                    _('You cannot set a new username when also selecting an existing user.'),
+                )
             elif User.objects.filter(username=self.cleaned_data['new_user']).exists():
                 self.add_error('new_user', _('This username is already in use.'))
             else:
-                new_user_obj = User.objects.create_user(self.cleaned_data['new_user'],
-                                                        self.instance.email,
-                                                        first_name=self.instance.first_name,
-                                                        last_name=self.instance.last_name)
+                new_user_obj = User.objects.create_user(
+                    self.cleaned_data['new_user'],
+                    self.instance.email,
+                    first_name=self.instance.first_name,
+                    last_name=self.instance.last_name,
+                )
 
                 self.cleaned_data['user'] = new_user_obj
 
@@ -80,11 +101,26 @@ class EditGroupForm(forms.ModelForm):
         model = Group
         fields = ['name', 'short_name', 'members', 'owners', 'parent_groups']
         widgets = {
-            'members': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains']),
-            'owners': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains']),
-            'parent_groups': ModelSelect2MultipleWidget(search_fields=['name__icontains', 'short_name__icontains']),
+            'members': ModelSelect2MultipleWidget(
+                search_fields=[
+                    'first_name__icontains',
+                    'last_name__icontains',
+                    'short_name__icontains',
+                ]
+            ),
+            'owners': ModelSelect2MultipleWidget(
+                search_fields=[
+                    'first_name__icontains',
+                    'last_name__icontains',
+                    'short_name__icontains',
+                ]
+            ),
+            'parent_groups': ModelSelect2MultipleWidget(
+                search_fields=['name__icontains', 'short_name__icontains']
+            ),
         }
 
+
 class EditSchoolForm(forms.ModelForm):
     class Meta:
         model = School
diff --git a/biscuit/core/menus.py b/biscuit/core/menus.py
index a3ba09e29e7a459c7b1a25a20c35cb425c4dcb17..f4afc55773b106cb26b4fcc408be9ebc061f4149 100644
--- a/biscuit/core/menus.py
+++ b/biscuit/core/menus.py
@@ -11,102 +11,116 @@ MENUS = {
                 {
                     'name': _('Stop impersonation'),
                     'url': 'impersonate-stop',
-                    'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.is_impersonate']
+                    'validators': [
+                        'menu_generator.validators.is_authenticated',
+                        'biscuit.core.util.core_helpers.is_impersonate',
+                    ],
                 },
                 {
                     'name': _('Login'),
                     'url': settings.LOGIN_URL,
-                    'validators': ['menu_generator.validators.is_anonymous']
+                    'validators': ['menu_generator.validators.is_anonymous'],
                 },
                 {
                     'name': _('Logout'),
                     'url': 'logout',
-                    'validators': ['menu_generator.validators.is_authenticated']
+                    'validators': ['menu_generator.validators.is_authenticated'],
                 },
                 {
                     'name': _('Two factor auth'),
                     'url': 'two_factor:profile',
-                    'validators': ['menu_generator.validators.is_authenticated', lambda request: 'two_factor' in settings.INSTALLED_APPS]
-                }
-            ]
+                    'validators': [
+                        'menu_generator.validators.is_authenticated',
+                        lambda request: 'two_factor' in settings.INSTALLED_APPS,
+                    ],
+                },
+            ],
         },
         {
             'name': _('Admin'),
             'url': '#',
-            'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'],
+            'validators': [
+                'menu_generator.validators.is_authenticated',
+                'menu_generator.validators.is_superuser',
+            ],
             'submenu': [
                 {
                     'name': _('Data management'),
                     'url': 'data_management',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
+                    'validators': [
+                        'menu_generator.validators.is_authenticated',
+                        'menu_generator.validators.is_superuser',
+                    ],
                 },
                 {
                     'name': _('System status'),
                     'url': 'system_status',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
+                    'validators': [
+                        'menu_generator.validators.is_authenticated',
+                        'menu_generator.validators.is_superuser',
+                    ],
                 },
                 {
                     'name': _('Impersonation'),
                     'url': 'impersonate-list',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
+                    'validators': [
+                        'menu_generator.validators.is_authenticated',
+                        'menu_generator.validators.is_superuser',
+                    ],
                 },
                 {
                     'name': _('Manage school'),
                     'url': 'school_management',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
-                }
-            ]
+                    'validators': [
+                        'menu_generator.validators.is_authenticated',
+                        'menu_generator.validators.is_superuser',
+                    ],
+                },
+            ],
         },
         {
             'name': _('People'),
             'url': '#',
             'root': True,
-            'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'],
+            'validators': [
+                'menu_generator.validators.is_authenticated',
+                'biscuit.core.util.core_helpers.has_person',
+            ],
             'submenu': [
                 {
                     'name': _('Persons'),
                     'url': 'persons',
-                    'validators': ['menu_generator.validators.is_authenticated']
+                    'validators': ['menu_generator.validators.is_authenticated'],
                 },
                 {
                     'name': _('Groups'),
                     'url': 'groups',
-                    'validators': ['menu_generator.validators.is_authenticated']
+                    'validators': ['menu_generator.validators.is_authenticated'],
                 },
                 {
                     'name': _('Persons and accounts'),
                     'url': 'persons_accounts',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
-                }
-            ]
-        }
+                    'validators': [
+                        'menu_generator.validators.is_authenticated',
+                        'menu_generator.validators.is_superuser',
+                    ],
+                },
+            ],
+        },
     ],
     'FOOTER_MENU_CORE': [
         {
             'name': _('BiscuIT Software'),
             'url': '#',
             'submenu': [
-                {
-                    'name': _('Website'),
-                    'url': 'https://biscuit.edugit.org/'
-                },
-                {
-                    'name': 'Teckids e.V.',
-                    'url': 'https://www.teckids.org/'
-                }
-            ]
+                {'name': _('Website'), 'url': 'https://biscuit.edugit.org/'},
+                {'name': 'Teckids e.V.', 'url': 'https://www.teckids.org/'},
+            ],
         },
     ],
-    'DATA_MANAGEMENT_MENU': [
-    ],
+    'DATA_MANAGEMENT_MENU': [],
     'SCHOOL_MANAGEMENT_MENU': [
-        {
-            'name': _('Edit school information'),
-            'url': 'edit_school_information',
-        },
-        {
-            'name': _('Edit school term'),
-            'url': 'edit_school_term',
-        }
+        {'name': _('Edit school information'), 'url': 'edit_school_information',},
+        {'name': _('Edit school term'), 'url': 'edit_school_term',},
     ],
 }
diff --git a/biscuit/core/mixins.py b/biscuit/core/mixins.py
index 9ec842a5ea99b793c688a63a9a2e8c1d5d9203b4..a5d535ac2cedcbd76e25e8c042d235fbc15b28e8 100644
--- a/biscuit/core/mixins.py
+++ b/biscuit/core/mixins.py
@@ -67,6 +67,7 @@ class ExtensibleModel(object):
 
         cls._safe_add(func, func.__name__)
 
+
 class CRUDMixin(models.Model):
     class Meta:
         abstract = True
@@ -78,8 +79,5 @@ class CRUDMixin(models.Model):
         content_type = ContentType.objects.get_for_model(self)
 
         return CRUDEvent.objects.filter(
-            object_id=self.pk,
-            content_type=content_type
-        ).select_related(
-            'user'
-        )
+            object_id=self.pk, content_type=content_type
+        ).select_related('user')
diff --git a/biscuit/core/models.py b/biscuit/core/models.py
index ae3f5950a570f17b8e8cec7ad96be167bca2d79a..a659136d97cc584643eae3cc715166870b678159 100644
--- a/biscuit/core/models.py
+++ b/biscuit/core/models.py
@@ -21,6 +21,7 @@ class ThemeSettings(dbsettings.Group):
     colour_light = dbsettings.StringValue(default='#f8f9fa')
     colour_dark = dbsettings.StringValue(default='#343a40')
 
+
 theme_settings = ThemeSettings('Global theme settings')
 
 
@@ -32,8 +33,11 @@ class School(models.Model):
     """
 
     name = models.CharField(verbose_name=_('Name'), max_length=30)
-    name_official = models.CharField(verbose_name=_('Official name'), max_length=200, help_text=_(
-        'Official name of the school, e.g. as given by supervisory authority'))
+    name_official = models.CharField(
+        verbose_name=_('Official name'),
+        max_length=200,
+        help_text=_('Official name of the school, e.g. as given by supervisory authority'),
+    )
 
     logo = ImageCropField(verbose_name=_('School logo'), blank=True, null=True)
     logo_cropping = ImageRatioField('logo', '600x600', size_warning=True)
@@ -51,13 +55,10 @@ class SchoolTerm(models.Model):
     be linked to.
     """
 
-    caption = models.CharField(verbose_name=_('Visible caption of the term'),
-                               max_length=30)
+    caption = models.CharField(verbose_name=_('Visible caption of the term'), max_length=30)
 
-    date_start = models.DateField(verbose_name=_(
-        'Effective start date of term'), null=True)
-    date_end = models.DateField(verbose_name=_(
-        'Effective end date of term'), null=True)
+    date_start = models.DateField(verbose_name=_('Effective start date of term'), null=True)
+    date_end = models.DateField(verbose_name=_('Effective end date of term'), null=True)
 
     current = models.NullBooleanField(default=None, unique=True)
 
@@ -75,54 +76,51 @@ class Person(models.Model, ExtensibleModel):
     class Meta:
         ordering = ['last_name', 'first_name']
 
-    SEX_CHOICES = [
-        ('f', _('female')),
-        ('m', _('male'))
-    ]
+    SEX_CHOICES = [('f', _('female')), ('m', _('male'))]
 
     user = models.OneToOneField(
-        get_user_model(), on_delete=models.SET_NULL, blank=True, null=True,
-        related_name='person')
-    is_active = models.BooleanField(
-        verbose_name=_('Is person active?'), default=True)
+        get_user_model(), on_delete=models.SET_NULL, blank=True, null=True, related_name='person'
+    )
+    is_active = models.BooleanField(verbose_name=_('Is person active?'), default=True)
 
     first_name = models.CharField(verbose_name=_('First name'), max_length=30)
     last_name = models.CharField(verbose_name=_('Last name'), max_length=30)
-    additional_name = models.CharField(verbose_name=_(
-        'Additional name(s)'), max_length=30, blank=True)
+    additional_name = models.CharField(
+        verbose_name=_('Additional name(s)'), max_length=30, blank=True
+    )
 
-    short_name = models.CharField(verbose_name=_(
-        'Short name'), max_length=5, blank=True, null=True, unique=True)
+    short_name = models.CharField(
+        verbose_name=_('Short name'), max_length=5, blank=True, null=True, unique=True
+    )
 
-    street = models.CharField(verbose_name=_(
-        'Street'), max_length=30, blank=True)
-    housenumber = models.CharField(verbose_name=_(
-        'Street number'), max_length=10, blank=True)
-    postal_code = models.CharField(verbose_name=_(
-        'Postal code'), max_length=5, blank=True)
-    place = models.CharField(verbose_name=_(
-        'Place'), max_length=30, blank=True)
+    street = models.CharField(verbose_name=_('Street'), max_length=30, blank=True)
+    housenumber = models.CharField(verbose_name=_('Street number'), max_length=10, blank=True)
+    postal_code = models.CharField(verbose_name=_('Postal code'), max_length=5, blank=True)
+    place = models.CharField(verbose_name=_('Place'), max_length=30, blank=True)
 
     phone_number = PhoneNumberField(verbose_name=_('Home phone'), blank=True)
-    mobile_number = PhoneNumberField(
-        verbose_name=_('Mobile phone'), blank=True)
+    mobile_number = PhoneNumberField(verbose_name=_('Mobile phone'), blank=True)
 
     email = models.EmailField(verbose_name=_('E-mail address'), blank=True)
 
-    date_of_birth = models.DateField(
-        verbose_name=_('Date of birth'), blank=True, null=True)
-    sex = models.CharField(verbose_name=_(
-        'Sex'), max_length=1, choices=SEX_CHOICES, blank=True)
+    date_of_birth = models.DateField(verbose_name=_('Date of birth'), blank=True, null=True)
+    sex = models.CharField(verbose_name=_('Sex'), max_length=1, choices=SEX_CHOICES, blank=True)
 
     photo = ImageCropField(verbose_name=_('Photo'), blank=True, null=True)
     photo_cropping = ImageRatioField('photo', '600x800', size_warning=True)
 
-    import_ref = models.CharField(verbose_name=_(
-        'Reference ID of import source'), max_length=64,
-        blank=True, null=True, editable=False, unique=True)
+    import_ref = models.CharField(
+        verbose_name=_('Reference ID of import source'),
+        max_length=64,
+        blank=True,
+        null=True,
+        editable=False,
+        unique=True,
+    )
 
-    guardians = models.ManyToManyField('self', verbose_name=_('Guardians / Parents'),
-                                       symmetrical=False, related_name='children')
+    guardians = models.ManyToManyField(
+        'self', verbose_name=_('Guardians / Parents'), symmetrical=False, related_name='children'
+    )
 
     primary_group = models.ForeignKey('Group', models.SET_NULL, null=True)
 
@@ -143,8 +141,7 @@ class Person(models.Model, ExtensibleModel):
         if it can't find one.
         """
 
-        group, created = Group.objects.get_or_create(short_name=value,
-                                                     defaults={'name': value})
+        group, created = Group.objects.get_or_create(short_name=value, defaults={'name': value})
         self.primary_group = group
 
     @property
@@ -163,16 +160,19 @@ class Group(models.Model, ExtensibleModel):
     class Meta:
         ordering = ['short_name', 'name']
 
-    name = models.CharField(verbose_name=_(
-        'Long name of group'), max_length=60, unique=True)
-    short_name = models.CharField(verbose_name=_(
-        'Short name of group'), max_length=16, unique=True)
+    name = models.CharField(verbose_name=_('Long name of group'), max_length=60, unique=True)
+    short_name = models.CharField(verbose_name=_('Short name of group'), max_length=16, unique=True)
 
     members = models.ManyToManyField('Person', related_name='member_of')
     owners = models.ManyToManyField('Person', related_name='owner_of')
 
-    parent_groups = models.ManyToManyField('self', related_name='child_groups',
-                                           symmetrical=False, verbose_name=_('Parent groups'), blank=True)
+    parent_groups = models.ManyToManyField(
+        'self',
+        related_name='child_groups',
+        symmetrical=False,
+        verbose_name=_('Parent groups'),
+        blank=True,
+    )
 
     def __str__(self) -> str:
         return '%s (%s)' % (self.name, self.short_name)
diff --git a/biscuit/core/settings.py b/biscuit/core/settings.py
index f70853445f219c1492daacc0ac4fdd904e41a217..66441e7143637d4f45d57cb76639599f23bd76ed 100644
--- a/biscuit/core/settings.py
+++ b/biscuit/core/settings.py
@@ -20,7 +20,7 @@ for directory in DIRS_FOR_DYNACONF:
 
 _settings = LazySettings(
     ENVVAR_PREFIX_FOR_DYNACONF=ENVVAR_PREFIX_FOR_DYNACONF,
-    SETTINGS_FILE_FOR_DYNACONF=SETTINGS_FILE_FOR_DYNACONF
+    SETTINGS_FILE_FOR_DYNACONF=SETTINGS_FILE_FOR_DYNACONF,
 )
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -36,7 +36,7 @@ DEBUG_TOOLBAR_CONFIG = {
     'RENDER_PANELS': True,
     'SHOW_COLLAPSED': True,
     'JQUERY_URL': '',
-    'SHOW_TOOLBAR_CALLBACK': 'biscuit.core.util.core_helpers.dt_show_toolbar'
+    'SHOW_TOOLBAR_CALLBACK': 'biscuit.core.util.core_helpers.dt_show_toolbar',
 }
 
 ALLOWED_HOSTS = _settings.get('http.allowed_hosts', [])
@@ -75,7 +75,7 @@ INSTALLED_APPS = [
     'otp_yubikey',
     'biscuit.core',
     'impersonate',
-    'two_factor'
+    'two_factor',
 ]
 
 INSTALLED_APPS += get_app_packages()
@@ -84,7 +84,7 @@ STATICFILES_FINDERS = [
     'django.contrib.staticfiles.finders.FileSystemFinder',
     'django.contrib.staticfiles.finders.AppDirectoriesFinder',
     'django_yarnpkg.finders.NodeModulesFinder',
-    'sass_processor.finders.CssFinder'
+    'sass_processor.finders.CssFinder',
 ]
 
 
@@ -121,7 +121,7 @@ TEMPLATES = [
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
                 'maintenance_mode.context_processors.maintenance_mode',
-                'settings_context_processor.context_processors.settings'
+                'settings_context_processor.context_processors.settings',
             ],
         },
     },
@@ -148,7 +148,7 @@ DATABASES = {
         'PASSWORD': _settings.get('database.password', None),
         'HOST': _settings.get('database.host', '127.0.0.1'),
         'PORT': _settings.get('database.port', '5432'),
-        'ATOMIC_REQUESTS': True
+        'ATOMIC_REQUESTS': True,
     }
 }
 
@@ -156,7 +156,7 @@ if _settings.get('caching.memcached.enabled', True):
     CACHES = {
         'default': {
             'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
-            'LOCATION': _settings.get('caching.memcached.address', '127.0.0.1:11211')
+            'LOCATION': _settings.get('caching.memcached.address', '127.0.0.1:11211'),
         }
     }
 
@@ -164,18 +164,10 @@ if _settings.get('caching.memcached.enabled', True):
 # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
 
 AUTH_PASSWORD_VALIDATORS = [
-    {
-        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
-    },
+    {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},
+    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',},
+    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',},
+    {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',},
 ]
 
 # Authentication backends are dynamically populated
@@ -200,7 +192,7 @@ if _settings.get('ldap.uri', None):
     AUTH_LDAP_USER_SEARCH = LDAPSearch(
         _settings.get('ldap.users.base'),
         ldap.SCOPE_SUBTREE,
-        _settings.get('ldap.users.filter', '(uid=%(user)s)')
+        _settings.get('ldap.users.filter', '(uid=%(user)s)'),
     )
 
     # Mapping of LDAP attributes to Django model fields
@@ -241,42 +233,33 @@ STATIC_ROOT = _settings.get('static.root', os.path.join(BASE_DIR, 'static'))
 MEDIA_ROOT = _settings.get('media.root', os.path.join(BASE_DIR, 'media'))
 NODE_MODULES_ROOT = _settings.get('node_modules.root', os.path.join(BASE_DIR, 'node_modules'))
 
-YARN_INSTALLED_APPS = [
-    'bootstrap',
-    'font-awesome',
-    'jquery',
-    'popper.js',
-    'datatables',
-    'select2'
-]
+YARN_INSTALLED_APPS = ['bootstrap', 'font-awesome', 'jquery', 'popper.js', 'datatables', 'select2']
 
 JS_URL = _settings.get('js_assets.url', STATIC_URL)
-JS_ROOT = _settings.get('js_assets.root', NODE_MODULES_ROOT+'/node_modules')
+JS_ROOT = _settings.get('js_assets.root', NODE_MODULES_ROOT + '/node_modules')
 
-FONT_AWESOME = {'url': JS_URL+'/font-awesome/css/font-awesome.min.css'}
+FONT_AWESOME = {'url': JS_URL + '/font-awesome/css/font-awesome.min.css'}
 
 BOOTSTRAP4 = {
-    'css_url': JS_URL+'/bootstrap/dist//css/bootstrap.min.css',
-    'javascript_url': JS_URL+'/bootstrap/dist/js/bootstrap.min.js',
-    'jquery_url': JS_URL+'/jquery/dist/jquery.min.js',
-    'popper_url': JS_URL+'/popper.js/dist/umd/popper.min.js',
+    'css_url': JS_URL + '/bootstrap/dist//css/bootstrap.min.css',
+    'javascript_url': JS_URL + '/bootstrap/dist/js/bootstrap.min.js',
+    'jquery_url': JS_URL + '/jquery/dist/jquery.min.js',
+    'popper_url': JS_URL + '/popper.js/dist/umd/popper.min.js',
     'include_jquery': True,
     'include_popper': True,
-    'javascript_in_head': True
+    'javascript_in_head': True,
 }
 
-SELECT2_CSS = JS_URL+'/select2/dist/css/select2.min.css'
-SELECT2_JS = JS_URL+'/select2/dist/js/select2.min.js'
-SELECT2_I18N_PATH = JS_URL+'/select2/dist/js/i18n'
+SELECT2_CSS = JS_URL + '/select2/dist/css/select2.min.css'
+SELECT2_JS = JS_URL + '/select2/dist/js/select2.min.js'
+SELECT2_I18N_PATH = JS_URL + '/select2/dist/js/i18n'
 
 ANY_JS = {
-    'DataTables': {
-        'js_url': JS_URL+'/datatables/media/js/jquery.dataTables.min.js'
-    },
+    'DataTables': {'js_url': JS_URL + '/datatables/media/js/jquery.dataTables.min.js'},
     'DataTables-Bootstrap4': {
-        'css_url': JS_URL+'/datatables/media/css/dataTables.bootstrap4.min.css',
-        'js_url': JS_URL+'/datatables/media/js/dataTables.bootstrap4.min.js'
-    }
+        'css_url': JS_URL + '/datatables/media/css/dataTables.bootstrap4.min.css',
+        'js_url': JS_URL + '/datatables/media/js/dataTables.bootstrap4.min.js',
+    },
 }
 
 SASS_PROCESSOR_AUTO_INCLUDE = False
@@ -284,9 +267,7 @@ SASS_PROCESSOR_CUSTOM_FUNCTIONS = {
     'get-colour': 'biscuit.core.util.sass_helpers.get_colour',
     'get-theme-setting': 'biscuit.core.util.sass_helpers.get_theme_setting',
 }
-SASS_PROCESSOR_INCLUDE_DIRS = [
-    _settings.get('bootstrap.sass_path', JS_ROOT+'/bootstrap/scss/')
-]
+SASS_PROCESSOR_INCLUDE_DIRS = [_settings.get('bootstrap.sass_path', JS_ROOT + '/bootstrap/scss/')]
 
 ADMINS = _settings.get('contact.admins', [])
 SERVER_EMAIL = _settings.get('contact.from', 'root@localhost')
@@ -307,30 +288,25 @@ TEMPLATE_VISIBLE_SETTINGS = ['ADMINS', 'DEBUG']
 
 MAINTENANCE_MODE = _settings.get('maintenance.enabled', None)
 MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = _settings.get(
-    'maintenance.ignore_ips', _settings.get('maintenance.internal_ips', []))
+    'maintenance.ignore_ips', _settings.get('maintenance.internal_ips', [])
+)
 MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = 'ipware.ip.get_ip'
 MAINTENANCE_MODE_IGNORE_SUPERUSER = True
-MAINTENANCE_MODE_STATE_FILE_PATH = _settings.get('maintenance.statefile', 'maintenance_mode_state.txt')
+MAINTENANCE_MODE_STATE_FILE_PATH = _settings.get(
+    'maintenance.statefile', 'maintenance_mode_state.txt'
+)
 
-IMPERSONATE = {
-    'USE_HTTP_REFERER': True,
-    'REQUIRE_SUPERUSER': True,
-    'ALLOW_SUPERUSER': True
-}
+IMPERSONATE = {'USE_HTTP_REFERER': True, 'REQUIRE_SUPERUSER': True, 'ALLOW_SUPERUSER': True}
 
 DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap4.html"
 
 DBBACKUP_STORAGE = _settings.get('backup.storage', 'django.core.files.storage.FileSystemStorage')
-DBBACKUP_STORAGE_OPTIONS = {
-    'location': _settings.get('backup.location', '/var/backups/biscuit')
-}
+DBBACKUP_STORAGE_OPTIONS = {'location': _settings.get('backup.location', '/var/backups/biscuit')}
 DBBACKUP_CLEANUP_KEEP = _settings.get('backup.keep.database', 10)
 DBBACKUP_CLEANUP_KEEP_MEDIA = _settings.get('backup.keep.media', 10)
 DBBACKUP_CRON_TIMES = _settings.get('backup.times', None) or ['03:57']
 
-CRON_CLASSES = [
-    'biscuit.core.cronjobs.Backup'
-]
+CRON_CLASSES = ['biscuit.core.cronjobs.Backup']
 
 ANONYMIZE_ENABLED = _settings.get('maintenance.anonymisable', True)
 
@@ -343,7 +319,10 @@ if _settings.get('2fa.sms.enabled', False):
     TWO_FACTOR_SMS_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
 
 if _settings.get('2fa.twilio.sid', None):
-    MIDDLEWARE.insert(MIDDLEWARE.index('django_otp.middleware.OTPMiddleware')+1, 'two_factor.middleware.threadlocals.ThreadLocals')
+    MIDDLEWARE.insert(
+        MIDDLEWARE.index('django_otp.middleware.OTPMiddleware') + 1,
+        'two_factor.middleware.threadlocals.ThreadLocals',
+    )
     TWILIO_SID = _settings.get('2fa.twilio.sid')
     TWILIO_TOKEN = _settings.get('2fa.twilio.token')
     TWILIO_CALLER_ID = _settings.get('2fa.twilio.callerid')
diff --git a/biscuit/core/tests/browser/test_selenium.py b/biscuit/core/tests/browser/test_selenium.py
index fcdde21653e644fc90461e5e52f4a3a506ffd7b1..21705ba508ee81825c7cbfecadbd6c09676fb9ab 100644
--- a/biscuit/core/tests/browser/test_selenium.py
+++ b/biscuit/core/tests/browser/test_selenium.py
@@ -7,9 +7,12 @@ from django.test.selenium import SeleniumTestCase, SeleniumTestCaseBase
 from django.urls import reverse
 
 SeleniumTestCaseBase.external_host = os.environ.get('TEST_HOST', '') or None
-SeleniumTestCaseBase.browsers = list(filter(bool, os.environ.get('TEST_SELENIUM_BROWSERS', '').split(',')))
+SeleniumTestCaseBase.browsers = list(
+    filter(bool, os.environ.get('TEST_SELENIUM_BROWSERS', '').split(','))
+)
 SeleniumTestCaseBase.selenium_hub = os.environ.get('TEST_SELENIUM_HUB', '') or None
 
+
 class SeleniumTests(SeleniumTestCase):
     serialized_rollback = True
 
@@ -18,7 +21,9 @@ class SeleniumTests(SeleniumTestCase):
         screenshot_path = os.environ.get('TEST_SCREENSHOT_PATH', None)
         if screenshot_path:
             os.makedirs(os.path.join(screenshot_path, cls.browser), exist_ok=True)
-            return cls.selenium.save_screenshot(os.path.join(screenshot_path, cls.browser, filename))
+            return cls.selenium.save_screenshot(
+                os.path.join(screenshot_path, cls.browser, filename)
+            )
         else:
             return False
 
@@ -47,9 +52,7 @@ class SeleniumTests(SeleniumTestCase):
         self._screenshot('login_default_superuser_filled.png')
 
         # Submit form by clicking django-two-factor-auth's Next button
-        self.selenium.find_element_by_xpath(
-            '//button[contains(text(), "Next")]'
-        ).click()
+        self.selenium.find_element_by_xpath('//button[contains(text(), "Next")]').click()
         self._screenshot('login_default_superuser_submitted.png')
 
         # Should redirect away from login page and not put up an alert about wrong credentials
diff --git a/biscuit/core/tests/models/test_person.py b/biscuit/core/tests/models/test_person.py
index e454d3b60709498564c1c005242694b08ad805bb..328f459b3b5859ac422a08cb61acbe1ea31715c5 100644
--- a/biscuit/core/tests/models/test_person.py
+++ b/biscuit/core/tests/models/test_person.py
@@ -5,9 +5,6 @@ from biscuit.core.models import Person
 
 @pytest.mark.django_db
 def test_full_name():
-    _person = Person.objects.create(
-            first_name='Jane',
-            last_name='Doe'
-    )
+    _person = Person.objects.create(first_name='Jane', last_name='Doe')
 
     assert _person.full_name == 'Doe, Jane'
diff --git a/biscuit/core/tests/templatetags/test_data_helpers.py b/biscuit/core/tests/templatetags/test_data_helpers.py
index 096903947c8c21870ad5c05fcffb5b0a8e522a12..22281a18462088f3bceef6f94a84f8f60200612f 100644
--- a/biscuit/core/tests/templatetags/test_data_helpers.py
+++ b/biscuit/core/tests/templatetags/test_data_helpers.py
@@ -1,21 +1,25 @@
 from biscuit.core.templatetags.data_helpers import get_dict
 
+
 def test_get_dict_object():
     class _Foo(object):
         bar = 12
 
     assert _Foo.bar == get_dict(_Foo, 'bar')
 
+
 def test_get_dict_dict():
     _foo = {'bar': 12}
 
     assert _foo['bar'] == get_dict(_foo, 'bar')
 
+
 def test_get_dict_list():
     _foo = [10, 11, 12]
 
     assert _foo[2] == get_dict(_foo, 2)
 
+
 def test_get_dict_invalid():
     _foo = 12
 
diff --git a/biscuit/core/tests/views/test_account.py b/biscuit/core/tests/views/test_account.py
index 3bffcf17e9e226ac04230d367219e3cc5942a835..6a5971720ab70e680e8dcc5688e27f6483705fbc 100644
--- a/biscuit/core/tests/views/test_account.py
+++ b/biscuit/core/tests/views/test_account.py
@@ -3,6 +3,7 @@ import pytest
 from django.conf import settings
 from django.urls import reverse
 
+
 @pytest.mark.django_db
 def test_index_not_logged_in(client):
     response = client.get('/')
@@ -10,6 +11,7 @@ def test_index_not_logged_in(client):
     assert response.status_code == 200
     assert reverse(settings.LOGIN_URL) in response.content.decode('utf-8')
 
+
 @pytest.mark.django_db
 def test_login(client, django_user_model):
     username = 'foo'
@@ -23,6 +25,7 @@ def test_login(client, django_user_model):
     assert response.status_code == 200
     assert reverse(settings.LOGIN_URL) not in response.content.decode('utf-8')
 
+
 @pytest.mark.django_db
 def test_index_not_linked_to_person(client, django_user_model):
     username = 'foo'
@@ -36,6 +39,7 @@ def test_index_not_linked_to_person(client, django_user_model):
     assert response.status_code == 200
     assert 'You are not linked to a person' in response.content.decode('utf-8')
 
+
 @pytest.mark.django_db
 def test_logout(client, django_user_model):
     username = 'foo'
diff --git a/biscuit/core/urls.py b/biscuit/core/urls.py
index 0ecfbb6f34d064a32170999f3886327100a6da9d..3b172dc5e7558df97b05cd02df1b1da35bc6d9d0 100644
--- a/biscuit/core/urls.py
+++ b/biscuit/core/urls.py
@@ -22,22 +22,19 @@ urlpatterns = [
     path('persons', views.persons, name='persons'),
     path('persons/accounts', views.persons_accounts, name='persons_accounts'),
     path('person', views.person, name='person'),
-    path('person/<int:id_>', views.person,
-         {'template': 'full'}, name='person_by_id'),
-    path('person/<int:id_>/card', views.person,
-         {'template': 'card'}, name='person_by_id_card'),
+    path('person/<int:id_>', views.person, {'template': 'full'}, name='person_by_id'),
+    path('person/<int:id_>/card', views.person, {'template': 'card'}, name='person_by_id_card'),
     path('person/<int:id_>/edit', views.edit_person, name='edit_person_by_id'),
     path('groups', views.groups, name='groups'),
     path('group/create', views.edit_group, name='create_group'),
-    path('group/<int:id_>', views.group,
-         {'template': 'full'}, name='group_by_id'),
+    path('group/<int:id_>', views.group, {'template': 'full'}, name='group_by_id'),
     path('group/<int:id_>/edit', views.edit_group, name='edit_group_by_id'),
     path('', views.index, name='index'),
     path('maintenance-mode/', include('maintenance_mode.urls')),
     path('impersonate/', include('impersonate.urls')),
     path('__i18n__/', include('django.conf.urls.i18n')),
     path('select2/', include('django_select2.urls')),
-    path('settings/', include('dbsettings.urls'))
+    path('settings/', include('dbsettings.urls')),
 ]
 
 # Serve static files from STATIC_ROOT to make it work with runserver
@@ -47,6 +44,7 @@ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
 # Add URLs for optional features
 if hasattr(settings, 'TWILIO_ACCOUNT_SID'):
     from two_factor.gateways.twilio.urls import urlpatterns as tf_twilio_urls  # noqa
+
     urlpatterns += [path('', include(tf_twilio_urls))]
 
 # Serve javascript-common if in development
@@ -58,5 +56,4 @@ for app_config in apps.app_configs.values():
     if not app_config.name.startswith('biscuit.apps.'):
         continue
 
-    urlpatterns.append(path('app/%s/' % app_config.label,
-                            include('%s.urls' % app_config.name)))
+    urlpatterns.append(path('app/%s/' % app_config.label, include('%s.urls' % app_config.name)))
diff --git a/biscuit/core/util/apps.py b/biscuit/core/util/apps.py
index 306bb1f701c0419f29610d84766d06ce777f6cd9..9b732d651caae3412e002717e57c518c515bbdae 100644
--- a/biscuit/core/util/apps.py
+++ b/biscuit/core/util/apps.py
@@ -11,7 +11,9 @@ class AppConfig(django.apps.AppConfig):
 
         # Run model extension code
         try:
-            import_module('.'.join(self.__class__.__module__.split('.')[:-1] + ['model_extensions']))
+            import_module(
+                '.'.join(self.__class__.__module__.split('.')[:-1] + ['model_extensions'])
+            )
         except ImportError:
             # ImportErrors are non-fatal because model extensions are optional.
             pass
diff --git a/biscuit/core/util/core_helpers.py b/biscuit/core/util/core_helpers.py
index 03ec0f6391811d62d22b002d4765816f74119115..4089026c884b03abe83a468e0949853581bcdf39 100644
--- a/biscuit/core/util/core_helpers.py
+++ b/biscuit/core/util/core_helpers.py
@@ -19,6 +19,7 @@ def dt_show_toolbar(request: HttpRequest) -> bool:
 
     return False
 
+
 def get_app_packages() -> Sequence[str]:
     """ Find all packages within the biscuit.apps namespace. """
 
diff --git a/biscuit/core/util/messages.py b/biscuit/core/util/messages.py
index 7b847885567b989100b89de4c830d73f4346bb70..e3c93dbb0301ea3eff1c2c7f4b18556d33c09789 100644
--- a/biscuit/core/util/messages.py
+++ b/biscuit/core/util/messages.py
@@ -5,7 +5,9 @@ from django.contrib import messages
 from django.http import HttpRequest
 
 
-def add_message(request: Optional[HttpRequest], level: int, message: str, **kwargs) -> Optional[Any]:
+def add_message(
+    request: Optional[HttpRequest], level: int, message: str, **kwargs
+) -> Optional[Any]:
     if request:
         return messages.add_message(request, level, message, **kwargs)
     else:
diff --git a/biscuit/core/views.py b/biscuit/core/views.py
index 585d049ae72bd65c0a8ffd16bba88056850f2f04..d3eb0e7752f964afc1d5ae630e65835dbe6f65ca 100644
--- a/biscuit/core/views.py
+++ b/biscuit/core/views.py
@@ -9,7 +9,13 @@ from django.utils.translation import ugettext_lazy as _
 from django_cron.models import CronJobLog
 
 from .decorators import admin_required
-from .forms import PersonsAccountsFormSet, EditPersonForm, EditGroupForm, EditSchoolForm, EditTermForm
+from .forms import (
+    PersonsAccountsFormSet,
+    EditPersonForm,
+    EditGroupForm,
+    EditSchoolForm,
+    EditTermForm,
+)
 from .models import Person, Group, School
 from .tables import PersonsTable, GroupsTable
 from .util import messages
@@ -181,9 +187,9 @@ def data_management(request: HttpRequest) -> HttpResponse:
 def system_status(request: HttpRequest) -> HttpResponse:
     context = {}
 
-    context['backups'] = CronJobLog.objects.filter(
-        code='biscuit.core.Backup'
-    ).order_by('-end_time')[:10]
+    context['backups'] = CronJobLog.objects.filter(code='biscuit.core.Backup').order_by(
+        '-end_time'
+    )[:10]
 
     return render(request, 'core/system_status.html', context)