Repository: rdmorganiser/rdmo (language: python)

Issue:
Dev 2.0.0 cleanup
* Remove views, static directories and templates for the old management interfaces
* Remove copy functions
* Rename `key` to `uri_path` for Condition, Task and View
* Refactor serializers (and remove list serializers)



Discussion / hints:
only these tests are still failing. Do you want to include these in this PR?
```
rdmo/management/tests/test_import_conditions.py FFFF                     [  4%]
rdmo/management/tests/test_import_domain.py .F.F                         [  4%]
rdmo/management/tests/test_import_options.py .F.F.F                      [  4%]
rdmo/management/tests/test_import_questions.py .F.F.F.F.F.F              [  4%]
rdmo/management/tests/test_import_tasks.py FFFF                          [  4%]
rdmo/management/tests/test_import_views.py FFFF                          [  4%]
rdmo/management/tests/test_viewset_import.py .....F..F.F..F...........   [  4%]
rdmo/management/tests/test_viewset_upload.py .....F..F.F..F.F..F........ [  4%]
```
For the copying of sites and editors fields, we would like to have them either empty or filled with current site for example.
This code below would make them empty, but would it be possible (not too difficult) to implement the current site feature in this PR?
```js
// management/assets/js/reducers/elementsReducer.js
if (state.elementAction == 'copy') {
    action.elements.element.id = null
    action.elements.element.read_only = false
    action.elements.element.sites = [] // or current site
    action.elements.element.editors = []  // or current site
  }
```

I created a new PR to this PR for this: https://github.com/rdmorganiser/rdmo/pull/640

It actually needs to be done in the action not the reducer, since the reducer has no access to the `config` part of the redux store.

Fix patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 34bcb9a11d..5c051880f6 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -28,4 +28,4 @@ Please state your operating system, the RDMO version, and (if applicable) the br
 
 ### References / Verweise
 
-* 
+*
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index c5d2e92343..e9f639996d 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -11,7 +11,7 @@ assignees: ''
 
 A clear and concise description of what the problem is, followed by the solution you'd like.
 
-### Affected 
+### Affected
 
 Who is affected by the change (Users, Managers, Admins)?
 
@@ -25,4 +25,4 @@ What sort of related functionality would you like to see in addition?
 
 ### References / Verweise
 
-* 
+*
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 568b78cc29..6c10207c3d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,7 +17,9 @@ repos:
         exclude: error\.xml$
       - id: check-yaml
       - id: end-of-file-fixer
+        exclude: \.html$|\.txt$
       - id: trailing-whitespace
+        exclude: \.dot$
       - id: debug-statements
   - repo: https://github.com/charliermarsh/ruff-pre-commit
     rev: v0.0.284
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9e512e9f7..124e0d648a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -100,7 +100,7 @@
 * Add account deletion for LDAP users
 * Fix attribute export
 * Fix condition resolution when going backwards
-* Prevent overlay errors if custom list is used 
+* Prevent overlay errors if custom list is used
 * Various fixes
 
 ## RDMO 1.6.2 (Nov 03, 2021)
diff --git a/LICENSE b/LICENSE
index 6b0b1270ff..d645695673 100644
--- a/LICENSE
+++ b/LICENSE
@@ -200,4 +200,3 @@
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-
diff --git a/pyproject.toml b/pyproject.toml
index 347e6e998a..f90c529851 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -137,6 +137,7 @@ select = [
 ignore = [
   "B006",   # mutable-argument-default
   "B007",   # unused-loop-control-variable
+  "B018",   # useless-expression
   "RUF012", # mutable-class-default
 ]
 
@@ -162,14 +163,20 @@ rest_framework = ["rest_framework"]
 "rdmo/**/models/__init__.py" = [
   "F401", # unused-import
 ]
+"rdmo/**/serializers/v1/__init__.py" = [
+  "F401", # unused-import
+]
 "rdmo/**/views/__init__.py" = [
   "F401", # unused-import
 ]
-# Ignore certain rules for tests, e.g. usage of assert is allowed
 "rdmo/**/tests/test_*.py" = [
   "S101", # assert
   "S106", # hardcoded-password-func-arg
 ]
+"testing/config/settings/__init__.py" = [
+  "F401", # unused-import
+  "F403", # undefined-names
+]
 
 [tool.pytest.ini_options]
 DJANGO_SETTINGS_MODULE = "config.settings"
diff --git a/rdmo/accounts/adapter.py b/rdmo/accounts/adapter.py
index a54cae687c..ccc7bf4d25 100644
--- a/rdmo/accounts/adapter.py
+++ b/rdmo/accounts/adapter.py
@@ -1,6 +1,7 @@
+from django.conf import settings
+
 from allauth.account.adapter import DefaultAccountAdapter
 from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
-from django.conf import settings
 
 
 class AccountAdapter(DefaultAccountAdapter):
diff --git a/rdmo/accounts/forms.py b/rdmo/accounts/forms.py
index 63fd10ceea..e8e5f3df61 100644
--- a/rdmo/accounts/forms.py
+++ b/rdmo/accounts/forms.py
@@ -22,7 +22,7 @@ class Meta:
             fields = ('first_name', 'last_name', 'email')
 
     def __init__(self, *args, **kwargs):
-        super(ProfileForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         self.fields['first_name'].widget = forms.TextInput(attrs={'placeholder': _('First name')})
         self.fields['last_name'].widget = forms.TextInput(attrs={'placeholder': _('Last name')})
@@ -50,7 +50,7 @@ def __init__(self, *args, **kwargs):
             self.fields[additional_field.key].initial = additional_field_value.value
 
     def save(self, *args, **kwargs):
-        super(ProfileForm, self).save(*args, **kwargs)
+        super().save(*args, **kwargs)
         self._save_additional_values()
 
     def _save_additional_values(self, user=None):
@@ -72,7 +72,7 @@ class SignupForm(ProfileForm):
     use_required_attribute = False
 
     def __init__(self, *args, **kwargs):
-        super(SignupForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         # add a consent field, the label is added in the template
         if settings.ACCOUNT_TERMS_OF_USE:
diff --git a/rdmo/accounts/models.py b/rdmo/accounts/models.py
index 9d9c097a76..226b81f6af 100644
--- a/rdmo/accounts/models.py
+++ b/rdmo/accounts/models.py
@@ -4,6 +4,7 @@
 from django.db.models.signals import post_save
 from django.dispatch import receiver
 from django.utils.translation import gettext_lazy as _
+
 from rdmo.core.models import TranslationMixin
 
 
diff --git a/rdmo/accounts/serializers/v1.py b/rdmo/accounts/serializers/v1.py
index ed9e627b6a..cd7018c4a9 100644
--- a/rdmo/accounts/serializers/v1.py
+++ b/rdmo/accounts/serializers/v1.py
@@ -2,6 +2,7 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group
 from django.contrib.sites.models import Site
+
 from rest_framework import serializers
 
 from rdmo.projects.models import Membership
diff --git a/rdmo/accounts/settings.py b/rdmo/accounts/settings.py
index bf92651f87..f978d8fe46 100644
--- a/rdmo/accounts/settings.py
+++ b/rdmo/accounts/settings.py
@@ -5,7 +5,7 @@
 user_view_permission = (
   auth_app,
   auth_model,
-  'view_{}'.format(auth_model)
+  f'view_{auth_model}'
 )
 
 GROUPS = (
diff --git a/rdmo/accounts/templatetags/accounts_tags.py b/rdmo/accounts/templatetags/accounts_tags.py
index 82d08eb6a8..6cadb0d25b 100644
--- a/rdmo/accounts/templatetags/accounts_tags.py
+++ b/rdmo/accounts/templatetags/accounts_tags.py
@@ -1,6 +1,6 @@
 from django import template
-from django.utils.translation import gettext_lazy as _
 from django.utils.safestring import mark_safe
+from django.utils.translation import gettext_lazy as _
 
 from ..utils import get_full_name
 
@@ -15,14 +15,11 @@ def full_name(user):
 @register.simple_tag()
 def user_data_as_dl(user):
     html = '<dl>'
-    html += '<dt>%(key)s</dt><dd>%(value)s</dd>' % {
-        'key': _('Name'),
-        'value': get_full_name(user)
-    }
+    html += '<dt>{key}</dt><dd>{value}</dd>'.format(
+        key=_('Name'),
+        value=get_full_name(user),
+    )
     for additional_value in user.additional_values.all():
-        html += '<dt>%(key)s</dt><dd>%(value)s</dd>' % {
-            'key': additional_value.field.text,
-            'value': additional_value.value
-        }
+        html += f'<dt>{additional_value.field.text}</dt><dd>{additional_value.value}</dd>'
     html += '</dl>'
     return mark_safe(html)
diff --git a/rdmo/accounts/urls/__init__.py b/rdmo/accounts/urls/__init__.py
index 13a739c451..aaa1354629 100644
--- a/rdmo/accounts/urls/__init__.py
+++ b/rdmo/accounts/urls/__init__.py
@@ -2,8 +2,7 @@
 from django.contrib.auth import views as auth_views
 from django.urls import include, re_path
 
-from ..views import (profile_update, remove_user, shibboleth_login,
-                     shibboleth_logout, terms_of_use, token)
+from ..views import profile_update, remove_user, shibboleth_login, shibboleth_logout, terms_of_use, token
 
 urlpatterns = [
     # edit own profile
@@ -18,9 +17,12 @@
 
 if settings.SHIBBOLETH:
     urlpatterns += [
-        re_path('^shibboleth/login/', shibboleth_login, name='shibboleth_login'),
-        re_path('^shibboleth/logout/', shibboleth_logout, name='shibboleth_logout'),
-        re_path('^logout/', auth_views.LogoutView.as_view(next_page=settings.SHIBBOLETH_LOGOUT_URL), name='account_logout'),
+        re_path('^shibboleth/login/',
+                shibboleth_login, name='shibboleth_login'),
+        re_path('^shibboleth/logout/',
+                shibboleth_logout, name='shibboleth_logout'),
+        re_path('^logout/',
+                auth_views.LogoutView.as_view(next_page=settings.SHIBBOLETH_LOGOUT_URL), name='account_logout'),
     ]
 
 if settings.ACCOUNT or settings.SOCIALACCOUNT:
@@ -30,8 +32,10 @@
     ]
 else:
     urlpatterns += [
-        re_path('^login/', auth_views.LoginView.as_view(template_name='account/login.html'), name='account_login'),
-        re_path('^logout/', auth_views.LogoutView.as_view(next_page=settings.LOGIN_REDIRECT_URL), name='account_logout'),
+        re_path('^login/',
+                auth_views.LoginView.as_view(template_name='account/login.html'), name='account_login'),
+        re_path('^logout/',
+                auth_views.LogoutView.as_view(next_page=settings.LOGIN_REDIRECT_URL), name='account_logout'),
     ]
 
 
diff --git a/rdmo/accounts/urls/v1.py b/rdmo/accounts/urls/v1.py
index 854cf8205d..deb5ae9050 100644
--- a/rdmo/accounts/urls/v1.py
+++ b/rdmo/accounts/urls/v1.py
@@ -1,4 +1,5 @@
 from django.urls import include, path
+
 from rest_framework import routers
 
 from ..viewsets import UserViewSet
diff --git a/rdmo/accounts/utils.py b/rdmo/accounts/utils.py
index 10cd76acfb..b200f64d18 100644
--- a/rdmo/accounts/utils.py
+++ b/rdmo/accounts/utils.py
@@ -14,7 +14,7 @@
 
 def get_full_name(user):
     if user.first_name and user.last_name:
-        return '%s %s' % (user.first_name, user.last_name)
+        return f'{user.first_name} {user.last_name}'
     else:
         return user.username
 
@@ -55,7 +55,8 @@ def delete_user(user=None, email=None, password=None):
         return False
 
     if user != database_user:
-        log.info('Deletion of user "%s" failed, the user from request (pk=%s) and database (pk=%s) differ.', username, user.pk, database_user.pk)
+        log.info('Deletion of user "%s" failed, the user from request (pk=%s) and database (pk=%s) differ.',
+                 username, user.pk, database_user.pk)
         return False
 
     if user.has_usable_password() and password is not None:
@@ -68,7 +69,8 @@ def delete_user(user=None, email=None, password=None):
             log.info('Deletion of user with usable password "%s" succeeded.', username)
             return True
         except Exception as e:
-            log.error('Deletion of user with usable password "%s" failed, an exception (%s) occured', (str(e), username))
+            log.error('Deletion of user with usable password "%s" failed, an exception (%s) occured',
+                      str(e), username)
             return False
     elif not user.has_usable_password() and password is None:
         try:
@@ -76,7 +78,8 @@ def delete_user(user=None, email=None, password=None):
             log.info('Deletion of user without usable password "%s" succeeded.', username)
             return True
         except Exception as e:
-            log.error('Deletion of user without usable password "%s" failed, an exception (%s) occured', (str(e), username))
+            log.error('Deletion of user without usable password "%s" failed, an exception (%s) occured',
+                      str(e), username)
             return False
     else:
         log.info('Deletion of user "%s" failed, probably wrong value for password given', username)
diff --git a/rdmo/accounts/views.py b/rdmo/accounts/views.py
index eeebe58123..7ed194997a 100644
--- a/rdmo/accounts/views.py
+++ b/rdmo/accounts/views.py
@@ -7,6 +7,7 @@
 from django.http import HttpResponseRedirect
 from django.shortcuts import render
 from django.urls import reverse
+
 from rest_framework.authtoken.models import Token
 
 from rdmo.core.utils import get_next, get_referer_path_info
diff --git a/rdmo/accounts/viewsets.py b/rdmo/accounts/viewsets.py
index baa733cb85..b377dd08df 100644
--- a/rdmo/accounts/viewsets.py
+++ b/rdmo/accounts/viewsets.py
@@ -1,16 +1,16 @@
 from django.contrib.auth import get_user_model
-from django_filters.rest_framework import DjangoFilterBackend
+
 from rest_framework.viewsets import ReadOnlyModelViewSet
 
+from django_filters.rest_framework import DjangoFilterBackend
+
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
-from rdmo.management.rules import is_editor, is_reviewer
 
-from .models import Role
 from .serializers.v1 import UserSerializer
 from .utils import is_site_manager
 
 
-class UserViewSetMixin(object):
+class UserViewSetMixin:
 
     def get_users_for_user(self, user):
         if user.is_authenticated:
diff --git a/rdmo/conditions/imports.py b/rdmo/conditions/imports.py
index 809dd6ea6a..f9f5f513f1 100644
--- a/rdmo/conditions/imports.py
+++ b/rdmo/conditions/imports.py
@@ -2,8 +2,7 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (check_permissions, set_common_fields,
-                               set_foreign_field, validate_instance)
+from rdmo.core.imports import check_permissions, set_common_fields, set_foreign_field, validate_instance
 
 from .models import Condition
 from .validators import ConditionLockedValidator, ConditionUniqueURIValidator
diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py
index e1a3303294..3dcfd73da0 100644
--- a/rdmo/conditions/models.py
+++ b/rdmo/conditions/models.py
@@ -3,7 +3,7 @@
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 
-from rdmo.core.utils import copy_model, join_url
+from rdmo.core.utils import join_url
 from rdmo.domain.models import Attribute
 
 
@@ -61,7 +61,8 @@ class Condition(models.Model):
         help_text=_('The sites that can edit this condition (in a multi site setup).')
     )
     source = models.ForeignKey(
-        Attribute, db_constraint=False, blank=True, null=True, on_delete=models.SET_NULL, related_name='conditions',
+        Attribute, blank=True, null=True, on_delete=models.SET_NULL, related_name='conditions',
+        db_constraint=False,
         verbose_name=_('Source'),
         help_text=_('The attribute of the value for this condition.')
     )
@@ -73,10 +74,12 @@ class Condition(models.Model):
     target_text = models.CharField(
         max_length=256, blank=True,
         verbose_name=_('Target (Text)'),
-        help_text=_('If using a regular value, the text value this condition is checking against (for boolean values use 1 and 0).')
+        help_text=_('If using a regular value, the text value this condition is checking against '
+                    '(for boolean values use 1 and 0).')
     )
     target_option = models.ForeignKey(
-        'options.Option', db_constraint=False, blank=True, null=True, on_delete=models.SET_NULL, related_name='conditions',
+        'options.Option', blank=True, null=True, on_delete=models.SET_NULL, related_name='conditions',
+        db_constraint=False,
         verbose_name=_('Target (Option)'),
         help_text=_('If using a value pointing to an option, the option this condition is checking against.')
     )
diff --git a/rdmo/conditions/serializers/v1.py b/rdmo/conditions/serializers/v1.py
index 6723584afb..6911b9285b 100644
--- a/rdmo/conditions/serializers/v1.py
+++ b/rdmo/conditions/serializers/v1.py
@@ -1,7 +1,6 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin)
+from rdmo.core.serializers import ElementModelSerializerMixin, ReadOnlyObjectPermissionSerializerMixin
 from rdmo.domain.models import Attribute
 from rdmo.options.models import OptionSet
 from rdmo.questions.models import Page, Question, QuestionSet
diff --git a/rdmo/conditions/viewsets.py b/rdmo/conditions/viewsets.py
index fd561ea209..3816b99921 100644
--- a/rdmo/conditions/viewsets.py
+++ b/rdmo/conditions/viewsets.py
@@ -1,10 +1,11 @@
-from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework.decorators import action
 from rest_framework.filters import SearchFilter
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
 
+from django_filters.rest_framework import DjangoFilterBackend
+
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import is_truthy, render_to_format
@@ -60,9 +61,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = ConditionRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'conditions/export/conditions.html', {
-                'conditions': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'conditions/export/conditions.html', {
+                    'conditions': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
diff --git a/rdmo/core/exports.py b/rdmo/core/exports.py
index f3e61a9712..89a4b28ade 100644
--- a/rdmo/core/exports.py
+++ b/rdmo/core/exports.py
@@ -1,7 +1,6 @@
-from django.conf import settings
-
 from xml.dom.minidom import parseString
 
+from django.conf import settings
 from django.http import HttpResponse
 
 
diff --git a/rdmo/core/imports.py b/rdmo/core/imports.py
index 4cebb92fc7..587afd54e1 100644
--- a/rdmo/core/imports.py
+++ b/rdmo/core/imports.py
@@ -6,6 +6,7 @@
 from random import randint
 
 from django.core.exceptions import ObjectDoesNotExist, ValidationError
+
 from rest_framework.utils import model_meta
 
 from rdmo.core.utils import get_languages
@@ -48,11 +49,11 @@ def set_common_fields(instance, element):
 
 def set_lang_field(instance, field_name, element):
     for lang_code, lang_string, lang_field in get_languages():
-        field = element.get('%s_%s' % (field_name, lang_code))
+        field = element.get(f'{field_name}_{lang_code}')
         if field:
-            setattr(instance, '%s_%s' % (field_name, lang_field), field)
+            setattr(instance, f'{field_name}_{lang_field}', field)
         else:
-            setattr(instance, '%s_%s' % (field_name, lang_field), '')
+            setattr(instance, f'{field_name}_{lang_field}', '')
 
 
 def set_foreign_field(instance, field_name, element) -> None:
@@ -139,7 +140,7 @@ def set_m2m_through_instances(instance, field_name, element, source_name, target
             try:
                 # look for the item in items
                 through_instance = next(filter(lambda item: getattr(item, target_name).uri == target_instance.uri,
-                                                through_instances))
+                                               through_instances))
 
                 # update order if the item if it changed
                 if through_instance.order != order:
@@ -224,21 +225,21 @@ def validate_instance(instance, element, *validators):
             validator(instance if instance.id else None)(vars(instance))
     except ValidationError as e:
         try:
-            exception_message = '; '.join(['{}: {}'.format(key, ', '.join(messages)) for key, messages in e.message_dict.items()])
+            exception_message = '; '.join(['{}: {}'.format(key, ', '.join(messages))
+                                           for key, messages in e.message_dict.items()])
         except AttributeError:
             exception_message = ''.join(e.messages)
     except ObjectDoesNotExist as e:
         exception_message = e
     finally:
-        if exception_message is None:
-            return
-        message = '{instance_model} {instance_uri} cannot be imported ({exception}).'.format(
-            instance_model=instance._meta.object_name,
-            instance_uri=element.get('uri'),
-            exception=exception_message
-        )
-        logger.info(message)
-        element['errors'].append(message)
+        if exception_message is not None:
+            message = '{instance_model} {instance_uri} cannot be imported ({exception}).'.format(
+                instance_model=instance._meta.object_name,
+                instance_uri=element.get('uri'),
+                exception=exception_message
+            )
+            logger.info(message)
+            element['errors'].append(message)
 
 
 def check_permissions(instance, element, user):
diff --git a/rdmo/core/mail.py b/rdmo/core/mail.py
index 64c85642fb..d4a210ca10 100644
--- a/rdmo/core/mail.py
+++ b/rdmo/core/mail.py
@@ -5,7 +5,7 @@
 
 def send_mail(subject, message, from_email=None, to=[], cc=[], bcc=[], reply_to=None, attachments=[]):
     site = Site.objects.get_current()
-    subject = '[{}] '.format(site.name) + subject
+    subject = f'[{site.name}] ' + subject
 
     if from_email is None:
         from_email = settings.DEFAULT_FROM_EMAIL
diff --git a/rdmo/core/management/commands/create_admin_user.py b/rdmo/core/management/commands/create_admin_user.py
index 487fabf00d..cf110c3399 100644
--- a/rdmo/core/management/commands/create_admin_user.py
+++ b/rdmo/core/management/commands/create_admin_user.py
@@ -1,5 +1,5 @@
-from django.core.management.base import BaseCommand
 from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand
 
 
 class Command(BaseCommand):
diff --git a/rdmo/core/management/commands/delete_users.py b/rdmo/core/management/commands/delete_users.py
index 493b970ac0..8deda1f4f2 100644
--- a/rdmo/core/management/commands/delete_users.py
+++ b/rdmo/core/management/commands/delete_users.py
@@ -23,7 +23,7 @@ def add_arguments(self, parser):
     def make_user_id_list(self, filename):
         user_ids = []
         try:
-            filecontent = open(filename, 'r')
+            filecontent = open(filename)
         except Exception as e:
             print('Error reading id list file. ' + str(e))
             sys.exit(1)
diff --git a/rdmo/core/management/commands/download_vendor_files.py b/rdmo/core/management/commands/download_vendor_files.py
index fed476e728..2c3e8935f8 100644
--- a/rdmo/core/management/commands/download_vendor_files.py
+++ b/rdmo/core/management/commands/download_vendor_files.py
@@ -3,10 +3,11 @@
 import os
 import shutil
 
-import requests
 from django.conf import settings
 from django.core.management.base import BaseCommand
 
+import requests
+
 
 class Command(BaseCommand):
 
@@ -23,7 +24,7 @@ def handle(self, *args, **options):
                 if file_type in vendor_conf:
                     for file in vendor_conf[file_type]:
                         # get the directory and the file_name
-                        path_tokens = ['vendor', key] + os.path.normpath(file['path']).split(os.path.sep)
+                        path_tokens = ['vendor', key, *os.path.normpath(file['path']).split(os.path.sep)]
                         directory = os.path.join(*path_tokens[:-1])
                         file_name = os.path.join(*path_tokens)
 
@@ -36,7 +37,7 @@ def handle(self, *args, **options):
                         # get the full url of the file
                         url = requests.compat.urljoin(vendor_conf['url'], file['path'])
 
-                        print('%s -> %s' % (url, file_name))
+                        print(f'{url} -> {file_name}')
 
                         # fetch the file from the cdn
                         response = requests.get(url)
diff --git a/rdmo/core/management/commands/find_inactive_users.py b/rdmo/core/management/commands/find_inactive_users.py
index 504d650c8f..e282dd06e2 100644
--- a/rdmo/core/management/commands/find_inactive_users.py
+++ b/rdmo/core/management/commands/find_inactive_users.py
@@ -1,13 +1,12 @@
-import sys
 import csv
-
+import sys
 from datetime import datetime
 
-import pytz
-
 from django.contrib.auth.models import User
 from django.core.management.base import BaseCommand
 
+import pytz
+
 
 class Command(BaseCommand):
 
diff --git a/rdmo/core/management/commands/find_spam_users.py b/rdmo/core/management/commands/find_spam_users.py
index 883b00f2b4..236a43964b 100644
--- a/rdmo/core/management/commands/find_spam_users.py
+++ b/rdmo/core/management/commands/find_spam_users.py
@@ -39,7 +39,7 @@ def save_csv(self, data, filename):
             print('List written to ' + filename)
 
     def print_file(self, filename):
-        f = open(filename, 'r')
+        f = open(filename)
         content = f.read()
         print(content)
         f.close()
diff --git a/rdmo/core/management/commands/find_users.py b/rdmo/core/management/commands/find_users.py
index 2625c5bacc..c679036246 100644
--- a/rdmo/core/management/commands/find_users.py
+++ b/rdmo/core/management/commands/find_users.py
@@ -48,7 +48,7 @@ def save_csv(self, data, filename):
             print('List written to ' + filename)
 
     def print_file(self, filename):
-        f = open(filename, 'r')
+        f = open(filename)
         content = f.read()
         print(content)
         f.close()
diff --git a/rdmo/core/management/commands/make_theme.py b/rdmo/core/management/commands/make_theme.py
index f71afbd2d2..9dab60d8ec 100644
--- a/rdmo/core/management/commands/make_theme.py
+++ b/rdmo/core/management/commands/make_theme.py
@@ -1,5 +1,5 @@
-from shutil import copyfile
 from pathlib import Path
+from shutil import copyfile
 
 from django.apps import apps
 from django.conf import settings
@@ -19,15 +19,15 @@ def copy(self, path):
         target_path = self.theme_path / Path(*path.parts[1:])
 
         if target_path.exists():
-            print('Skip {} -> {}. Target file exists.'.format(source_path, target_path))
+            print(f'Skip {source_path} -> {target_path}. Target file exists.')
         else:
-            print('Copy {} -> {}.'.format(source_path, target_path))
+            print(f'Copy {source_path} -> {target_path}.')
 
             target_path.parent.mkdir(parents=True, exist_ok=True)
             copyfile(source_path, target_path)
 
     def enable_theme(self):
-        settings_line = 'INSTALLED_APPS = [\'{}\'] + INSTALLED_APPS'.format(self.theme_name)
+        settings_line = f'INSTALLED_APPS = [\'{self.theme_name}\'] + INSTALLED_APPS'
         replaced = False
 
         local_settings = self.local_path.read_text().splitlines()
@@ -48,8 +48,10 @@ def enable_theme(self):
         self.local_path.write_text('\n'.join(local_settings))
 
     def add_arguments(self, parser):
-        parser.add_argument('--name', action='store', default='rdmo_theme', help='Module name for the theme.')
-        parser.add_argument('--file', action='store', help='Copy specific file/template, e.g. core/static/css/variables.scss.')
+        parser.add_argument('--name', action='store', default='rdmo_theme',
+                            help='Module name for the theme.')
+        parser.add_argument('--file', action='store',
+                            help='Copy specific file/template, e.g. core/static/css/variables.scss.')
 
     def handle(self, *args, **options):
         self.setup(options)
@@ -64,9 +66,9 @@ def handle(self, *args, **options):
             self.copy(Path('core') / 'static' / 'core' / 'css' / 'variables.scss')
 
             for language, language_string in settings.LANGUAGES:
-                self.copy(Path('core') / 'templates' / 'core' / 'home_text_{}.html'.format(language))
-                self.copy(Path('core') / 'templates' / 'core' / 'about_text_{}.html'.format(language))
-                self.copy(Path('core') / 'templates' / 'core' / 'footer_text_{}.html'.format(language))
+                self.copy(Path('core') / 'templates' / 'core' / f'home_text_{language}.html')
+                self.copy(Path('core') / 'templates' / 'core' / f'about_text_{language}.html')
+                self.copy(Path('core') / 'templates' / 'core' / f'footer_text_{language}.html')
 
             print('Enable theme by adding the necessary config line.')
             self.enable_theme()
diff --git a/rdmo/core/management/commands/set_uri_prefix.py b/rdmo/core/management/commands/set_uri_prefix.py
index 46c5e03a05..8acffb0a1e 100644
--- a/rdmo/core/management/commands/set_uri_prefix.py
+++ b/rdmo/core/management/commands/set_uri_prefix.py
@@ -1,9 +1,9 @@
 from django.core.management.base import BaseCommand
 
 from rdmo.conditions.models import Condition
-from rdmo.options.models import OptionSet, Option
 from rdmo.domain.models import Attribute
-from rdmo.questions.models import Catalog, Section, Subsection, QuestionSet, Question
+from rdmo.options.models import Option, OptionSet
+from rdmo.questions.models import Catalog, Question, QuestionSet, Section, Subsection
 from rdmo.tasks.models import Task
 from rdmo.views.models import View
 
diff --git a/rdmo/core/managers.py b/rdmo/core/managers.py
index 083c91eb06..e1f68ebf70 100644
--- a/rdmo/core/managers.py
+++ b/rdmo/core/managers.py
@@ -4,20 +4,20 @@
 from .constants import PERMISSIONS
 
 
-class CurrentSiteQuerySetMixin(object):
+class CurrentSiteQuerySetMixin:
 
     def filter_current_site(self):
         return self.filter(models.Q(sites=None) | models.Q(sites=settings.SITE_ID))
 
 
-class GroupsQuerySetMixin(object):
+class GroupsQuerySetMixin:
 
     def filter_group(self, user):
         groups = user.groups.all()
         return self.filter(models.Q(groups=None) | models.Q(groups__in=groups))
 
 
-class AvailabilityQuerySetMixin(object):
+class AvailabilityQuerySetMixin:
 
     def filter_availability(self, user):
         model = str(self.model._meta)
@@ -29,19 +29,19 @@ def filter_availability(self, user):
             return self.filter(available=True)
 
 
-class CurrentSiteManagerMixin(object):
+class CurrentSiteManagerMixin:
 
     def filter_current_site(self):
         return self.get_queryset().filter_current_site()
 
 
-class GroupsManagerMixin(object):
+class GroupsManagerMixin:
 
     def filter_group(self, user):
         return self.get_queryset().filter_group(user)
 
 
-class AvailabilityManagerMixin(object):
+class AvailabilityManagerMixin:
 
     def filter_availability(self, user):
         return self.get_queryset().filter_availability(user)
diff --git a/rdmo/core/models.py b/rdmo/core/models.py
index 9ac75d4cc4..85f7ece3af 100644
--- a/rdmo/core/models.py
+++ b/rdmo/core/models.py
@@ -25,10 +25,10 @@ def save(self, *args, **kwargs):
 
         self.updated = now()
 
-        super(Model, self).save(*args, **kwargs)
+        super().save(*args, **kwargs)
 
 
-class TranslationMixin(object):
+class TranslationMixin:
 
     def trans(self, field):
         current_language = get_language()
@@ -36,12 +36,12 @@ def trans(self, field):
         languages = get_languages()
         for lang_code, lang_string, lang_field in languages:
             if lang_code == current_language:
-                r = getattr(self, '%s_%s' % (field, lang_field)) or None
+                r = getattr(self, f'{field}_{lang_field}') or None
                 if r is not None:
                     return r
                 elif settings.REPLACE_MISSING_TRANSLATION:
                     for i in range(1, 6):
-                        r = getattr(self, '%s_%s' % (field, 'lang' + str(i))) or None
+                        r = getattr(self, '{}_{}'.format(field, 'lang' + str(i))) or None
                         if r is not None:
                             return r
         return ''
diff --git a/rdmo/core/permissions.py b/rdmo/core/permissions.py
index d86bcb1b44..6cdc1b5662 100644
--- a/rdmo/core/permissions.py
+++ b/rdmo/core/permissions.py
@@ -1,7 +1,6 @@
 import logging
 
-from rest_framework.permissions import (DjangoModelPermissions,
-                                        DjangoObjectPermissions)
+from rest_framework.permissions import DjangoModelPermissions, DjangoObjectPermissions
 
 logger = logging.getLogger(__name__)
 
diff --git a/rdmo/core/plugins.py b/rdmo/core/plugins.py
index 4b1762d4bb..ce61e2f11a 100644
--- a/rdmo/core/plugins.py
+++ b/rdmo/core/plugins.py
@@ -3,7 +3,7 @@
 from .utils import import_class
 
 
-class Plugin(object):
+class Plugin:
 
     def __init__(self, key, label, class_name):
         self.key = key
@@ -21,7 +21,9 @@ def get_plugins(plugin_settings):
 def get_plugin(plugin_settings, plugin_key):
     try:
         key, label, class_name = next(
-            (key, label, class_name) for key, label, class_name in getattr(settings, plugin_settings) if key == plugin_key
+            (key, label, class_name)
+            for key, label, class_name in getattr(settings, plugin_settings)
+            if key == plugin_key
         )
         return import_class(class_name)(key, label, class_name)
     except StopIteration:
diff --git a/rdmo/core/renderers.py b/rdmo/core/renderers.py
index b0503beeb9..987cff907f 100644
--- a/rdmo/core/renderers.py
+++ b/rdmo/core/renderers.py
@@ -3,6 +3,7 @@
 from django.utils.encoding import smart_str
 from django.utils.timezone import get_current_timezone, now
 from django.utils.xmlutils import SimplerXMLGenerator
+
 from rest_framework.renderers import BaseRenderer
 
 from rdmo import __version__
@@ -32,7 +33,7 @@ def render(self, data, context={}):
 
     def render_text_element(self, xml, tag, attrs, text):
         # remove None values from attrs
-        attrs = dict((key, value) for key, value in attrs.items() if value)
+        attrs = {key: value for key, value in attrs.items() if value}
 
         xml.startElement(tag, attrs)
         if text is not None:
diff --git a/rdmo/core/serializers.py b/rdmo/core/serializers.py
index c1f5e6a28a..1c06cc8a50 100644
--- a/rdmo/core/serializers.py
+++ b/rdmo/core/serializers.py
@@ -3,8 +3,8 @@
 from django.contrib.auth.models import Group
 from django.contrib.sites.models import Site
 from django.db.models import Max
+
 from rest_framework import serializers
-from rest_framework.reverse import reverse
 from rest_framework.utils import model_meta
 
 from rdmo.core.utils import get_language_warning, get_languages, markdown2html
@@ -35,7 +35,7 @@ class MarkdownSerializerMixin(serializers.Serializer):
     markdown_fields = ()
 
     def to_representation(self, instance):
-        response = super(MarkdownSerializerMixin, self).to_representation(instance)
+        response = super().to_representation(instance)
 
         for markdown_field in self.markdown_fields:
             if markdown_field in response and response[markdown_field]:
@@ -44,10 +44,10 @@ def to_representation(self, instance):
         return response
 
 
-class TranslationSerializerMixin(object):
+class TranslationSerializerMixin:
 
     def __init__(self, *args, **kwargs):
-        super(TranslationSerializerMixin, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         meta = getattr(self, 'Meta', None)
         if meta is None:
@@ -55,17 +55,17 @@ def __init__(self, *args, **kwargs):
 
         for lang_code, lang_string, lang_field in get_languages():
             for field in meta.trans_fields:
-                field_name = '%s_%s' % (field, lang_field)
+                field_name = f'{field}_{lang_field}'
                 model_field = meta.model._meta.get_field(field_name)
 
-                self.fields['%s_%s' % (field, lang_code)] = serializers.CharField(
+                self.fields[f'{field}_{lang_code}'] = serializers.CharField(
                     source=field_name,
                     required=not model_field.blank,
                     allow_null=model_field.null,
                     allow_blank=model_field.blank)
 
 
-class ThroughModelSerializerMixin(object):
+class ThroughModelSerializerMixin:
 
     def create(self, validated_data):
         parent_fields = self.get_parent_fields(validated_data)
@@ -113,8 +113,7 @@ def set_through_fields(self, instance, through_fields):
             for data in validated_data:
                 try:
                     # look for the item in items
-                    item = next(filter(lambda item: getattr(item, target_name) ==
-                                        data.get(target_name), items))
+                    item = next(filter(lambda item: getattr(item, target_name) == data.get(target_name), items))
                     # update order if the item if it changed
                     if item.order != data.get('order'):
                         item.order = data.get('order')
@@ -188,7 +187,7 @@ def get_model(self, obj):
 class ElementWarningSerializerMixin(serializers.ModelSerializer):
 
     def get_warning(self, obj):
-        return any([get_language_warning(obj, field_name) for field_name in self.Meta.warning_fields])
+        return any(get_language_warning(obj, field_name) for field_name in self.Meta.warning_fields)
 
 
 class ReadOnlyObjectPermissionSerializerMixin:
@@ -213,7 +212,7 @@ class ReadOnlyObjectPermissionSerializerMixin:
     def construct_object_permission(model, action_name: str) -> str:
         model_app_label = model._meta.app_label
         model_name = model._meta.model_name
-        perm = '%s.%s_%s_object' % (model_app_label, action_name, model_name)
+        perm = f'{model_app_label}.{action_name}_{model_name}_object'
         return perm
 
     def get_read_only(self, obj) -> bool:
diff --git a/rdmo/core/swagger.py b/rdmo/core/swagger.py
index a9b43c71fc..32d5e3d747 100644
--- a/rdmo/core/swagger.py
+++ b/rdmo/core/swagger.py
@@ -3,6 +3,7 @@
 from rest_framework.response import Response
 from rest_framework.schemas import SchemaGenerator
 from rest_framework.views import APIView
+
 from rest_framework_swagger import renderers
 
 from .urls.v1 import urlpatterns
diff --git a/rdmo/core/templatetags/core_tags.py b/rdmo/core/templatetags/core_tags.py
index bafb865f02..52a094b9ee 100644
--- a/rdmo/core/templatetags/core_tags.py
+++ b/rdmo/core/templatetags/core_tags.py
@@ -22,9 +22,9 @@ def i18n_switcher():
     for language, language_string in settings.LANGUAGES:
         url = reverse('i18n_switcher', args=[language])
         if language == translation.get_language():
-            string += "<li><a href=\"%s\"><u>%s</u></a></li>" % (url, language_string)
+            string += f"<li><a href=\"{url}\"><u>{language_string}</u></a></li>"
         else:
-            string += "<li><a href=\"%s\">%s</a></li>" % (url, language_string)
+            string += f"<li><a href=\"{url}\">{language_string}</a></li>"
     return mark_safe(string)
 
 
@@ -60,34 +60,34 @@ def vendor(vendor_key):
     if 'js' in vendor_config:
         for file in vendor_config['js']:
             if settings.VENDOR_CDN:
-                tag = '<script src="%(url)s/%(path)s" integrity="%(sri)s" crossorigin="anonymous"></script>' % {
-                    'url': vendor_config['url'].rstrip('/'),
-                    'path': file['path'],
-                    'sri': file['sri'] if 'sri' in file else ''
-                }
+                tag = '<script src="{url}/{path}" integrity="{sri}" crossorigin="anonymous"></script>'.format(
+                    url=vendor_config['url'].rstrip('/'),
+                    path=file['path'],
+                    sri=file['sri'] if 'sri' in file else '',
+                )
             else:
-                tag = '<script src="%(static_url)s/%(vendor_key)s/%(path)s"></script>' % {
-                    'static_url': settings.STATIC_URL.rstrip('/'),
-                    'vendor_key': vendor_key,
-                    'path': file['path']
-                }
+                tag = '<script src="{static_url}/{vendor_key}/{path}"></script>'.format(
+                    static_url=settings.STATIC_URL.rstrip('/'),
+                    vendor_key=vendor_key,
+                    path=file['path'],
+                )
 
             tags.append(tag)
 
     if 'css' in vendor_config:
         for file in vendor_config['css']:
             if settings.VENDOR_CDN:
-                tag = '<link rel="stylesheet" href="%(url)s/%(path)s" integrity="%(sri)s" crossorigin="anonymous" />' % {
-                    'url': vendor_config['url'].rstrip('/'),
-                    'path': file['path'],
-                    'sri': file['sri'] if 'sri' in file else ''
-                }
+                tag = '<link rel="stylesheet" href="{url}/{path}" integrity="{sri}" crossorigin="anonymous" />'.format(
+                    url=vendor_config['url'].rstrip('/'),
+                    path=file['path'],
+                    sri=file['sri'] if 'sri' in file else '',
+                )
             else:
-                tag = '<link rel="stylesheet" href="%(static_url)s/%(vendor_key)s/%(path)s" />' % {
-                    'static_url': settings.STATIC_URL.rstrip('/'),
-                    'vendor_key': vendor_key,
-                    'path': file['path']
-                }
+                tag = '<link rel="stylesheet" href="{static_url}/{vendor_key}/{path}" />'.format(
+                    static_url=settings.STATIC_URL.rstrip('/'),
+                    vendor_key=vendor_key,
+                    path=file['path'],
+                )
 
             tags.append(tag)
 
diff --git a/rdmo/core/urls/v1.py b/rdmo/core/urls/v1.py
index 0b3a4e7671..442d612de5 100644
--- a/rdmo/core/urls/v1.py
+++ b/rdmo/core/urls/v1.py
@@ -1,4 +1,5 @@
 from django.urls import include, path
+
 from rest_framework import routers
 
 from ..viewsets import GroupViewSet, SettingsViewSet, SitesViewSet
diff --git a/rdmo/core/utils.py b/rdmo/core/utils.py
index b3dd0bebcf..22d9d42b31 100644
--- a/rdmo/core/utils.py
+++ b/rdmo/core/utils.py
@@ -7,14 +7,15 @@
 from tempfile import mkstemp
 from urllib.parse import urlparse
 
-import pypandoc
-from defusedcsv import csv
 from django.apps import apps
 from django.conf import settings
 from django.http import Http404, HttpResponse, HttpResponseBadRequest
 from django.template.loader import get_template
 from django.utils.encoding import force_str
 from django.utils.translation import gettext_lazy as _
+
+import pypandoc
+from defusedcsv import csv
 from markdown import markdown
 
 log = logging.getLogger(__name__)
@@ -149,7 +150,7 @@ def get_language_fields(field_name):
 
 def get_language_warning(obj, field):
     for lang_code, lang_string, lang_field in get_languages():
-        if not getattr(obj, '%s_%s' % (field, lang_field)):
+        if not getattr(obj, f'{field}_{lang_field}'):
             return True
     return False
 
@@ -158,7 +159,7 @@ def set_export_reference_document(format, context):
     # try to get the view uri from the context
     try:
         view = context['view']
-        view_uri = getattr(view, 'uri')
+        view_uri = view.uri
     except (AttributeError, KeyError, TypeError):
         view_uri = None
 
@@ -215,11 +216,11 @@ def render_to_format(request, export_format, title, template_src, context):
     if export_format == 'html':
         # create the response object
         response = HttpResponse(html)
-        response['Content-Disposition'] = 'filename="%s.%s"' % (title, export_format)
+        response['Content-Disposition'] = f'filename="{title}.{export_format}"'
 
     else:
         pandoc_args = settings.EXPORT_PANDOC_ARGS.get(export_format, [])
-        content_disposition = 'attachment; filename="%s.%s"' % (title, export_format)
+        content_disposition = f'attachment; filename="{title}.{export_format}"'
 
         if export_format == 'pdf':
             # check pandoc version (the pdf arg changed to version 2)
@@ -229,23 +230,23 @@ def render_to_format(request, export_format, title, template_src, context):
                 ) for arg in pandoc_args]
 
             # display pdf in browser
-            content_disposition = 'filename="%s.%s"' % (title, export_format)
+            content_disposition = f'filename="{title}.{export_format}"'
 
         # use reference document for certain file formats
         refdoc = set_export_reference_document(export_format, context)
         if refdoc is not None and export_format in ['docx', 'odt']:
             # check pandoc version (the args changed to version 2)
             if get_pandoc_main_version() == 1:
-                pandoc_args.append('--reference-{}={}'.format(export_format, refdoc))
+                pandoc_args.append(f'--reference-{export_format}={refdoc}')
             else:
-                pandoc_args.append('--reference-doc={}'.format(refdoc))
+                pandoc_args.append(f'--reference-doc={refdoc}')
 
         # add the possible resource-path
         if pandoc_version_at_least("2") is True:
-            pandoc_args.append('--resource-path={}'.format(settings.STATIC_ROOT))
+            pandoc_args.append(f'--resource-path={settings.STATIC_ROOT}')
             if 'resource_path' in context:
                 resource_path = Path(settings.MEDIA_ROOT).joinpath(context['resource_path'])
-                pandoc_args.append('--resource-path={}'.format(resource_path))
+                pandoc_args.append(f'--resource-path={resource_path}')
 
         # create a temporary file
         (tmp_fd, tmp_filename) = mkstemp('.' + export_format)
diff --git a/rdmo/core/validators.py b/rdmo/core/validators.py
index 4c48404d06..ecf8a8bc61 100644
--- a/rdmo/core/validators.py
+++ b/rdmo/core/validators.py
@@ -1,13 +1,12 @@
 import re
 
-from django.core.exceptions import (MultipleObjectsReturned,
-                                    ObjectDoesNotExist, ValidationError)
+from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist, ValidationError
 from django.utils.translation import gettext_lazy as _
 
 from rest_framework import serializers
 
 
-class InstanceValidator(object):
+class InstanceValidator:
 
     '''
     BaseValidator which should work with model instances, used by
diff --git a/rdmo/core/views.py b/rdmo/core/views.py
index 45ec24bb76..3ec0ec2a47 100644
--- a/rdmo/core/views.py
+++ b/rdmo/core/views.py
@@ -2,9 +2,8 @@
 
 from django.conf import settings
 from django.contrib.auth.decorators import login_required
-from django.contrib.auth.mixins import \
-    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 from django.contrib.auth.mixins import LoginRequiredMixin
+from django.contrib.auth.mixins import PermissionRequiredMixin as DjangoPermissionRequiredMixin
 from django.contrib.auth.views import redirect_to_login
 from django.core.exceptions import PermissionDenied
 from django.http import HttpResponseRedirect
@@ -14,10 +13,11 @@
 from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import ensure_csrf_cookie
 from django.views.generic.base import View
+
 from rest_framework import mixins, viewsets
 from rest_framework.response import Response
-from rules.contrib.views import \
-    PermissionRequiredMixin as RulesPermissionRequiredMixin
+
+from rules.contrib.views import PermissionRequiredMixin as RulesPermissionRequiredMixin
 
 from .serializers import ChoicesSerializer
 from .utils import get_next, get_referer, get_referer_path_info
@@ -89,10 +89,10 @@ def post(self, request, *args, **kwargs):
         if 'cancel' in request.POST:
             return HttpResponseRedirect(get_next(request))
         else:
-            return super(RedirectViewMixin, self).post(request, *args, **kwargs)
+            return super().post(request, *args, **kwargs)
 
     def get_context_data(self, **kwargs):
-        context_data = super(RedirectViewMixin, self).get_context_data(**kwargs)
+        context_data = super().get_context_data(**kwargs)
         if 'next' in self.request.GET:
             context_data['next'] = self.request.GET['next']
         else:
@@ -103,10 +103,10 @@ def get_success_url(self):
         if 'next' in self.request.GET:
             return self.request.GET['next']
         else:
-            return super(RedirectViewMixin, self).get_success_url()
+            return super().get_success_url()
 
 
-class PermissionRedirectMixin(object):
+class PermissionRedirectMixin:
 
     def handle_no_permission(self):
         if self.request.user.is_authenticated:
@@ -114,11 +114,11 @@ def handle_no_permission(self):
         return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())
 
 
-class ModelPermissionMixin(LoginRequiredMixin, PermissionRedirectMixin, DjangoPermissionRequiredMixin, object):
+class ModelPermissionMixin(LoginRequiredMixin, PermissionRedirectMixin, DjangoPermissionRequiredMixin):
     pass
 
 
-class ObjectPermissionMixin(PermissionRedirectMixin, RulesPermissionRequiredMixin, object):
+class ObjectPermissionMixin(PermissionRedirectMixin, RulesPermissionRequiredMixin):
     pass
 
 
diff --git a/rdmo/core/viewsets.py b/rdmo/core/viewsets.py
index 1b6af7cbbe..eaa97a2072 100644
--- a/rdmo/core/viewsets.py
+++ b/rdmo/core/viewsets.py
@@ -1,8 +1,8 @@
 from django.conf import settings
 from django.contrib.auth.models import Group
 from django.contrib.sites.models import Site
-from rest_framework import status, viewsets
-from rest_framework.decorators import action
+
+from rest_framework import viewsets
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 
diff --git a/rdmo/core/xml.py b/rdmo/core/xml.py
index a4f67f4963..6e2c53b82d 100644
--- a/rdmo/core/xml.py
+++ b/rdmo/core/xml.py
@@ -59,7 +59,7 @@ def flat_xml_to_elements(root):
                     element[tag]['model'] = models[sub_node.tag]
             elif 'lang' in sub_node.attrib:
                 # this node has the lang attribute!
-                element['%s_%s' % (tag, sub_node.attrib['lang'])] = sub_node.text
+                element['{}_{}'.format(tag, sub_node.attrib['lang'])] = sub_node.text
             elif list(sub_node):
                 # this node is a list!
                 element[tag] = []
@@ -86,7 +86,7 @@ def flat_xml_to_elements(root):
 def get_ns_tag(tag, ns_map):
     tag_split = tag.split(':')
     try:
-        return '{%s}%s' % (ns_map[tag_split[0]], tag_split[1])
+        return f'{{{ns_map[tag_split[0]]}}}{tag_split[1]}'
     except KeyError:
         return None
 
diff --git a/rdmo/domain/admin.py b/rdmo/domain/admin.py
index b7abc1552b..756e41f4d3 100644
--- a/rdmo/domain/admin.py
+++ b/rdmo/domain/admin.py
@@ -3,8 +3,7 @@
 from django.db import models
 
 from .models import Attribute
-from .validators import (AttributeLockedValidator, AttributeParentValidator,
-                         AttributeUniqueURIValidator)
+from .validators import AttributeLockedValidator, AttributeParentValidator, AttributeUniqueURIValidator
 
 
 class AttributeAdminForm(forms.ModelForm):
diff --git a/rdmo/domain/imports.py b/rdmo/domain/imports.py
index 6cd660b116..5a97f6bcfa 100644
--- a/rdmo/domain/imports.py
+++ b/rdmo/domain/imports.py
@@ -2,12 +2,10 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (check_permissions, set_common_fields,
-                               set_foreign_field, validate_instance)
+from rdmo.core.imports import check_permissions, set_common_fields, set_foreign_field, validate_instance
 
 from .models import Attribute
-from .validators import (AttributeLockedValidator, AttributeParentValidator,
-                         AttributeUniqueURIValidator)
+from .validators import AttributeLockedValidator, AttributeParentValidator, AttributeUniqueURIValidator
 
 logger = logging.getLogger(__name__)
 
diff --git a/rdmo/domain/models.py b/rdmo/domain/models.py
index fd377d4887..3da76b9137 100644
--- a/rdmo/domain/models.py
+++ b/rdmo/domain/models.py
@@ -2,9 +2,10 @@
 from django.contrib.sites.models import Site
 from django.db import models
 from django.utils.translation import gettext_lazy as _
+
 from mptt.models import MPTTModel, TreeForeignKey
 
-from rdmo.core.utils import copy_model, join_url
+from rdmo.core.utils import join_url
 
 
 class Attribute(MPTTModel):
diff --git a/rdmo/domain/serializers/v1.py b/rdmo/domain/serializers/v1.py
index 4e2b5f3e3a..cf3254e06c 100644
--- a/rdmo/domain/serializers/v1.py
+++ b/rdmo/domain/serializers/v1.py
@@ -3,13 +3,11 @@
 from rest_framework import serializers
 
 from rdmo.conditions.models import Condition
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin)
+from rdmo.core.serializers import ElementModelSerializerMixin, ReadOnlyObjectPermissionSerializerMixin
 from rdmo.questions.models import Page, Question, QuestionSet
 
 from ..models import Attribute
-from ..validators import (AttributeLockedValidator, AttributeParentValidator,
-                          AttributeUniqueURIValidator)
+from ..validators import AttributeLockedValidator, AttributeParentValidator, AttributeUniqueURIValidator
 
 log = logging.getLogger(__name__)
 
@@ -52,7 +50,8 @@ class AttributeSerializer(BaseAttributeSerializer):
     values_count = serializers.IntegerField(read_only=True)
 
     class Meta(BaseAttributeSerializer.Meta):
-        fields = BaseAttributeSerializer.Meta.fields + (
+        fields = (
+            *BaseAttributeSerializer.Meta.fields,
             'conditions',
             'pages',
             'questionsets',
@@ -78,8 +77,9 @@ def get_attributes(self, obj):
 class AttributeListSerializer(BaseAttributeSerializer):
 
     class Meta(BaseAttributeSerializer.Meta):
-        fields = BaseAttributeSerializer.Meta.fields + (
-            'is_leaf_node',
+        fields = (
+            *BaseAttributeSerializer.Meta.fields,
+            'is_leaf_node'
         )
 
 
@@ -88,8 +88,9 @@ class AttributeNestedSerializer(AttributeListSerializer):
     elements = serializers.SerializerMethodField()
 
     class Meta(AttributeListSerializer.Meta):
-        fields = AttributeListSerializer.Meta.fields + (
-            'elements',
+        fields = (
+            *AttributeListSerializer.Meta.fields,
+            'elements'
         )
 
     def get_elements(self, obj):
diff --git a/rdmo/domain/validators.py b/rdmo/domain/validators.py
index e6cb5be542..c6939d5ed9 100644
--- a/rdmo/domain/validators.py
+++ b/rdmo/domain/validators.py
@@ -1,7 +1,6 @@
 from django.utils.translation import gettext_lazy as _
 
-from rdmo.core.validators import (InstanceValidator, LockedValidator,
-                                  UniqueURIValidator)
+from rdmo.core.validators import InstanceValidator, LockedValidator, UniqueURIValidator
 
 from .models import Attribute
 
@@ -46,14 +45,18 @@ def __call__(self, data, serializer=None):
                     # get the original from the view when cloning an attribute
                     if parent in view.get_object().get_descendants(include_self=True):
                         self.raise_validation_error({
-                            'parent': [_('An attribute may not be cloned to be a child of itself or one of its descendants.')]
+                            'parent': [
+                                _('An attribute may not be cloned to be a child of itself or one of its descendants.')
+                            ]
                         })
 
             # only check updated attributes
             if self.instance:
                 if parent in self.instance.get_descendants(include_self=True):
                     self.raise_validation_error({
-                        'parent': [_('An attribute may not be moved to be a child of itself or one of its descendants.')]
+                        'parent': [
+                            _('An attribute may not be moved to be a child of itself or one of its descendants.')
+                        ]
                     })
 
 
diff --git a/rdmo/domain/viewsets.py b/rdmo/domain/viewsets.py
index f4f042bbdd..268702fc7f 100644
--- a/rdmo/domain/viewsets.py
+++ b/rdmo/domain/viewsets.py
@@ -1,10 +1,12 @@
 from django.db import models
-from django_filters.rest_framework import DjangoFilterBackend
+
 from rest_framework.decorators import action
 from rest_framework.filters import SearchFilter
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
 
+from django_filters.rest_framework import DjangoFilterBackend
+
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import render_to_csv, render_to_format
@@ -12,8 +14,12 @@
 from .models import Attribute
 from .renderers import AttributeRenderer
 from .serializers.export import AttributeExportSerializer
-from .serializers.v1 import (AttributeIndexSerializer, AttributeListSerializer,
-                             AttributeNestedSerializer, AttributeSerializer)
+from .serializers.v1 import (
+    AttributeIndexSerializer,
+    AttributeListSerializer,
+    AttributeNestedSerializer,
+    AttributeSerializer,
+)
 
 
 class AttributeViewSet(ModelViewSet):
@@ -76,6 +82,8 @@ def detail_export(self, request, pk=None, export_format='xml'):
             delimiter = ',' if export_format == 'csvcomma' else ';'
             return render_to_csv('domain', rows, delimiter)
         else:
-            return render_to_format(self.request, export_format, self.get_object().key, 'domain/export/attributes.html', {
-                'attributes': attributes
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().key, 'domain/export/attributes.html', {
+                    'attributes': attributes
+                }
+            )
diff --git a/rdmo/management/apps.py b/rdmo/management/apps.py
index 60aed1e190..40349d3b19 100644
--- a/rdmo/management/apps.py
+++ b/rdmo/management/apps.py
@@ -7,4 +7,4 @@ class ManagementConfig(AppConfig):
     verbose_name = _('Management')
 
     def ready(self):
-        from . import rules
+        from . import rules  # noqa: F401
diff --git a/rdmo/management/assets/js/components/import/ImportCondition.js b/rdmo/management/assets/js/components/import/ImportCondition.js
index f6edc4907a..58598f2429 100644
--- a/rdmo/management/assets/js/components/import/ImportCondition.js
+++ b/rdmo/management/assets/js/components/import/ImportCondition.js
@@ -47,4 +47,4 @@ ImportCondition.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportCondition
\ No newline at end of file
+export default ImportCondition
diff --git a/rdmo/management/assets/js/components/import/ImportOption.js b/rdmo/management/assets/js/components/import/ImportOption.js
index e2ca7d4828..af8f0166ac 100644
--- a/rdmo/management/assets/js/components/import/ImportOption.js
+++ b/rdmo/management/assets/js/components/import/ImportOption.js
@@ -47,4 +47,4 @@ ImportOption.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportOption
\ No newline at end of file
+export default ImportOption
diff --git a/rdmo/management/assets/js/components/import/ImportOptionSet.js b/rdmo/management/assets/js/components/import/ImportOptionSet.js
index 6eebc93543..e7798c051d 100644
--- a/rdmo/management/assets/js/components/import/ImportOptionSet.js
+++ b/rdmo/management/assets/js/components/import/ImportOptionSet.js
@@ -47,4 +47,4 @@ ImportOptionSet.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportOptionSet
\ No newline at end of file
+export default ImportOptionSet
diff --git a/rdmo/management/assets/js/components/import/ImportPage.js b/rdmo/management/assets/js/components/import/ImportPage.js
index 42a9b210fc..2fb438f904 100644
--- a/rdmo/management/assets/js/components/import/ImportPage.js
+++ b/rdmo/management/assets/js/components/import/ImportPage.js
@@ -47,4 +47,4 @@ ImportPage.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportPage
\ No newline at end of file
+export default ImportPage
diff --git a/rdmo/management/assets/js/components/import/ImportQuestion.js b/rdmo/management/assets/js/components/import/ImportQuestion.js
index 441beaca84..12c757ee28 100644
--- a/rdmo/management/assets/js/components/import/ImportQuestion.js
+++ b/rdmo/management/assets/js/components/import/ImportQuestion.js
@@ -47,4 +47,4 @@ ImportQuestion.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportQuestion
\ No newline at end of file
+export default ImportQuestion
diff --git a/rdmo/management/assets/js/components/import/ImportQuestionSet.js b/rdmo/management/assets/js/components/import/ImportQuestionSet.js
index 23f44988a5..8163015cbe 100644
--- a/rdmo/management/assets/js/components/import/ImportQuestionSet.js
+++ b/rdmo/management/assets/js/components/import/ImportQuestionSet.js
@@ -47,4 +47,4 @@ ImportQuestionSet.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportQuestionSet
\ No newline at end of file
+export default ImportQuestionSet
diff --git a/rdmo/management/assets/js/components/import/ImportSection.js b/rdmo/management/assets/js/components/import/ImportSection.js
index 986c0e5e75..92203a8771 100644
--- a/rdmo/management/assets/js/components/import/ImportSection.js
+++ b/rdmo/management/assets/js/components/import/ImportSection.js
@@ -47,4 +47,4 @@ ImportSection.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportSection
\ No newline at end of file
+export default ImportSection
diff --git a/rdmo/management/assets/js/components/import/ImportTask.js b/rdmo/management/assets/js/components/import/ImportTask.js
index 964ef7ffef..72b2b73158 100644
--- a/rdmo/management/assets/js/components/import/ImportTask.js
+++ b/rdmo/management/assets/js/components/import/ImportTask.js
@@ -49,4 +49,4 @@ ImportTask.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportTask
\ No newline at end of file
+export default ImportTask
diff --git a/rdmo/management/assets/js/components/import/ImportView.js b/rdmo/management/assets/js/components/import/ImportView.js
index 903b1676de..e48196efb5 100644
--- a/rdmo/management/assets/js/components/import/ImportView.js
+++ b/rdmo/management/assets/js/components/import/ImportView.js
@@ -49,4 +49,4 @@ ImportView.propTypes = {
   importActions: PropTypes.object.isRequired
 }
 
-export default ImportView
\ No newline at end of file
+export default ImportView
diff --git a/rdmo/management/imports.py b/rdmo/management/imports.py
index 70a47a4bff..e47a3c7001 100644
--- a/rdmo/management/imports.py
+++ b/rdmo/management/imports.py
@@ -3,9 +3,7 @@
 from rdmo.conditions.imports import import_condition
 from rdmo.domain.imports import import_attribute
 from rdmo.options.imports import import_option, import_optionset
-from rdmo.questions.imports import (import_catalog, import_page,
-                                    import_question, import_questionset,
-                                    import_section)
+from rdmo.questions.imports import import_catalog, import_page, import_question, import_questionset, import_section
 from rdmo.tasks.imports import import_task
 from rdmo.views.imports import import_view
 
diff --git a/rdmo/management/management/commands/import.py b/rdmo/management/management/commands/import.py
index 81159e6b1a..3438164220 100644
--- a/rdmo/management/management/commands/import.py
+++ b/rdmo/management/management/commands/import.py
@@ -3,8 +3,7 @@
 from django.core.management.base import BaseCommand, CommandError
 from django.utils.translation import gettext_lazy as _
 
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.management.imports import import_elements
 
 logger = logging.getLogger(__name__)
diff --git a/rdmo/management/urls/v1.py b/rdmo/management/urls/v1.py
index 59458ac612..d0bee28c52 100644
--- a/rdmo/management/urls/v1.py
+++ b/rdmo/management/urls/v1.py
@@ -1,4 +1,5 @@
 from django.urls import include, path
+
 from rest_framework import routers
 
 from ..viewsets import ImportViewSet, MetaViewSet, UploadViewSet
diff --git a/rdmo/management/views.py b/rdmo/management/views.py
index a9136f9dbd..18ce86b5bf 100644
--- a/rdmo/management/views.py
+++ b/rdmo/management/views.py
@@ -2,16 +2,17 @@
 
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.views.generic import TemplateView
+
 from rules import test_rule
-from rules.contrib.views import \
-    PermissionRequiredMixin as RulesPermissionRequiredMixin
+from rules.contrib.views import PermissionRequiredMixin as RulesPermissionRequiredMixin
 
 from rdmo.core.views import CSRFViewMixin, PermissionRedirectMixin
 
 logger = logging.getLogger(__name__)
 
 
-class ManagementView(LoginRequiredMixin, PermissionRedirectMixin, RulesPermissionRequiredMixin, CSRFViewMixin, TemplateView):
+class ManagementView(LoginRequiredMixin, PermissionRedirectMixin, RulesPermissionRequiredMixin,
+                     CSRFViewMixin, TemplateView):
     template_name = 'management/management.html'
 
     def has_permission(self):
diff --git a/rdmo/management/viewsets.py b/rdmo/management/viewsets.py
index 414431c583..36c625dc85 100644
--- a/rdmo/management/viewsets.py
+++ b/rdmo/management/viewsets.py
@@ -1,6 +1,7 @@
 import logging
 
 from django.utils.translation import gettext_lazy as _
+
 from rest_framework import viewsets
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
@@ -9,8 +10,7 @@
 from rdmo.conditions.models import Condition
 from rdmo.core.imports import handle_uploaded_file
 from rdmo.core.utils import get_model_field_meta, is_truthy
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.domain.models import Attribute
 from rdmo.options.models import Option, OptionSet
 from rdmo.questions.models import Catalog, Page, Question, QuestionSet, Section
@@ -50,8 +50,8 @@ def create(self, request, *args, **kwargs):
         # step 1: store xml file as tmp file
         try:
             uploaded_file = request.FILES['file']
-        except KeyError:
-            raise ValidationError({'file': [_('This field may not be blank.')]})
+        except KeyError as e:
+            raise ValidationError({'file': [_('This field may not be blank.')]}) from e
         else:
             import_tmpfile_name = handle_uploaded_file(uploaded_file)
 
@@ -59,20 +59,22 @@ def create(self, request, *args, **kwargs):
         root = read_xml_file(import_tmpfile_name)
         if root is None:
             logger.info('XML parsing error. Import failed.')
-            raise ValidationError({'file': [_('The content of the xml file does not consist of well formed data or markup.')]})
+            raise ValidationError({'file': [
+                _('The content of the xml file does not consist of well formed data or markup.')
+            ]})
 
         # step 3: create element dicts from xml
         try:
             elements = flat_xml_to_elements(root)
         except KeyError as e:
             logger.info('Import failed with KeyError (%s)' % e)
-            raise ValidationError({'file': [_('This is not a valid RDMO XML file.')]})
+            raise ValidationError({'file': [_('This is not a valid RDMO XML file.')]}) from e
         except TypeError as e:
             logger.info('Import failed with TypeError (%s)' % e)
-            raise ValidationError({'file': [_('This is not a valid RDMO XML file.')]})
+            raise ValidationError({'file': [_('This is not a valid RDMO XML file.')]}) from e
         except AttributeError as e:
             logger.info('Import failed with AttributeError (%s)' % e)
-            raise ValidationError({'file': [_('This is not a valid RDMO XML file.')]})
+            raise ValidationError({'file': [_('This is not a valid RDMO XML file.')]}) from e
 
         # step 4: convert elements from previous versions
         elements = convert_elements(elements, root.attrib.get('version'))
@@ -98,10 +100,10 @@ def create(self, request, *args, **kwargs):
         # step 1: store xml file as tmp file
         try:
             elements = request.data['elements']
-        except KeyError:
-            raise ValidationError({'elements': [_('This field may not be blank.')]})
-        except TypeError:
-            raise ValidationError({'elements': [_('This is not a valid RDMO import JSON.')]})
+        except KeyError as e:
+            raise ValidationError({'elements': [_('This field may not be blank.')]}) from e
+        except TypeError as e:
+            raise ValidationError({'elements': [_('This is not a valid RDMO import JSON.')]}) from e
 
         # step 3: import the elements
         import_elements(elements, user=request.user)
diff --git a/rdmo/options/admin.py b/rdmo/options/admin.py
index baaffdafde..bf5e21c65c 100644
--- a/rdmo/options/admin.py
+++ b/rdmo/options/admin.py
@@ -4,8 +4,12 @@
 from rdmo.core.utils import get_language_fields
 
 from .models import Option, OptionSet, OptionSetOption
-from .validators import (OptionLockedValidator, OptionSetLockedValidator,
-                         OptionSetUniqueURIValidator, OptionUniqueURIValidator)
+from .validators import (
+    OptionLockedValidator,
+    OptionSetLockedValidator,
+    OptionSetUniqueURIValidator,
+    OptionUniqueURIValidator,
+)
 
 
 class OptionSetAdminForm(ElementAdminForm):
@@ -48,7 +52,7 @@ class OptionSetAdmin(admin.ModelAdmin):
 class OptionAdmin(admin.ModelAdmin):
     form = OptionAdminForm
 
-    search_fields = ['uri'] + get_language_fields('text')
+    search_fields = ['uri', *get_language_fields('text')]
     list_display = ('uri', 'text', 'additional_input')
     readonly_fields = ('uri', )
     list_filter = ('editors', 'optionsets', 'additional_input')
diff --git a/rdmo/options/imports.py b/rdmo/options/imports.py
index fa2023a0a0..757fc34778 100644
--- a/rdmo/options/imports.py
+++ b/rdmo/options/imports.py
@@ -2,15 +2,23 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (check_permissions, set_common_fields,
-                               set_lang_field, set_m2m_instances,
-                               set_m2m_through_instances,
-                               set_reverse_m2m_through_instance,
-                               validate_instance)
+from rdmo.core.imports import (
+    check_permissions,
+    set_common_fields,
+    set_lang_field,
+    set_m2m_instances,
+    set_m2m_through_instances,
+    set_reverse_m2m_through_instance,
+    validate_instance,
+)
 
 from .models import Option, OptionSet
-from .validators import (OptionLockedValidator, OptionSetLockedValidator,
-                         OptionSetUniqueURIValidator, OptionUniqueURIValidator)
+from .validators import (
+    OptionLockedValidator,
+    OptionSetLockedValidator,
+    OptionSetUniqueURIValidator,
+    OptionUniqueURIValidator,
+)
 
 logger = logging.getLogger(__name__)
 
diff --git a/rdmo/options/models.py b/rdmo/options/models.py
index 565d30ec20..f6b77f67eb 100644
--- a/rdmo/options/models.py
+++ b/rdmo/options/models.py
@@ -226,7 +226,7 @@ def text(self):
 
     @property
     def label(self):
-        return '%s ("%s")' % (self.uri, self.text)
+        return f'{self.uri} ("{self.text}")'
 
     @property
     def is_locked(self):
diff --git a/rdmo/options/serializers/v1/__init__.py b/rdmo/options/serializers/v1/__init__.py
index 2dc884099a..def6624fd8 100644
--- a/rdmo/options/serializers/v1/__init__.py
+++ b/rdmo/options/serializers/v1/__init__.py
@@ -1,3 +1,2 @@
 from .option import OptionIndexSerializer, OptionSerializer
-from .optionset import (OptionSetIndexSerializer, OptionSetNestedSerializer,
-                        OptionSetSerializer)
+from .optionset import OptionSetIndexSerializer, OptionSetNestedSerializer, OptionSetSerializer
diff --git a/rdmo/options/serializers/v1/option.py b/rdmo/options/serializers/v1/option.py
index d685a1d40b..b608dcdd5b 100644
--- a/rdmo/options/serializers/v1/option.py
+++ b/rdmo/options/serializers/v1/option.py
@@ -1,10 +1,12 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   ThroughModelSerializerMixin,
-                                   TranslationSerializerMixin)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    ThroughModelSerializerMixin,
+    TranslationSerializerMixin,
+)
 
 from ...models import Option, OptionSet
 from ...validators import OptionLockedValidator, OptionUniqueURIValidator
diff --git a/rdmo/options/serializers/v1/optionset.py b/rdmo/options/serializers/v1/optionset.py
index 885a439c53..264c2f7128 100644
--- a/rdmo/options/serializers/v1/optionset.py
+++ b/rdmo/options/serializers/v1/optionset.py
@@ -1,8 +1,10 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   ThroughModelSerializerMixin)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    ThroughModelSerializerMixin,
+)
 from rdmo.questions.models import Question
 
 from ...models import OptionSet, OptionSetOption
@@ -64,8 +66,9 @@ class OptionSetNestedSerializer(OptionSetSerializer):
     elements = serializers.SerializerMethodField()
 
     class Meta(OptionSetSerializer.Meta):
-        fields = OptionSetSerializer.Meta.fields + (
-            'elements',
+        fields = (
+            *OptionSetSerializer.Meta.fields,
+            'elements'
         )
 
     def get_elements(self, obj):
diff --git a/rdmo/options/urls/v1.py b/rdmo/options/urls/v1.py
index 5a5eb1ce11..88e879bb23 100644
--- a/rdmo/options/urls/v1.py
+++ b/rdmo/options/urls/v1.py
@@ -1,4 +1,5 @@
 from django.urls import include, path
+
 from rest_framework import routers
 
 from ..viewsets import OptionSetViewSet, OptionViewSet, ProviderViewSet
diff --git a/rdmo/options/viewsets.py b/rdmo/options/viewsets.py
index 8e423b85de..804e003fcc 100644
--- a/rdmo/options/viewsets.py
+++ b/rdmo/options/viewsets.py
@@ -1,12 +1,14 @@
 from django.conf import settings
 from django.db import models
-from django_filters.rest_framework import DjangoFilterBackend
+
 from rest_framework.decorators import action
 from rest_framework.filters import SearchFilter
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
 
+from django_filters.rest_framework import DjangoFilterBackend
+
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import is_truthy, render_to_format
@@ -14,11 +16,14 @@
 
 from .models import Option, OptionSet
 from .renderers import OptionRenderer, OptionSetRenderer
-from .serializers.export import (OptionExportSerializer,
-                                 OptionSetExportSerializer)
-from .serializers.v1 import (OptionIndexSerializer, OptionSerializer,
-                             OptionSetIndexSerializer,
-                             OptionSetNestedSerializer, OptionSetSerializer)
+from .serializers.export import OptionExportSerializer, OptionSetExportSerializer
+from .serializers.v1 import (
+    OptionIndexSerializer,
+    OptionSerializer,
+    OptionSetIndexSerializer,
+    OptionSetNestedSerializer,
+    OptionSetSerializer,
+)
 
 
 class OptionSetViewSet(ModelViewSet):
@@ -70,9 +75,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = OptionSetRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'options/export/optionsets.html', {
-                'optionsets': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'options/export/optionsets.html', {
+                    'optionsets': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
@@ -127,9 +134,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = OptionRenderer().render([serializer.data])
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'options/export/options.html', {
-                'options': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'options/export/options.html', {
+                    'options': [self.get_object()]
+                }
+            )
 
 
 class ProviderViewSet(ChoicesViewSet):
diff --git a/rdmo/overlays/models.py b/rdmo/overlays/models.py
index 86371ccfb4..16703a497b 100644
--- a/rdmo/overlays/models.py
+++ b/rdmo/overlays/models.py
@@ -33,4 +33,4 @@ class Meta:
         verbose_name_plural = _('Overlays')
 
     def __str__(self):
-        return '{} / {}'.format(self.user, self.url_name)
+        return f'{self.user} / {self.url_name}'
diff --git a/rdmo/overlays/urls/v1.py b/rdmo/overlays/urls/v1.py
index b560efb73b..9054f26c1d 100644
--- a/rdmo/overlays/urls/v1.py
+++ b/rdmo/overlays/urls/v1.py
@@ -1,4 +1,5 @@
 from django.urls import include, path
+
 from rest_framework import routers
 
 from ..viewsets import OverlayViewSet
diff --git a/rdmo/overlays/viewsets.py b/rdmo/overlays/viewsets.py
index 120c8692b2..20a1aa3895 100644
--- a/rdmo/overlays/viewsets.py
+++ b/rdmo/overlays/viewsets.py
@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.contrib.sites.models import Site
+
 from rest_framework.decorators import action
 from rest_framework.exceptions import NotFound
 from rest_framework.permissions import IsAuthenticated
@@ -11,7 +12,8 @@
 
 class OverlayViewSet(ViewSet):
 
-    @action(detail=False, methods=['post'], url_path='(?P<url_name>[-\\w]+)/current', permission_classes=[IsAuthenticated])
+    @action(detail=False, methods=['post'], url_path='(?P<url_name>[-\\w]+)/current',
+            permission_classes=[IsAuthenticated])
     def current(self, request, url_name=None):
         site = Site.objects.get_current()
         overlays = settings.OVERLAYS.get(url_name)[:]
@@ -29,7 +31,8 @@ def current(self, request, url_name=None):
             'last': overlay.current == overlays[-1]
         })
 
-    @action(detail=False, methods=['post'], url_path='(?P<url_name>[-\\w]+)/next', permission_classes=[IsAuthenticated])
+    @action(detail=False, methods=['post'], url_path='(?P<url_name>[-\\w]+)/next',
+            permission_classes=[IsAuthenticated])
     def next(self, request, url_name=None):
         site = Site.objects.get_current()
         overlays = settings.OVERLAYS.get(url_name)[:]
@@ -51,7 +54,8 @@ def next(self, request, url_name=None):
             'last': overlay.current == overlays[-1]
         })
 
-    @action(detail=False, methods=['post'], url_path='(?P<url_name>[-\\w]+)/dismiss', permission_classes=[IsAuthenticated])
+    @action(detail=False, methods=['post'], url_path='(?P<url_name>[-\\w]+)/dismiss',
+            permission_classes=[IsAuthenticated])
     def dismiss(self, request, url_name=None):
         site = Site.objects.get_current()
         overlays = settings.OVERLAYS.get(url_name)[:]
diff --git a/rdmo/projects/admin.py b/rdmo/projects/admin.py
index 70d2af6cda..e8890cf19d 100644
--- a/rdmo/projects/admin.py
+++ b/rdmo/projects/admin.py
@@ -1,9 +1,18 @@
 from django.contrib import admin
 from django.db.models import Prefetch
 
-from .models import (Continuation, Integration, IntegrationOption, Invite,
-                     Issue, IssueResource, Membership, Project, Snapshot,
-                     Value)
+from .models import (
+    Continuation,
+    Integration,
+    IntegrationOption,
+    Invite,
+    Issue,
+    IssueResource,
+    Membership,
+    Project,
+    Snapshot,
+    Value,
+)
 
 
 class ProjectAdmin(admin.ModelAdmin):
diff --git a/rdmo/projects/apps.py b/rdmo/projects/apps.py
index a64d38c848..178bb4ccd3 100644
--- a/rdmo/projects/apps.py
+++ b/rdmo/projects/apps.py
@@ -8,7 +8,7 @@ class ProjectsConfig(AppConfig):
     verbose_name = _('Projects')
 
     def ready(self):
-        from . import rules # noqa: F401
+        from . import rules  # noqa: F401
 
         if settings.PROJECT_REMOVE_VIEWS:
-            from . import handlers # noqa: F401
+            from . import handlers  # noqa: F401
diff --git a/rdmo/projects/exports.py b/rdmo/projects/exports.py
index 52f728a850..6be2125f7c 100644
--- a/rdmo/projects/exports.py
+++ b/rdmo/projects/exports.py
@@ -5,8 +5,8 @@
 from rdmo.core.exports import prettify_xml
 from rdmo.core.plugins import Plugin
 from rdmo.core.utils import render_to_csv, render_to_json
-from rdmo.views.utils import ProjectWrapper
 from rdmo.views.templatetags import view_tags
+from rdmo.views.utils import ProjectWrapper
 
 from .renderers import XMLRenderer
 from .serializers.export import ProjectSerializer as ExportSerializer
@@ -31,7 +31,8 @@ def get_set(self, path, set_prefix=''):
                                   .order_by('set_index', 'collection_index')
 
     def get_values(self, path, set_prefix='', set_index=0):
-        return self.project.values.filter(snapshot=self.snapshot, attribute__path=path, set_prefix=set_prefix, set_index=set_index) \
+        return self.project.values.filter(snapshot=self.snapshot, attribute__path=path,
+                                          set_prefix=set_prefix, set_index=set_index) \
                                   .order_by('collection_index')
 
     def get_value(self, path, set_prefix='', set_index=0, collection_index=0):
diff --git a/rdmo/projects/filters.py b/rdmo/projects/filters.py
index 843bc576fb..064f712363 100644
--- a/rdmo/projects/filters.py
+++ b/rdmo/projects/filters.py
@@ -1,6 +1,7 @@
-from django_filters import CharFilter, FilterSet
 from rest_framework.filters import BaseFilterBackend
 
+from django_filters import CharFilter, FilterSet
+
 from .models import Project
 
 
diff --git a/rdmo/projects/forms.py b/rdmo/projects/forms.py
index 7ef1761f3a..a6e32f7195 100644
--- a/rdmo/projects/forms.py
+++ b/rdmo/projects/forms.py
@@ -12,8 +12,7 @@
 from rdmo.core.utils import markdown2html
 
 from .constants import ROLE_CHOICES
-from .models import (Integration, IntegrationOption, Invite, Membership,
-                     Project, Snapshot)
+from .models import Integration, IntegrationOption, Invite, Membership, Project, Snapshot
 
 
 class CatalogChoiceField(forms.ModelChoiceField):
@@ -22,21 +21,23 @@ class CatalogChoiceField(forms.ModelChoiceField):
 
     def label_from_instance(self, obj):
         if obj.available is False:
-            return mark_safe('<div class="text-muted">%s%s</br>%s</div>' % (obj.title, self._unavailable_icon, markdown2html(obj.help)))
+            return mark_safe('<div class="text-muted">{}{}</br>{}</div>'.format(
+                obj.title, self._unavailable_icon, markdown2html(obj.help)
+            ))
 
-        return mark_safe('<b>%s</b></br>%s' % (obj.title, markdown2html(obj.help)))
+        return mark_safe(f'<b>{obj.title}</b></br>{markdown2html(obj.help)}')
 
 
 class TasksMultipleChoiceField(forms.ModelMultipleChoiceField):
 
     def label_from_instance(self, obj):
-        return mark_safe('<b>%s</b></br>%s' % (obj.title, markdown2html(obj.text)))
+        return mark_safe(f'<b>{obj.title}</b></br>{markdown2html(obj.text)}')
 
 
 class ViewsMultipleChoiceField(forms.ModelMultipleChoiceField):
 
     def label_from_instance(self, obj):
-        return mark_safe('<b>%s</b></br>%s' % (obj.title, markdown2html(obj.help)))
+        return mark_safe(f'<b>{obj.title}</b></br>{markdown2html(obj.help)}')
 
 
 class ProjectForm(forms.ModelForm):
@@ -166,11 +167,11 @@ class Meta:
 
     def __init__(self, *args, **kwargs):
         self.project = kwargs.pop('project')
-        super(SnapshotCreateForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def save(self, *args, **kwargs):
         self.instance.project = self.project
-        return super(SnapshotCreateForm, self).save(*args, **kwargs)
+        return super().save(*args, **kwargs)
 
 
 class MembershipCreateForm(forms.Form):
@@ -192,7 +193,8 @@ def __init__(self, *args, **kwargs):
             self.fields['silent'] = forms.BooleanField(
                 required=False,
                 label=_('Add member silently'),
-                help_text=_('As site manager or admin, you can directly add users without notifying them via e-mail, when you check the following checkbox.')
+                help_text=_('As site manager or admin, you can directly add users without notifying them via e-mail, '
+                            'when you check the following checkbox.')
             )
 
     def clean_username_or_email(self):
@@ -201,13 +203,14 @@ def clean_username_or_email(self):
 
         # check if it is a registered user
         try:
-            self.cleaned_data['user'] = usermodel.objects.get(Q(username=username_or_email) | Q(email__iexact=username_or_email))
+            self.cleaned_data['user'] = usermodel.objects.get(Q(username=username_or_email) |
+                                                              Q(email__iexact=username_or_email))
             self.cleaned_data['email'] = self.cleaned_data['user'].email
 
             if self.cleaned_data['user'] in self.project.user.all():
                 raise ValidationError(_('The user is already a member of the project.'))
 
-        except (usermodel.DoesNotExist, usermodel.MultipleObjectsReturned):
+        except (usermodel.DoesNotExist, usermodel.MultipleObjectsReturned) as e:
             if settings.PROJECT_SEND_INVITE:
                 # check if it is a valid email address, this will raise the correct ValidationError
                 EmailValidator()(username_or_email)
@@ -217,7 +220,8 @@ def clean_username_or_email(self):
             else:
                 self.cleaned_data['user'] = None
                 self.cleaned_data['email'] = None
-                raise ValidationError(_('A user with this username or e-mail was not found. Only registered users can be invited.'))
+                raise ValidationError(_('A user with this username or e-mail was not found. '
+                                        'Only registered users can be invited.')) from e
 
     def clean(self):
         if self.cleaned_data.get('silent') is True and self.cleaned_data.get('user') is None:
diff --git a/rdmo/projects/handlers.py b/rdmo/projects/handlers.py
index b6cf047684..6f65709cbe 100644
--- a/rdmo/projects/handlers.py
+++ b/rdmo/projects/handlers.py
@@ -5,8 +5,8 @@
 from django.db.models.signals import m2m_changed
 from django.dispatch import receiver
 
+from rdmo.projects.models import Membership, Project
 from rdmo.questions.models import Catalog
-from rdmo.projects.models import Project, Membership
 from rdmo.views.models import View
 
 logger = logging.getLogger(__name__)
diff --git a/rdmo/projects/imports.py b/rdmo/projects/imports.py
index 1ace407925..3b20736a4e 100644
--- a/rdmo/projects/imports.py
+++ b/rdmo/projects/imports.py
@@ -4,15 +4,15 @@
 import mimetypes
 from urllib.parse import quote
 
-import requests
-
 from django import forms
 from django.core.files import File
 from django.shortcuts import redirect, render
 from django.utils.translation import gettext_lazy as _
 
-from rdmo.core.plugins import Plugin
+import requests
+
 from rdmo.core.imports import handle_fetched_file
+from rdmo.core.plugins import Plugin
 from rdmo.core.xml import get_ns_map, get_uri, read_xml_file
 from rdmo.domain.models import Attribute
 from rdmo.options.models import Option
@@ -20,6 +20,7 @@
 from rdmo.services.providers import GitHubProviderMixin, GitLabProviderMixin
 from rdmo.tasks.models import Task
 from rdmo.views.models import View
+
 from .models import Project, Snapshot, Value
 
 log = logging.getLogger(__name__)
diff --git a/rdmo/projects/management/commands/export_projects.py b/rdmo/projects/management/commands/export_projects.py
index e65fd0b83f..f754b3f433 100644
--- a/rdmo/projects/management/commands/export_projects.py
+++ b/rdmo/projects/management/commands/export_projects.py
@@ -1,5 +1,4 @@
 import logging
-
 from pathlib import Path
 
 from django.conf import settings
@@ -14,7 +13,6 @@
 from rdmo.views.models import View
 from rdmo.views.utils import ProjectWrapper
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -53,7 +51,7 @@ def export_answers(self):
         current_snapshot = None
 
         if self.format not in dict(settings.EXPORT_FORMATS):
-            raise CommandError('Format "{}" is not supported for answers.'.format(self.format))
+            raise CommandError(f'Format "{self.format}" is not supported for answers.')
 
         for project in self.get_queryset():
             context = {
@@ -65,19 +63,20 @@ def export_answers(self):
                 'resource_path': get_value_path(project, current_snapshot)
             }
 
-            response = render_to_format(None, context['format'], context['title'], 'projects/project_answers_export.html', context)
+            response = render_to_format(None, context['format'], context['title'],
+                                        'projects/project_answers_export.html', context)
             self.write_file(self.path / str(project.id) / 'answers', response)
 
     def export_view(self, key):
         current_snapshot = None
 
         if self.format not in dict(settings.EXPORT_FORMATS):
-            raise CommandError('Format "{}" is not supported for answers.'.format(self.format))
+            raise CommandError(f'Format "{self.format}" is not supported for answers.')
 
         try:
             view = View.objects.get(key=key)
-        except View.DoesNotExist:
-            raise CommandError('A view with the key "{}" was not found.'.format(key))
+        except View.DoesNotExist as e:
+            raise CommandError(f'A view with the key "{key}" was not found.') from e
 
         for project in self.get_queryset():
             context = {
@@ -91,14 +90,15 @@ def export_view(self, key):
                 'resource_path': get_value_path(project, current_snapshot)
             }
 
-            response = render_to_format(None, context['format'], context['title'], 'projects/project_view_export.html', context)
+            response = render_to_format(None, context['format'], context['title'],
+                                        'projects/project_view_export.html', context)
             self.write_file(self.path / str(project.id) / key, response)
 
     def export_projects(self):
         for project in self.get_queryset():
             export_plugin = get_plugin('PROJECT_EXPORTS', self.format)
             if export_plugin is None:
-                raise CommandError('Format "{}" is not supported.'.format(self.format))
+                raise CommandError(f'Format "{self.format}" is not supported.')
 
             export_plugin.project = project
             export_plugin.snapshot = None
@@ -111,7 +111,7 @@ def write_file(self, path, response):
         file_path = path / file_name
         file_path.parent.mkdir(exist_ok=True, parents=True)
 
-        print('Writing {}'.format(file_path))
+        print(f'Writing {file_path}')
 
         with file_path.open('wb') as fp:
             fp.write(response.content)
diff --git a/rdmo/projects/management/commands/find_inactive_projects.py b/rdmo/projects/management/commands/find_inactive_projects.py
index 63f1713fac..a540846022 100644
--- a/rdmo/projects/management/commands/find_inactive_projects.py
+++ b/rdmo/projects/management/commands/find_inactive_projects.py
@@ -1,14 +1,12 @@
-import sys
 import csv
-
+import sys
 from datetime import datetime
 
-import pytz
-
+from django.core.management.base import BaseCommand
 from django.db import models
 from django.db.models.functions import Greatest
-from django.core.management.base import BaseCommand
 
+import pytz
 
 from rdmo.projects.models import Project, Value
 
diff --git a/rdmo/projects/management/commands/prune_projects.py b/rdmo/projects/management/commands/prune_projects.py
index c101cc91c7..41cd724ee3 100644
--- a/rdmo/projects/management/commands/prune_projects.py
+++ b/rdmo/projects/management/commands/prune_projects.py
@@ -1,6 +1,6 @@
 from django.core.management.base import BaseCommand, CommandError
 
-from rdmo.projects.models import Project, Membership
+from rdmo.projects.models import Membership, Project
 
 
 class Command(BaseCommand):
@@ -36,7 +36,7 @@ def handle(self, *args, **options):
 
         self.stdout.write('Found projects without %s:' % (roles))
         for proj in candidates:
-            self.stdout.write('%s (id=%s)' % (proj, proj.id))
+            self.stdout.write(f'{proj} (id={proj.id})')
             if options['remove']:
                 self.stdout.write('...removing...', ending='')
                 proj.delete()
diff --git a/rdmo/projects/managers.py b/rdmo/projects/managers.py
index 95bd5c763d..8ee22b0da2 100644
--- a/rdmo/projects/managers.py
+++ b/rdmo/projects/managers.py
@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.db import models
+
 from mptt.models import TreeManager
 from mptt.querysets import TreeQuerySet
 
diff --git a/rdmo/projects/mixins.py b/rdmo/projects/mixins.py
index b7be2b3b46..5a18cab513 100644
--- a/rdmo/projects/mixins.py
+++ b/rdmo/projects/mixins.py
@@ -3,8 +3,8 @@
 
 from django.contrib.sites.shortcuts import get_current_site
 from django.core.exceptions import ValidationError
-from django.http import HttpResponseRedirect, Http404
-from django.shortcuts import get_object_or_404, render, redirect
+from django.http import Http404, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, redirect, render
 from django.utils.translation import gettext_lazy as _
 
 from rdmo.core.imports import handle_uploaded_file
@@ -12,11 +12,10 @@
 from rdmo.questions.models import Question
 
 from .models import Membership, Project
-from .utils import (save_import_snapshot_values, save_import_tasks,
-                    save_import_values, save_import_views)
+from .utils import save_import_snapshot_values, save_import_tasks, save_import_values, save_import_views
 
 
-class ProjectImportMixin(object):
+class ProjectImportMixin:
 
     def get_current_values(self, current_project):
         queryset = current_project.values.filter(snapshot=None).select_related('attribute', 'option')
@@ -116,7 +115,8 @@ def import_form(self):
                 self.request.session['import_key'] = import_key
 
                 # attach questions and current values
-                self.update_values(current_project, import_plugin.catalog, import_plugin.values, import_plugin.snapshots)
+                self.update_values(current_project, import_plugin.catalog,
+                                   import_plugin.values, import_plugin.snapshots)
 
                 return render(self.request, 'projects/project_import.html', {
                     'method': 'import_file',
@@ -168,7 +168,8 @@ def import_file(self):
                     }, status=400)
 
                 # attach questions and current values
-                self.update_values(current_project, import_plugin.catalog, import_plugin.values, import_plugin.snapshots)
+                self.update_values(current_project, import_plugin.catalog,
+                                   import_plugin.values, import_plugin.snapshots)
 
                 if current_project:
                     save_import_values(self.object, import_plugin.values, checked)
diff --git a/rdmo/projects/models/continuation.py b/rdmo/projects/models/continuation.py
index e01b858970..05b32baf12 100644
--- a/rdmo/projects/models/continuation.py
+++ b/rdmo/projects/models/continuation.py
@@ -30,4 +30,4 @@ class Meta:
         verbose_name_plural = _('Continuations')
 
     def __str__(self):
-        return '%s/%s/%s' % (self.project, self.user, self.page)
+        return f'{self.project}/{self.user}/{self.page}'
diff --git a/rdmo/projects/models/integration.py b/rdmo/projects/models/integration.py
index b8e29c2926..b8cdc52169 100644
--- a/rdmo/projects/models/integration.py
+++ b/rdmo/projects/models/integration.py
@@ -27,7 +27,7 @@ class Meta:
         verbose_name_plural = _('Integrations')
 
     def __str__(self):
-        return '%s / %s' % (self.project.title, self.provider_key)
+        return f'{self.project.title} / {self.provider_key}'
 
     @property
     def provider(self):
@@ -76,4 +76,4 @@ class Meta:
         verbose_name_plural = _('Integration options')
 
     def __str__(self):
-        return '%s / %s / %s = %s' % (self.integration.project.title, self.integration.provider_key, self.key, self.value)
+        return f'{self.integration.project.title} / {self.integration.provider_key} / {self.key} = {self.value}'
diff --git a/rdmo/projects/models/invite.py b/rdmo/projects/models/invite.py
index 174868e580..1c1c202b79 100644
--- a/rdmo/projects/models/invite.py
+++ b/rdmo/projects/models/invite.py
@@ -51,7 +51,7 @@ class Meta:
         verbose_name_plural = _('Invites')
 
     def __str__(self):
-        return '%s / %s / %s' % (self.project.title, self.email, self.role)
+        return f'{self.project.title} / {self.email} / {self.role}'
 
     def save(self, *args, **kwargs):
         if self.timestamp is None:
diff --git a/rdmo/projects/models/issue.py b/rdmo/projects/models/issue.py
index 0d0c1ab3fa..bfaf95474d 100644
--- a/rdmo/projects/models/issue.py
+++ b/rdmo/projects/models/issue.py
@@ -45,7 +45,7 @@ class Meta:
         verbose_name_plural = _('Issues')
 
     def __str__(self):
-        return '%s / %s / %s' % (self.project.title, self.task, self.status)
+        return f'{self.project.title} / {self.task} / {self.status}'
 
     def get_absolute_url(self):
         return reverse('project', kwargs={'pk': self.project.pk})
@@ -118,4 +118,4 @@ class Meta:
         verbose_name_plural = _('Issue resources')
 
     def __str__(self):
-        return '%s / %s / %s' % (self.issue.project.title, self.issue, self.url)
+        return f'{self.issue.project.title} / {self.issue} / {self.url}'
diff --git a/rdmo/projects/models/membership.py b/rdmo/projects/models/membership.py
index 0ec26a92f1..f794dff3f9 100644
--- a/rdmo/projects/models/membership.py
+++ b/rdmo/projects/models/membership.py
@@ -39,7 +39,7 @@ class Meta:
         verbose_name_plural = _('Memberships')
 
     def __str__(self):
-        return '%s / %s / %s' % (self.project.title, self.user.username, self.role)
+        return f'{self.project.title} / {self.user.username} / {self.role}'
 
     def get_absolute_url(self):
         return reverse('project', kwargs={'pk': self.project.pk})
diff --git a/rdmo/projects/models/project.py b/rdmo/projects/models/project.py
index e1e1afbfb7..eae2128fe2 100644
--- a/rdmo/projects/models/project.py
+++ b/rdmo/projects/models/project.py
@@ -8,7 +8,9 @@
 from django.urls import reverse
 from django.utils.functional import cached_property
 from django.utils.translation import gettext_lazy as _
+
 from mptt.models import MPTTModel, TreeForeignKey
+
 from rdmo.core.models import Model
 from rdmo.domain.models import Attribute
 from rdmo.questions.models import Catalog, Question
@@ -88,7 +90,8 @@ def clean(self):
     def progress(self):
         # create a queryset for the attributes of the catalog for this project
         # the subquery is used to query only attributes which have a question in the catalog, which is not optional
-        questions = Question.objects.filter_by_catalog(self.catalog).filter(attribute_id=OuterRef('pk')).exclude(is_optional=True)
+        questions = Question.objects.filter_by_catalog(self.catalog) \
+                                    .filter(attribute_id=OuterRef('pk')).exclude(is_optional=True)
         attributes = Attribute.objects.annotate(active=Exists(questions)).filter(active=True).distinct()
 
         # query the total number of attributes from the qs above
diff --git a/rdmo/projects/models/snapshot.py b/rdmo/projects/models/snapshot.py
index 284f9947cd..0403da40cc 100644
--- a/rdmo/projects/models/snapshot.py
+++ b/rdmo/projects/models/snapshot.py
@@ -34,7 +34,7 @@ class Meta:
         verbose_name_plural = _('Snapshots')
 
     def __str__(self):
-        return '%s / %s' % (self.project.title, self.title)
+        return f'{self.project.title} / {self.title}'
 
     def get_absolute_url(self):
         return reverse('project', kwargs={'pk': self.project.pk})
diff --git a/rdmo/projects/models/value.py b/rdmo/projects/models/value.py
index 9540cd72d7..39fec66834 100644
--- a/rdmo/projects/models/value.py
+++ b/rdmo/projects/models/value.py
@@ -1,14 +1,14 @@
 import mimetypes
 from pathlib import Path
 
-import iso8601
 from django.db import models
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
+
+import iso8601
 from django_cleanup import cleanup
 
-from rdmo.core.constants import (VALUE_TYPE_BOOLEAN, VALUE_TYPE_CHOICES,
-                                 VALUE_TYPE_DATETIME, VALUE_TYPE_TEXT)
+from rdmo.core.constants import VALUE_TYPE_BOOLEAN, VALUE_TYPE_CHOICES, VALUE_TYPE_DATETIME, VALUE_TYPE_TEXT
 from rdmo.core.models import Model
 from rdmo.domain.models import Attribute
 from rdmo.options.models import Option
@@ -170,7 +170,7 @@ def value_and_unit(self):
         if value is None:
             return ''
         elif self.unit:
-            return '%s %s' % (value, self.unit)
+            return f'{value} {self.unit}'
         else:
             return value
 
diff --git a/rdmo/projects/permissions.py b/rdmo/projects/permissions.py
index 54aaaf8e8e..c549b02e49 100644
--- a/rdmo/projects/permissions.py
+++ b/rdmo/projects/permissions.py
@@ -1,4 +1,4 @@
-from rdmo.core.permissions import log_result, HasObjectPermission
+from rdmo.core.permissions import HasObjectPermission, log_result
 
 
 class HasProjectsPermission(HasObjectPermission):
diff --git a/rdmo/projects/providers.py b/rdmo/projects/providers.py
index 9bbadf0447..a891ffc8ee 100644
--- a/rdmo/projects/providers.py
+++ b/rdmo/projects/providers.py
@@ -1,13 +1,14 @@
 import hmac
 import json
-from urllib.parse import quote, urlencode
+from urllib.parse import quote
 
 from django.core.exceptions import ObjectDoesNotExist
 from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.shortcuts import render
 from django.utils.translation import gettext_lazy as _
 
 from rdmo.core.plugins import Plugin
-from rdmo.services.providers import OauthProviderMixin, GitHubProviderMixin, GitLabProviderMixin
+from rdmo.services.providers import GitHubProviderMixin, GitLabProviderMixin, OauthProviderMixin
 
 
 class IssueProvider(Plugin):
@@ -37,7 +38,7 @@ def send_issue(self, request, issue, integration, subject, message, attachments)
         return self.post(request, url, data)
 
     def post_success(self, request, response):
-        from rdmo.projects.models import Issue, Integration, IssueResource
+        from rdmo.projects.models import Integration, Issue, IssueResource
 
         # get the upstream url of the issue
         remote_url = self.get_issue_url(response)
@@ -92,7 +93,7 @@ def get_secret(self, integration):
     def get_post_url(self, request, issue, integration, subject, message, attachments):
         repo = self.get_repo(integration)
         if repo:
-            return 'https://api.github.com/repos/{}/issues'.format(repo)
+            return f'https://api.github.com/repos/{repo}/issues'
 
     def get_post_data(self, request, issue, integration, subject, message, attachments):
         return {
@@ -159,8 +160,8 @@ class GitLabIssueProvider(GitLabProviderMixin, OauthIssueProvider):
 
     @property
     def description(self):
-        return _('This integration allow the creation of issues in arbitrary repositories on {}. '
-                 'The upload of attachments is not supported by GitLab.'.format(self.gitlab_url))
+        return _(f'This integration allow the creation of issues in arbitrary repositories on {self.gitlab_url}. '
+                 'The upload of attachments is not supported by GitLab.')
 
     def get_repo(self, integration):
         try:
diff --git a/rdmo/projects/rules.py b/rdmo/projects/rules.py
index 898867aecf..93a2fec87e 100644
--- a/rdmo/projects/rules.py
+++ b/rdmo/projects/rules.py
@@ -1,7 +1,8 @@
+from django.contrib.sites.shortcuts import get_current_site
+
 import rules
 from rules.predicates import is_superuser
 
-from django.contrib.sites.shortcuts import get_current_site
 
 @rules.predicate
 def is_project_member(user, project):
@@ -78,7 +79,7 @@ def is_site_manager_for_current_site(user, request):
 
 rules.add_perm('projects.view_issue_object', is_project_member | is_site_manager)
 rules.add_perm('projects.add_issue_object', is_project_manager | is_project_owner | is_site_manager)
-rules.add_perm('projects.change_issue_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)
+rules.add_perm('projects.change_issue_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)  # noqa: E501
 rules.add_perm('projects.delete_issue_object', is_project_manager | is_project_owner | is_site_manager)
 
 rules.add_perm('projects.view_snapshot_object', is_project_member | is_site_manager)
@@ -88,8 +89,8 @@ def is_site_manager_for_current_site(user, request):
 
 rules.add_perm('projects.view_value_object', is_project_member | is_site_manager)
 rules.add_perm('projects.add_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)
-rules.add_perm('projects.change_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)
-rules.add_perm('projects.delete_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)
+rules.add_perm('projects.change_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)  # noqa: E501
+rules.add_perm('projects.delete_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)  # noqa: E501
 
 rules.add_perm('projects.view_page_object', is_project_member | is_site_manager)
 
diff --git a/rdmo/projects/serializers/v1/__init__.py b/rdmo/projects/serializers/v1/__init__.py
index d0f4fc7c3a..7e65e7cb9f 100644
--- a/rdmo/projects/serializers/v1/__init__.py
+++ b/rdmo/projects/serializers/v1/__init__.py
@@ -1,13 +1,13 @@
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.utils.translation import gettext_lazy as _
+
 from rest_framework import serializers
 
 from rdmo.questions.models import Catalog
 from rdmo.services.validators import ProviderValidator
 
-from ...models import (Integration, IntegrationOption, Invite, Issue,
-                       IssueResource, Membership, Project, Snapshot, Value)
+from ...models import Integration, IntegrationOption, Invite, Issue, IssueResource, Membership, Project, Snapshot, Value
 from ...validators import ValueValidator
 
 
diff --git a/rdmo/projects/serializers/v1/page.py b/rdmo/projects/serializers/v1/page.py
index dd72906cad..838cfe4591 100644
--- a/rdmo/projects/serializers/v1/page.py
+++ b/rdmo/projects/serializers/v1/page.py
@@ -1,6 +1,7 @@
 from django.utils.translation import gettext_lazy as _
 
 from rest_framework import serializers
+
 from rdmo.conditions.models import Condition
 from rdmo.core.serializers import MarkdownSerializerMixin
 from rdmo.options.models import Option, OptionSet
diff --git a/rdmo/projects/static/projects/css/project.scss b/rdmo/projects/static/projects/css/project.scss
index 1feccf80a7..1fda0c6b55 100644
--- a/rdmo/projects/static/projects/css/project.scss
+++ b/rdmo/projects/static/projects/css/project.scss
@@ -21,4 +21,4 @@
     .fa-sign-out {
         width: 11px;
     }
-}
\ No newline at end of file
+}
diff --git a/rdmo/projects/urls/__init__.py b/rdmo/projects/urls/__init__.py
index f2bad7fc27..b71fb82221 100644
--- a/rdmo/projects/urls/__init__.py
+++ b/rdmo/projects/urls/__init__.py
@@ -1,75 +1,141 @@
 from django.urls import re_path
 
-from ..views import (IntegrationCreateView, IntegrationDeleteView,
-                     IntegrationUpdateView, IntegrationWebhookView,
-                     InviteDeleteView, IssueDetailView, IssueSendView,
-                     IssueUpdateView, MembershipCreateView,
-                     MembershipDeleteView, MembershipUpdateView,
-                     ProjectAnswersExportView, ProjectAnswersView,
-                     ProjectCancelView, ProjectCreateImportView,
-                     ProjectCreateView, ProjectDeleteView, ProjectDetailView,
-                     ProjectErrorView, ProjectExportView, ProjectJoinView,
-                     ProjectLeaveView, ProjectQuestionsView, ProjectsView,
-                     ProjectUpdateCatalogView, ProjectUpdateImportView,
-                     ProjectUpdateInformationView, ProjectUpdateParentView,
-                     ProjectUpdateTasksView, ProjectUpdateView,
-                     ProjectUpdateViewsView, ProjectViewExportView,
-                     ProjectViewView, SiteProjectsView, SnapshotCreateView,
-                     SnapshotRollbackView, SnapshotUpdateView)
+from ..views import (
+    IntegrationCreateView,
+    IntegrationDeleteView,
+    IntegrationUpdateView,
+    IntegrationWebhookView,
+    InviteDeleteView,
+    IssueDetailView,
+    IssueSendView,
+    IssueUpdateView,
+    MembershipCreateView,
+    MembershipDeleteView,
+    MembershipUpdateView,
+    ProjectAnswersExportView,
+    ProjectAnswersView,
+    ProjectCancelView,
+    ProjectCreateImportView,
+    ProjectCreateView,
+    ProjectDeleteView,
+    ProjectDetailView,
+    ProjectErrorView,
+    ProjectExportView,
+    ProjectJoinView,
+    ProjectLeaveView,
+    ProjectQuestionsView,
+    ProjectsView,
+    ProjectUpdateCatalogView,
+    ProjectUpdateImportView,
+    ProjectUpdateInformationView,
+    ProjectUpdateParentView,
+    ProjectUpdateTasksView,
+    ProjectUpdateView,
+    ProjectUpdateViewsView,
+    ProjectViewExportView,
+    ProjectViewView,
+    SiteProjectsView,
+    SnapshotCreateView,
+    SnapshotRollbackView,
+    SnapshotUpdateView,
+)
 
 urlpatterns = [
-    re_path(r'^$', ProjectsView.as_view(), name='projects'),
-    re_path(r'^all/$', SiteProjectsView.as_view(), name='site_projects'),
+    re_path(r'^$',
+            ProjectsView.as_view(), name='projects'),
+    re_path(r'^all/$',
+            SiteProjectsView.as_view(), name='site_projects'),
 
-    re_path(r'^create/$', ProjectCreateView.as_view(), name='project_create'),
-    re_path(r'^join/(?P<token>.+)/$', ProjectJoinView.as_view(), name='project_join'),
-    re_path(r'^cancel/(?P<token>.+)/$', ProjectCancelView.as_view(), name='project_cancel'),
-    re_path(r'^import/$', ProjectCreateImportView.as_view(), name='project_create_import'),
-    re_path(r'^import/(?P<format>[a-z-]+)/$', ProjectCreateImportView.as_view(), name='project_create_import'),
+    re_path(r'^create/$',
+            ProjectCreateView.as_view(), name='project_create'),
+    re_path(r'^join/(?P<token>.+)/$',
+            ProjectJoinView.as_view(), name='project_join'),
+    re_path(r'^cancel/(?P<token>.+)/$',
+            ProjectCancelView.as_view(), name='project_cancel'),
+    re_path(r'^import/$',
+            ProjectCreateImportView.as_view(), name='project_create_import'),
+    re_path(r'^import/(?P<format>[a-z-]+)/$',
+            ProjectCreateImportView.as_view(), name='project_create_import'),
 
-    re_path(r'^(?P<pk>[0-9]+)/$', ProjectDetailView.as_view(), name='project'),
-    re_path(r'^(?P<pk>[0-9]+)/update/$', ProjectUpdateView.as_view(), name='project_update'),
-    re_path(r'^(?P<pk>[0-9]+)/update/information/$', ProjectUpdateInformationView.as_view(), name='project_update_information'),
-    re_path(r'^(?P<pk>[0-9]+)/update/catalog/$', ProjectUpdateCatalogView.as_view(), name='project_update_catalog'),
-    re_path(r'^(?P<pk>[0-9]+)/update/parent/$', ProjectUpdateParentView.as_view(), name='project_update_parent'),
-    re_path(r'^(?P<pk>[0-9]+)/update/tasks/$', ProjectUpdateTasksView.as_view(), name='project_update_tasks'),
-    re_path(r'^(?P<pk>[0-9]+)/update/views/$', ProjectUpdateViewsView.as_view(), name='project_update_views'),
-    re_path(r'^(?P<pk>[0-9]+)/delete/$', ProjectDeleteView.as_view(), name='project_delete'),
-    re_path(r'^(?P<pk>[0-9]+)/leave/$', ProjectLeaveView.as_view(), name='project_leave'),
-    re_path(r'^(?P<pk>[0-9]+)/export/(?P<format>[a-z-]+)/$', ProjectExportView.as_view(), name='project_export'),
-    re_path(r'^(?P<pk>[0-9]+)/import/$', ProjectUpdateImportView.as_view(), name='project_update_import'),
-    re_path(r'^(?P<pk>[0-9]+)/import/(?P<format>[a-z-]+)/$', ProjectUpdateImportView.as_view(), name='project_update_import'),
+    re_path(r'^(?P<pk>[0-9]+)/$',
+            ProjectDetailView.as_view(), name='project'),
+    re_path(r'^(?P<pk>[0-9]+)/update/$',
+            ProjectUpdateView.as_view(), name='project_update'),
+    re_path(r'^(?P<pk>[0-9]+)/update/information/$',
+            ProjectUpdateInformationView.as_view(), name='project_update_information'),
+    re_path(r'^(?P<pk>[0-9]+)/update/catalog/$',
+            ProjectUpdateCatalogView.as_view(), name='project_update_catalog'),
+    re_path(r'^(?P<pk>[0-9]+)/update/parent/$',
+            ProjectUpdateParentView.as_view(), name='project_update_parent'),
+    re_path(r'^(?P<pk>[0-9]+)/update/tasks/$',
+            ProjectUpdateTasksView.as_view(), name='project_update_tasks'),
+    re_path(r'^(?P<pk>[0-9]+)/update/views/$',
+            ProjectUpdateViewsView.as_view(), name='project_update_views'),
+    re_path(r'^(?P<pk>[0-9]+)/delete/$',
+            ProjectDeleteView.as_view(), name='project_delete'),
+    re_path(r'^(?P<pk>[0-9]+)/leave/$',
+            ProjectLeaveView.as_view(), name='project_leave'),
+    re_path(r'^(?P<pk>[0-9]+)/export/(?P<format>[a-z-]+)/$',
+            ProjectExportView.as_view(), name='project_export'),
+    re_path(r'^(?P<pk>[0-9]+)/import/$',
+            ProjectUpdateImportView.as_view(), name='project_update_import'),
+    re_path(r'^(?P<pk>[0-9]+)/import/(?P<format>[a-z-]+)/$',
+            ProjectUpdateImportView.as_view(), name='project_update_import'),
 
-    re_path(r'^(?P<project_id>[0-9]+)/memberships/create/$', MembershipCreateView.as_view(), name='membership_create'),
-    re_path(r'^(?P<project_id>[0-9]+)/memberships/(?P<pk>[0-9]+)/update/$', MembershipUpdateView.as_view(), name='membership_update'),
-    re_path(r'^(?P<project_id>[0-9]+)/memberships/(?P<pk>[0-9]+)/delete/$', MembershipDeleteView.as_view(), name='membership_delete'),
-    re_path(r'^(?P<project_id>[0-9]+)/invite/(?P<pk>[0-9]+)/delete/$', InviteDeleteView.as_view(), name='invite_delete'),
+    re_path(r'^(?P<project_id>[0-9]+)/memberships/create/$',
+            MembershipCreateView.as_view(), name='membership_create'),
+    re_path(r'^(?P<project_id>[0-9]+)/memberships/(?P<pk>[0-9]+)/update/$',
+            MembershipUpdateView.as_view(), name='membership_update'),
+    re_path(r'^(?P<project_id>[0-9]+)/memberships/(?P<pk>[0-9]+)/delete/$',
+            MembershipDeleteView.as_view(), name='membership_delete'),
+    re_path(r'^(?P<project_id>[0-9]+)/invite/(?P<pk>[0-9]+)/delete/$',
+            InviteDeleteView.as_view(), name='invite_delete'),
 
-    re_path(r'^(?P<project_id>[0-9]+)/integrations/create/(?P<provider_key>[a-z]+)/$', IntegrationCreateView.as_view(), name='integration_create'),
-    re_path(r'^(?P<project_id>[0-9]+)/integrations/(?P<pk>[0-9]+)/update/$', IntegrationUpdateView.as_view(), name='integration_update'),
-    re_path(r'^(?P<project_id>[0-9]+)/integrations/(?P<pk>[0-9]+)/delete/$', IntegrationDeleteView.as_view(), name='integration_delete'),
-    re_path(r'^(?P<project_id>[0-9]+)/integrations/(?P<pk>[0-9]+)/webhook/$', IntegrationWebhookView.as_view(), name='integration_webhook'),
+    re_path(r'^(?P<project_id>[0-9]+)/integrations/create/(?P<provider_key>[a-z]+)/$',
+            IntegrationCreateView.as_view(), name='integration_create'),
+    re_path(r'^(?P<project_id>[0-9]+)/integrations/(?P<pk>[0-9]+)/update/$',
+            IntegrationUpdateView.as_view(), name='integration_update'),
+    re_path(r'^(?P<project_id>[0-9]+)/integrations/(?P<pk>[0-9]+)/delete/$',
+            IntegrationDeleteView.as_view(), name='integration_delete'),
+    re_path(r'^(?P<project_id>[0-9]+)/integrations/(?P<pk>[0-9]+)/webhook/$',
+            IntegrationWebhookView.as_view(), name='integration_webhook'),
 
-    re_path(r'^(?P<project_id>[0-9]+)/issues/(?P<pk>[0-9]+)/$', IssueDetailView.as_view(), name='issue'),
-    re_path(r'^(?P<project_id>[0-9]+)/issues/(?P<pk>[0-9]+)/update/$', IssueUpdateView.as_view(), name='issue_update'),
-    re_path(r'^(?P<project_id>[0-9]+)/issues/(?P<pk>[0-9]+)/send/$', IssueSendView.as_view(), name='issue_send'),
+    re_path(r'^(?P<project_id>[0-9]+)/issues/(?P<pk>[0-9]+)/$',
+            IssueDetailView.as_view(), name='issue'),
+    re_path(r'^(?P<project_id>[0-9]+)/issues/(?P<pk>[0-9]+)/update/$',
+            IssueUpdateView.as_view(), name='issue_update'),
+    re_path(r'^(?P<project_id>[0-9]+)/issues/(?P<pk>[0-9]+)/send/$',
+            IssueSendView.as_view(), name='issue_send'),
 
-    re_path(r'^(?P<project_id>[0-9]+)/snapshots/create/$', SnapshotCreateView.as_view(), name='snapshot_create'),
-    re_path(r'^(?P<project_id>[0-9]+)/snapshots/(?P<pk>[0-9]+)/update/$', SnapshotUpdateView.as_view(), name='snapshot_update'),
-    re_path(r'^(?P<project_id>[0-9]+)/snapshots/(?P<pk>[0-9]+)/rollback/$', SnapshotRollbackView.as_view(), name='snapshot_rollback'),
+    re_path(r'^(?P<project_id>[0-9]+)/snapshots/create/$',
+            SnapshotCreateView.as_view(), name='snapshot_create'),
+    re_path(r'^(?P<project_id>[0-9]+)/snapshots/(?P<pk>[0-9]+)/update/$',
+            SnapshotUpdateView.as_view(), name='snapshot_update'),
+    re_path(r'^(?P<project_id>[0-9]+)/snapshots/(?P<pk>[0-9]+)/rollback/$',
+            SnapshotRollbackView.as_view(), name='snapshot_rollback'),
 
-    re_path(r'^(?P<pk>[0-9]+)/answers/$', ProjectAnswersView.as_view(), name='project_answers'),
-    re_path(r'^(?P<pk>[0-9]+)/answers/export/(?P<format>[a-z]+)/$', ProjectAnswersExportView.as_view(), name='project_answers_export'),
+    re_path(r'^(?P<pk>[0-9]+)/answers/$',
+            ProjectAnswersView.as_view(), name='project_answers'),
+    re_path(r'^(?P<pk>[0-9]+)/answers/export/(?P<format>[a-z]+)/$',
+            ProjectAnswersExportView.as_view(), name='project_answers_export'),
 
-    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/answers/$', ProjectAnswersView.as_view(), name='project_answers'),
-    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/answers/export/(?P<format>[a-z]+)/$', ProjectAnswersExportView.as_view(), name='project_answers_export'),
+    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/answers/$',
+            ProjectAnswersView.as_view(), name='project_answers'),
+    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/answers/export/(?P<format>[a-z]+)/$',
+            ProjectAnswersExportView.as_view(), name='project_answers_export'),
 
-    re_path(r'^(?P<pk>[0-9]+)/views/(?P<view_id>[0-9]+)/$', ProjectViewView.as_view(), name='project_view'),
-    re_path(r'^(?P<pk>[0-9]+)/views/(?P<view_id>[0-9]+)/export/(?P<format>[a-z]+)/$', ProjectViewExportView.as_view(), name='project_view_export'),
+    re_path(r'^(?P<pk>[0-9]+)/views/(?P<view_id>[0-9]+)/$',
+            ProjectViewView.as_view(), name='project_view'),
+    re_path(r'^(?P<pk>[0-9]+)/views/(?P<view_id>[0-9]+)/export/(?P<format>[a-z]+)/$',
+            ProjectViewExportView.as_view(), name='project_view_export'),
 
-    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/views/(?P<view_id>[0-9]+)/$', ProjectViewView.as_view(), name='project_view'),
-    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/views/(?P<view_id>[0-9]+)/export/(?P<format>[a-z]+)/$', ProjectViewExportView.as_view(), name='project_view_export'),
+    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/views/(?P<view_id>[0-9]+)/$',
+            ProjectViewView.as_view(), name='project_view'),
+    re_path(r'^(?P<pk>[0-9]+)/snapshots/(?P<snapshot_id>[0-9]+)/views/(?P<view_id>[0-9]+)/export/(?P<format>[a-z]+)/$',
+            ProjectViewExportView.as_view(), name='project_view_export'),
 
-    re_path(r'^(?P<pk>[0-9]+)/questions/', ProjectQuestionsView.as_view(), name='project_questions'),
-    re_path(r'^(?P<pk>[0-9]+)/error/', ProjectErrorView.as_view(), name='project_error'),
+    re_path(r'^(?P<pk>[0-9]+)/questions/',
+            ProjectQuestionsView.as_view(), name='project_questions'),
+    re_path(r'^(?P<pk>[0-9]+)/error/',
+            ProjectErrorView.as_view(), name='project_error'),
 ]
diff --git a/rdmo/projects/urls/v1.py b/rdmo/projects/urls/v1.py
index 2af97ae2dd..8f8f373a3d 100644
--- a/rdmo/projects/urls/v1.py
+++ b/rdmo/projects/urls/v1.py
@@ -1,12 +1,23 @@
 from django.urls import include, path
+
 from rest_framework_extensions.routers import ExtendedDefaultRouter
 
-from ..viewsets import (IntegrationViewSet, InviteViewSet, IssueViewSet,
-                        MembershipViewSet, ProjectIntegrationViewSet,
-                        ProjectInviteViewSet, ProjectIssueViewSet,
-                        ProjectMembershipViewSet, ProjectPageViewSet,
-                        ProjectSnapshotViewSet, ProjectValueViewSet,
-                        ProjectViewSet, SnapshotViewSet, ValueViewSet)
+from ..viewsets import (
+    IntegrationViewSet,
+    InviteViewSet,
+    IssueViewSet,
+    MembershipViewSet,
+    ProjectIntegrationViewSet,
+    ProjectInviteViewSet,
+    ProjectIssueViewSet,
+    ProjectMembershipViewSet,
+    ProjectPageViewSet,
+    ProjectSnapshotViewSet,
+    ProjectValueViewSet,
+    ProjectViewSet,
+    SnapshotViewSet,
+    ValueViewSet,
+)
 
 app_name = 'v1-projects'
 
diff --git a/rdmo/projects/utils.py b/rdmo/projects/utils.py
index 931052d0db..94b57e0793 100644
--- a/rdmo/projects/utils.py
+++ b/rdmo/projects/utils.py
@@ -40,9 +40,7 @@ def check_conditions(conditions, values, set_prefix=None, set_index=None):
 def save_import_values(project, values, checked):
     for value in values:
         if value.attribute:
-            value_key = '{value.attribute.uri}[{value.set_prefix}][{value.set_index}][{value.collection_index}]'.format(
-                value=value
-            )
+            value_key = f'{value.attribute.uri}[{value.set_prefix}][{value.set_index}][{value.collection_index}]'
 
             if value_key in checked:
                 current_value = value.current
@@ -103,7 +101,7 @@ def save_import_snapshot_values(project, snapshots, checked):
 
         for value in snapshot.snapshot_values:
             if value.attribute:
-                value_key = '{value.attribute.uri}[{snapshot_index}][{value.set_prefix}][{value.set_index}][{value.collection_index}]'.format(
+                value_key = '{value.attribute.uri}[{snapshot_index}][{value.set_prefix}][{value.set_index}][{value.collection_index}]'.format(  # noqa: E501
                     value=value,
                     snapshot_index=snapshot.snapshot_index
                 )
diff --git a/rdmo/projects/validators.py b/rdmo/projects/validators.py
index 1bd9cba4d4..3c60a66a32 100644
--- a/rdmo/projects/validators.py
+++ b/rdmo/projects/validators.py
@@ -1,12 +1,13 @@
 from django.conf import settings
 from django.utils.translation import gettext_lazy as _
+
 from rest_framework import serializers
 
 from rdmo.core.constants import VALUE_TYPE_FILE
 from rdmo.core.utils import human2bytes
 
 
-class ValueValidator(object):
+class ValueValidator:
 
     requires_context = True
 
@@ -14,10 +15,10 @@ def __call__(self, data, serializer):
         if data.get('value_type') == VALUE_TYPE_FILE:
             try:
                 serializer.context['view'].get_object()
-            except AssertionError:
+            except AssertionError as e:
                 project = serializer.context['view'].project
 
                 if project.file_size > human2bytes(settings.PROJECT_FILE_QUOTA):
                     raise serializers.ValidationError({
                         'value': [_('You reached the file quota for this project.')]
-                    })
+                    }) from e
diff --git a/rdmo/projects/views/__init__.py b/rdmo/projects/views/__init__.py
index b9fb680aca..063dc97280 100644
--- a/rdmo/projects/views/__init__.py
+++ b/rdmo/projects/views/__init__.py
@@ -1,19 +1,29 @@
-from .integration import (IntegrationCreateView, IntegrationDeleteView,
-                          IntegrationUpdateView, IntegrationWebhookView)
+from .integration import IntegrationCreateView, IntegrationDeleteView, IntegrationUpdateView, IntegrationWebhookView
 from .invite import InviteDeleteView
 from .issue import IssueDetailView, IssueSendView, IssueUpdateView
-from .membership import (MembershipCreateView, MembershipDeleteView,
-                         MembershipUpdateView)
-from .project import (ProjectCancelView, ProjectDeleteView, ProjectDetailView,
-                      ProjectErrorView, ProjectExportView, ProjectJoinView,
-                      ProjectLeaveView, ProjectQuestionsView, ProjectsView,
-                      SiteProjectsView)
+from .membership import MembershipCreateView, MembershipDeleteView, MembershipUpdateView
+from .project import (
+    ProjectCancelView,
+    ProjectDeleteView,
+    ProjectDetailView,
+    ProjectErrorView,
+    ProjectExportView,
+    ProjectJoinView,
+    ProjectLeaveView,
+    ProjectQuestionsView,
+    ProjectsView,
+    SiteProjectsView,
+)
 from .project_answers import ProjectAnswersExportView, ProjectAnswersView
 from .project_create import ProjectCreateImportView, ProjectCreateView
-from .project_update import (ProjectUpdateCatalogView, ProjectUpdateImportView,
-                             ProjectUpdateInformationView,
-                             ProjectUpdateParentView, ProjectUpdateTasksView,
-                             ProjectUpdateView, ProjectUpdateViewsView)
+from .project_update import (
+    ProjectUpdateCatalogView,
+    ProjectUpdateImportView,
+    ProjectUpdateInformationView,
+    ProjectUpdateParentView,
+    ProjectUpdateTasksView,
+    ProjectUpdateView,
+    ProjectUpdateViewsView,
+)
 from .project_view import ProjectViewExportView, ProjectViewView
-from .snapshot import (SnapshotCreateView, SnapshotRollbackView,
-                       SnapshotUpdateView)
+from .snapshot import SnapshotCreateView, SnapshotRollbackView, SnapshotUpdateView
diff --git a/rdmo/projects/views/integration.py b/rdmo/projects/views/integration.py
index 631d2264c9..a0191c0b85 100644
--- a/rdmo/projects/views/integration.py
+++ b/rdmo/projects/views/integration.py
@@ -88,5 +88,5 @@ def post(self, request, *args, **kwargs):
         try:
             integration = Integration.objects.filter(project_id=project_id).get(pk=pk)
             return integration.provider.webhook(request, integration)
-        except Integration.DoesNotExist:
-            raise Http404
+        except Integration.DoesNotExist as e:
+            raise Http404 from e
diff --git a/rdmo/projects/views/issue.py b/rdmo/projects/views/issue.py
index 3a97c39de7..4c01fda62c 100644
--- a/rdmo/projects/views/issue.py
+++ b/rdmo/projects/views/issue.py
@@ -7,6 +7,7 @@
 from django.template import TemplateSyntaxError
 from django.template.loader import render_to_string
 from django.views.generic import DetailView, UpdateView
+
 from rest_framework.reverse import reverse
 
 from rdmo.core.mail import send_mail
@@ -130,12 +131,12 @@ def post(self, request, *args, **kwargs):
 
             attachments = []
             if form.cleaned_data.get('attachments_answers'):
-                title = '{}.{}'.format(project.title, attachments_format)
+                title = f'{project.title}.{attachments_format}'
                 response = self.render_project_answers(project, snapshot, attachments_format)
                 attachments.append((title, response.content, response['content-type']))
 
             for view in form.cleaned_data.get('attachments_views'):
-                title = '{}.{}'.format(project.title, attachments_format)
+                title = f'{project.title}.{attachments_format}'
                 response = self.render_project_views(project, snapshot, view, attachments_format)
                 attachments.append((title, response.content, response['content-type']))
 
@@ -152,7 +153,8 @@ def post(self, request, *args, **kwargs):
                     pass
             else:
                 if mail_form.is_valid():
-                    to_emails = mail_form.cleaned_data.get('recipients', []) + mail_form.cleaned_data.get('recipients_input', [])
+                    to_emails = [*mail_form.cleaned_data.get('recipients', []),
+                                 *mail_form.cleaned_data.get('recipients_input', [])]
                     cc_emails = [request.user.email]
                     reply_to = [request.user.email]
 
@@ -168,12 +170,14 @@ def post(self, request, *args, **kwargs):
         return self.render_to_response(self.get_context_data(form=form, mail_form=mail_form))
 
     def render_project_answers(self, project, snapshot, attachments_format):
-        return render_to_format(self.request, attachments_format, project.title, 'projects/project_answers_export.html', {
-            'project': ProjectWrapper(project, snapshot),
-            'format': attachments_format,
-            'title': project.title,
-            'resource_path': get_value_path(project, snapshot)
-        })
+        return render_to_format(
+            self.request, attachments_format, project.title, 'projects/project_answers_export.html', {
+                'project': ProjectWrapper(project, snapshot),
+                'format': attachments_format,
+                'title': project.title,
+                'resource_path': get_value_path(project, snapshot)
+            }
+        )
 
     def render_project_views(self, project, snapshot, view, attachments_format):
         try:
@@ -181,10 +185,12 @@ def render_project_views(self, project, snapshot, view, attachments_format):
         except TemplateSyntaxError:
             return HttpResponse()
 
-        return render_to_format(self.request, attachments_format, project.title, 'projects/project_view_export.html', {
-            'format': attachments_format,
-            'title': project.title,
-            'view': view,
-            'rendered_view': rendered_view,
-            'resource_path': get_value_path(project, snapshot)
-        })
+        return render_to_format(
+            self.request, attachments_format, project.title, 'projects/project_view_export.html', {
+                'format': attachments_format,
+                'title': project.title,
+                'view': view,
+                'rendered_view': rendered_view,
+                'resource_path': get_value_path(project, snapshot)
+            }
+        )
diff --git a/rdmo/projects/views/membership.py b/rdmo/projects/views/membership.py
index 19c76fff4a..8392ff2d18 100644
--- a/rdmo/projects/views/membership.py
+++ b/rdmo/projects/views/membership.py
@@ -1,8 +1,7 @@
 import logging
 
 from django.conf import settings
-from django.http import (HttpResponseBadRequest, HttpResponseForbidden,
-                         HttpResponseRedirect)
+from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect
 from django.shortcuts import get_object_or_404
 from django.urls import reverse
 from django.views.generic import DeleteView, UpdateView
@@ -73,7 +72,8 @@ def delete(self, *args, **kwargs):
         if (self.request.user in self.obj.project.owners) or is_site_manager(self.request.user):
             # user is owner or site manager
             if is_last_owner(self.obj.project, self.obj.user):
-                logger.info('User "%s" not allowed to remove last user "%s"', self.request.user.username, self.obj.user.username)
+                logger.info('User "%s" not allowed to remove last user "%s"',
+                            self.request.user.username, self.obj.user.username)
                 return HttpResponseBadRequest()
             else:
                 logger.info('User "%s" deletes user "%s"', self.request.user.username, self.obj.user.username)
diff --git a/rdmo/projects/views/project.py b/rdmo/projects/views/project.py
index 16050259d0..6bbe56fbd5 100644
--- a/rdmo/projects/views/project.py
+++ b/rdmo/projects/views/project.py
@@ -10,11 +10,11 @@
 from django.shortcuts import get_object_or_404, redirect
 from django.urls import reverse_lazy
 from django.utils.decorators import method_decorator
-from django.utils.http import urlencode
 from django.utils.translation import gettext_lazy as _
 from django.views.decorators.csrf import ensure_csrf_cookie
 from django.views.generic import DeleteView, DetailView, TemplateView
 from django.views.generic.edit import FormMixin
+
 from django_filters.views import FilterView
 
 from rdmo.accounts.utils import is_site_manager
@@ -66,7 +66,7 @@ def get_queryset(self):
         return queryset
 
     def get_context_data(self, **kwargs):
-        context = super(ProjectsView, self).get_context_data(**kwargs)
+        context = super().get_context_data(**kwargs)
         context['number_of_projects'] = self.get_queryset().count()
         context['invites'] = Invite.objects.filter(user=self.request.user)
         context['is_site_manager'] = is_site_manager(self.request.user)
@@ -101,13 +101,13 @@ def get_queryset(self):
             raise PermissionDenied()
 
     def get_context_data(self, **kwargs):
-        context = super(SiteProjectsView, self).get_context_data(**kwargs)
+        context = super().get_context_data(**kwargs)
         context['number_of_projects'] = self.get_queryset().count()
         context['number_of_filtered_projects'] = context["filter"].qs.count()
         context = set_context_querystring_with_filter_and_page(context)
         context['catalogs'] = Catalog.objects.filter_current_site() \
-                                .filter_group(self.request.user) \
-                                .filter_availability(self.request.user)
+                                             .filter_group(self.request.user) \
+                                             .filter_availability(self.request.user)
         return context
 
 
@@ -126,11 +126,12 @@ class ProjectDetailView(ObjectPermissionMixin, DetailView):
     permission_required = 'projects.view_project_object'
 
     def get_context_data(self, **kwargs):
-        context = super(ProjectDetailView, self).get_context_data(**kwargs)
+        context = super().get_context_data(**kwargs)
         project = context['project']
         ancestors = project.get_ancestors(include_self=True)
         values = project.values.filter(snapshot=None).select_related('attribute', 'option')
-        highest = Membership.objects.filter(project__in=ancestors, user_id=OuterRef('user_id')).order_by('-project__level')
+        highest = Membership.objects.filter(project__in=ancestors, user_id=OuterRef('user_id')) \
+                                    .order_by('-project__level')
         memberships = Membership.objects.filter(project__in=ancestors) \
                                         .annotate(highest=Subquery(highest.values('project__level')[:1])) \
                                         .filter(highest=F('project__level')) \
diff --git a/rdmo/projects/views/project_answers.py b/rdmo/projects/views/project_answers.py
index dd216fadd3..754a2bd4ed 100644
--- a/rdmo/projects/views/project_answers.py
+++ b/rdmo/projects/views/project_answers.py
@@ -3,6 +3,7 @@
 from django.conf import settings
 from django.shortcuts import redirect
 from django.views.generic import DetailView
+
 from rdmo.core.constants import VALUE_TYPE_FILE
 from rdmo.core.utils import render_to_format
 from rdmo.core.views import ObjectPermissionMixin
@@ -10,6 +11,7 @@
 
 from ..models import Project, Snapshot
 from ..utils import get_value_path
+
 logger = logging.getLogger(__name__)
 
 
@@ -81,4 +83,5 @@ def get_context_data(self, **kwargs):
         return context
 
     def render_to_response(self, context, **response_kwargs):
-        return render_to_format(self.request, context['format'], context['title'], 'projects/project_answers_export.html', context)
+        return render_to_format(self.request, context['format'], context['title'],
+                                'projects/project_answers_export.html', context)
diff --git a/rdmo/projects/views/project_create.py b/rdmo/projects/views/project_create.py
index 9e2f6e8137..eb1cac6f50 100644
--- a/rdmo/projects/views/project_create.py
+++ b/rdmo/projects/views/project_create.py
@@ -40,7 +40,7 @@ def form_valid(self, form):
         form.instance.site = get_current_site(self.request)
 
         # save the project
-        response = super(ProjectCreateView, self).form_valid(form)
+        response = super().form_valid(form)
 
         # add all tasks to project
         tasks = Task.objects.filter_current_site() \
diff --git a/rdmo/projects/views/project_update.py b/rdmo/projects/views/project_update.py
index 98c7c4bab2..b6e79be2cd 100644
--- a/rdmo/projects/views/project_update.py
+++ b/rdmo/projects/views/project_update.py
@@ -1,17 +1,20 @@
 import logging
 
-from django.http import HttpResponseRedirect
-from django.shortcuts import render
-from django.utils.translation import gettext_lazy as _
 from django.views.generic import UpdateView
+
 from rdmo.core.views import ObjectPermissionMixin, RedirectViewMixin
 from rdmo.questions.models import Catalog
 from rdmo.tasks.models import Task
 from rdmo.views.models import View
 
-from ..forms import (ProjectForm, ProjectUpdateCatalogForm,
-                     ProjectUpdateInformationForm, ProjectUpdateParentForm,
-                     ProjectUpdateTasksForm, ProjectUpdateViewsForm)
+from ..forms import (
+    ProjectForm,
+    ProjectUpdateCatalogForm,
+    ProjectUpdateInformationForm,
+    ProjectUpdateParentForm,
+    ProjectUpdateTasksForm,
+    ProjectUpdateViewsForm,
+)
 from ..mixins import ProjectImportMixin
 from ..models import Project
 
diff --git a/rdmo/projects/views/project_view.py b/rdmo/projects/views/project_view.py
index 71cc6896f9..d7d1d95e2c 100644
--- a/rdmo/projects/views/project_view.py
+++ b/rdmo/projects/views/project_view.py
@@ -4,6 +4,7 @@
 from django.http import Http404
 from django.template import TemplateSyntaxError
 from django.views.generic import DetailView
+
 from rdmo.core.constants import VALUE_TYPE_FILE
 from rdmo.core.utils import render_to_format
 from rdmo.core.views import ObjectPermissionMixin
@@ -22,7 +23,7 @@ class ProjectViewView(ObjectPermissionMixin, DetailView):
     template_name = 'projects/project_view.html'
 
     def get_context_data(self, **kwargs):
-        context = super(ProjectViewView, self).get_context_data(**kwargs)
+        context = super().get_context_data(**kwargs)
 
         # prefetch most elements of the catalog
         context['project'].catalog.prefetch_elements()
@@ -34,8 +35,8 @@ def get_context_data(self, **kwargs):
 
         try:
             context['view'] = context['project'].views.get(pk=self.kwargs.get('view_id'))
-        except View.DoesNotExist:
-            raise Http404
+        except View.DoesNotExist as e:
+            raise Http404 from e
 
         try:
             context['rendered_view'] = context['view'].render(context['project'],
@@ -64,7 +65,7 @@ class ProjectViewExportView(ObjectPermissionMixin, DetailView):
     def get_context_data(self, **kwargs):
         export_format = self.kwargs.get('format')
 
-        context = super(ProjectViewExportView, self).get_context_data(**kwargs)
+        context = super().get_context_data(**kwargs)
 
         # prefetch most elements of the catalog
         context['project'].catalog.prefetch_elements()
@@ -76,8 +77,8 @@ def get_context_data(self, **kwargs):
 
         try:
             context['view'] = context['project'].views.get(pk=self.kwargs.get('view_id'))
-        except View.DoesNotExist:
-            raise Http404
+        except View.DoesNotExist as e:
+            raise Http404 from e
 
         try:
             context['rendered_view'] = context['view'].render(context['project'],
@@ -95,4 +96,5 @@ def get_context_data(self, **kwargs):
         return context
 
     def render_to_response(self, context, **response_kwargs):
-        return render_to_format(self.request, context['format'], context['title'], 'projects/project_view_export.html', context)
+        return render_to_format(self.request, context['format'], context['title'],
+                                'projects/project_view_export.html', context)
diff --git a/rdmo/projects/views/snapshot.py b/rdmo/projects/views/snapshot.py
index d693025754..ec378c5c99 100644
--- a/rdmo/projects/views/snapshot.py
+++ b/rdmo/projects/views/snapshot.py
@@ -20,13 +20,13 @@ class SnapshotCreateView(ObjectPermissionMixin, RedirectViewMixin, CreateView):
 
     def dispatch(self, *args, **kwargs):
         self.project = get_object_or_404(Project.objects.all(), pk=self.kwargs['project_id'])
-        return super(SnapshotCreateView, self).dispatch(*args, **kwargs)
+        return super().dispatch(*args, **kwargs)
 
     def get_permission_object(self):
         return self.project
 
     def get_form_kwargs(self):
-        kwargs = super(SnapshotCreateView, self).get_form_kwargs()
+        kwargs = super().get_form_kwargs()
         kwargs['project'] = self.project
         return kwargs
 
diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py
index fcf562af9e..25352b1b29 100644
--- a/rdmo/projects/viewsets.py
+++ b/rdmo/projects/viewsets.py
@@ -3,17 +3,17 @@
 from django.db.models import prefetch_related_objects
 from django.http import Http404, HttpResponseRedirect
 from django.utils.translation import gettext_lazy as _
-from django_filters.rest_framework import DjangoFilterBackend
+
 from rest_framework import serializers
 from rest_framework.decorators import action
 from rest_framework.exceptions import NotFound
-from rest_framework.mixins import (CreateModelMixin, ListModelMixin,
-                                   RetrieveModelMixin, UpdateModelMixin)
+from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
-from rest_framework.viewsets import (GenericViewSet, ModelViewSet,
-                                     ReadOnlyModelViewSet)
+from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
+
+from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework_extensions.mixins import NestedViewSetMixin
 
 from rdmo.conditions.models import Condition
@@ -25,21 +25,25 @@
 from rdmo.views.models import View
 
 from .filters import SnapshotFilterBackend, ValueFilterBackend
-from .models import (Continuation, Integration, Invite, Issue, Membership,
-                     Project, Snapshot, Value)
-from .permissions import (HasProjectPagePermission, HasProjectPermission,
-                          HasProjectsPermission)
-from .serializers.v1 import (IntegrationSerializer, InviteSerializer,
-                             IssueSerializer, MembershipSerializer,
-                             ProjectIntegrationSerializer,
-                             ProjectInviteSerializer,
-                             ProjectInviteUpdateSerializer,
-                             ProjectIssueSerializer,
-                             ProjectMembershipSerializer,
-                             ProjectMembershipUpdateSerializer,
-                             ProjectSerializer, ProjectSnapshotSerializer,
-                             ProjectValueSerializer, SnapshotSerializer,
-                             ValueSerializer)
+from .models import Continuation, Integration, Invite, Issue, Membership, Project, Snapshot, Value
+from .permissions import HasProjectPagePermission, HasProjectPermission, HasProjectsPermission
+from .serializers.v1 import (
+    IntegrationSerializer,
+    InviteSerializer,
+    IssueSerializer,
+    MembershipSerializer,
+    ProjectIntegrationSerializer,
+    ProjectInviteSerializer,
+    ProjectInviteUpdateSerializer,
+    ProjectIssueSerializer,
+    ProjectMembershipSerializer,
+    ProjectMembershipUpdateSerializer,
+    ProjectSerializer,
+    ProjectSnapshotSerializer,
+    ProjectValueSerializer,
+    SnapshotSerializer,
+    ValueSerializer,
+)
 from .serializers.v1.overview import ProjectOverviewSerializer
 from .serializers.v1.page import PageSerializer
 from .utils import check_conditions, send_invite_email
@@ -139,8 +143,8 @@ def options(self, request, pk=None):
             try:
                 optionset_id = request.GET.get('optionset')
                 optionset = OptionSet.objects.get(pk=optionset_id)
-            except (ValueError, OptionSet.DoesNotExist):
-                raise NotFound()
+            except (ValueError, OptionSet.DoesNotExist) as e:
+                raise NotFound() from e
 
             # check if the optionset belongs to this catalog and if it has a provider
             if Question.objects.filter_by_catalog(project.catalog).filter(optionsets=optionset) and \
@@ -194,8 +198,8 @@ def initial(self, request, *args, **kwargs):
     def get_project_from_parent_viewset(self):
         try:
             return Project.objects.filter_user(self.request.user).get(pk=self.get_parents_query_dict().get('project'))
-        except Project.DoesNotExist:
-            raise Http404
+        except Project.DoesNotExist as e:
+            raise Http404 from e
 
     def perform_create(self, serializer):
         # this call provides the nested serializers with the project
@@ -344,7 +348,8 @@ def set(self, request, parent_lookup_project, pk=None):
             if element.attribute == value.attribute:
                 attributes.update([descendant.attribute for descendant in element.descendants])
 
-        values = self.get_queryset().filter(attribute__in=attributes, set_prefix=value.set_prefix, set_index=value.set_index)
+        values = self.get_queryset().filter(attribute__in=attributes, set_prefix=value.set_prefix,
+                                            set_index=value.set_index)
         values.delete()
 
         return Response(status=204)
@@ -419,7 +424,8 @@ def retrieve(self, request, *args, **kwargs):
             if request.GET.get('back') == 'true':
                 prev_page = self.project.catalog.get_prev_page(page)
                 if prev_page is not None:
-                    url = reverse('v1-projects:project-page-detail', args=[self.project.id, prev_page.id]) + '?back=true'
+                    url = reverse('v1-projects:project-page-detail',
+                                  args=[self.project.id, prev_page.id]) + '?back=true'
                     return HttpResponseRedirect(url, status=303)
             else:
                 next_page = self.project.catalog.get_next_page(page)
diff --git a/rdmo/questions/admin.py b/rdmo/questions/admin.py
index b4119821aa..9b52d07f74 100644
--- a/rdmo/questions/admin.py
+++ b/rdmo/questions/admin.py
@@ -5,18 +5,33 @@
 from rdmo.core.admin import ElementAdminForm
 from rdmo.core.utils import get_language_fields
 
-from .models import (Catalog, CatalogSection, Page, PageQuestion,
-                     PageQuestionSet, Question, QuestionSet,
-                     QuestionSetQuestion, QuestionSetQuestionSet, Section,
-                     SectionPage)
+from .models import (
+    Catalog,
+    CatalogSection,
+    Page,
+    PageQuestion,
+    PageQuestionSet,
+    Question,
+    QuestionSet,
+    QuestionSetQuestion,
+    QuestionSetQuestionSet,
+    Section,
+    SectionPage,
+)
 from .utils import get_widget_type_choices
-from .validators import (CatalogLockedValidator, CatalogUniqueURIValidator,
-                         PageLockedValidator, PageUniqueURIValidator,
-                         QuestionLockedValidator, QuestionSetLockedValidator,
-                         QuestionSetQuestionSetValidator,
-                         QuestionSetUniqueURIValidator,
-                         QuestionUniqueURIValidator, SectionLockedValidator,
-                         SectionUniqueURIValidator)
+from .validators import (
+    CatalogLockedValidator,
+    CatalogUniqueURIValidator,
+    PageLockedValidator,
+    PageUniqueURIValidator,
+    QuestionLockedValidator,
+    QuestionSetLockedValidator,
+    QuestionSetQuestionSetValidator,
+    QuestionSetUniqueURIValidator,
+    QuestionUniqueURIValidator,
+    SectionLockedValidator,
+    SectionUniqueURIValidator,
+)
 
 
 class CatalogAdminForm(ElementAdminForm):
@@ -85,7 +100,7 @@ class CatalogAdmin(admin.ModelAdmin):
     form = CatalogAdminForm
     inlines = (CatalogSectionInline, )
 
-    search_fields = ['uri'] + get_language_fields('title')
+    search_fields = ['uri', *get_language_fields('title')]
     list_display = ('uri', 'title', 'projects_count', 'available')
     readonly_fields = ('uri', )
     list_filter = ('available', )
@@ -108,7 +123,7 @@ class SectionAdmin(admin.ModelAdmin):
     form = SectionAdminForm
     inlines = (SectionPageInline, )
 
-    search_fields = ['uri'] + get_language_fields('title')
+    search_fields = ['uri', *get_language_fields('title')]
     list_display = ('uri', 'title')
     readonly_fields = ('uri', )
     list_filter = ('catalogs', )
@@ -129,7 +144,7 @@ class PageAdmin(admin.ModelAdmin):
     form = PageAdminForm
     inlines = (PageQuestionSetInline, PageQuestionInline)
 
-    search_fields = ['uri'] + get_language_fields('title') + get_language_fields('help')
+    search_fields = ['uri', *get_language_fields('title'), *get_language_fields('help')]
     list_display = ('uri', 'attribute', 'is_collection')
     readonly_fields = ('uri', )
     list_filter = ('sections__catalogs', 'sections', 'is_collection')
@@ -151,7 +166,7 @@ class QuestionSetAdmin(admin.ModelAdmin):
     form = QuestionSetAdminForm
     inlines = (QuestionSetQuestionSetInline, QuestionSetQuestionInline)
 
-    search_fields = ['uri'] + get_language_fields('title') + get_language_fields('help')
+    search_fields = ['uri', *get_language_fields('title'), *get_language_fields('help')]
     list_display = ('uri', 'attribute', 'is_collection')
     readonly_fields = ('uri', )
     list_filter = ('pages__sections__catalogs', 'pages__sections', 'pages', 'is_collection')
@@ -161,10 +176,11 @@ class QuestionSetAdmin(admin.ModelAdmin):
 class QuestionAdmin(admin.ModelAdmin):
     form = QuestionAdminForm
 
-    search_fields = ['uri'] + get_language_fields('help') + get_language_fields('text')
+    search_fields = ['uri', *get_language_fields('help'), *get_language_fields('text')]
     list_display = ('uri', 'attribute', 'text', 'is_collection')
     readonly_fields = ('uri', )
-    list_filter = ('pages__sections__catalogs', 'pages__sections', 'pages', 'is_collection', 'widget_type', 'value_type')
+    list_filter = ('pages__sections__catalogs', 'pages__sections', 'pages', 'is_collection',
+                   'widget_type', 'value_type')
     filter_horizontal = ('editors', 'optionsets', 'conditions')
 
 
diff --git a/rdmo/questions/imports.py b/rdmo/questions/imports.py
index 4616312036..ab79204b26 100644
--- a/rdmo/questions/imports.py
+++ b/rdmo/questions/imports.py
@@ -2,20 +2,31 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (check_permissions, set_common_fields,
-                               set_foreign_field, set_lang_field,
-                               set_m2m_instances, set_m2m_through_instances,
-                               set_reverse_m2m_through_instance,
-                               validate_instance)
+from rdmo.core.imports import (
+    check_permissions,
+    set_common_fields,
+    set_foreign_field,
+    set_lang_field,
+    set_m2m_instances,
+    set_m2m_through_instances,
+    set_reverse_m2m_through_instance,
+    validate_instance,
+)
 
 from .models import Catalog, Page, Question, QuestionSet, Section
 from .utils import get_widget_types
-from .validators import (CatalogLockedValidator, CatalogUniqueURIValidator,
-                         PageLockedValidator, PageUniqueURIValidator,
-                         QuestionLockedValidator, QuestionSetLockedValidator,
-                         QuestionSetUniqueURIValidator,
-                         QuestionUniqueURIValidator, SectionLockedValidator,
-                         SectionUniqueURIValidator)
+from .validators import (
+    CatalogLockedValidator,
+    CatalogUniqueURIValidator,
+    PageLockedValidator,
+    PageUniqueURIValidator,
+    QuestionLockedValidator,
+    QuestionSetLockedValidator,
+    QuestionSetUniqueURIValidator,
+    QuestionUniqueURIValidator,
+    SectionLockedValidator,
+    SectionUniqueURIValidator,
+)
 
 logger = logging.getLogger(__name__)
 
@@ -154,8 +165,8 @@ def import_questionset(element, save=False, user=None):
         questionset.save()
         set_m2m_instances(questionset, 'conditions', element)
         set_reverse_m2m_through_instance(questionset, 'page', element, 'questionset', 'page', 'questionset_pages')
-        set_reverse_m2m_through_instance(questionset, 'questionset', element, 'questionset', 'parent', 'questionset_parents')
-        set_m2m_through_instances(questionset, 'questionsets', element, 'parent', 'questionset', 'questionset_questionsets')
+        set_reverse_m2m_through_instance(questionset, 'questionset', element, 'questionset', 'parent', 'questionset_parents')  # noqa: E501
+        set_m2m_through_instances(questionset, 'questionsets', element, 'parent', 'questionset', 'questionset_questionsets')  # noqa: E501
         set_m2m_through_instances(questionset, 'questions', element, 'questionset', 'question', 'questionset_questions')
         questionset.editors.add(Site.objects.get_current())
 
@@ -210,7 +221,7 @@ def import_question(element, save=False, user=None):
 
         question.save()
         set_reverse_m2m_through_instance(question, 'page', element, 'question', 'page', 'question_pages')
-        set_reverse_m2m_through_instance(question, 'questionset', element, 'question', 'questionset', 'question_questionsets')
+        set_reverse_m2m_through_instance(question, 'questionset', element, 'question', 'questionset', 'question_questionsets')  # noqa: E501
         set_m2m_instances(question, 'conditions', element)
         set_m2m_instances(question, 'optionsets', element)
         question.editors.add(Site.objects.get_current())
diff --git a/rdmo/questions/managers.py b/rdmo/questions/managers.py
index 90e06debc6..31a57bceb9 100644
--- a/rdmo/questions/managers.py
+++ b/rdmo/questions/managers.py
@@ -1,10 +1,13 @@
 from django.db import models
 
-from rdmo.core.managers import (AvailabilityManagerMixin,
-                                AvailabilityQuerySetMixin,
-                                CurrentSiteManagerMixin,
-                                CurrentSiteQuerySetMixin, GroupsManagerMixin,
-                                GroupsQuerySetMixin)
+from rdmo.core.managers import (
+    AvailabilityManagerMixin,
+    AvailabilityQuerySetMixin,
+    CurrentSiteManagerMixin,
+    CurrentSiteQuerySetMixin,
+    GroupsManagerMixin,
+    GroupsQuerySetMixin,
+)
 
 
 class CatalogQuestionSet(CurrentSiteQuerySetMixin, GroupsQuerySetMixin, AvailabilityQuerySetMixin, models.QuerySet):
diff --git a/rdmo/questions/models/catalog.py b/rdmo/questions/models/catalog.py
index 03e18f8643..592980bbe8 100644
--- a/rdmo/questions/models/catalog.py
+++ b/rdmo/questions/models/catalog.py
@@ -6,7 +6,7 @@
 from django.utils.translation import gettext_lazy as _
 
 from rdmo.core.models import Model, TranslationMixin
-from rdmo.core.utils import copy_model, get_language_fields, join_url
+from rdmo.core.utils import join_url
 
 from ..managers import CatalogManager
 
@@ -170,7 +170,7 @@ def elements(self):
     def descendants(self):
         descendants = []
         for element in self.elements:
-            descendants += [element] + element.descendants
+            descendants += [element, *element.descendants]
         return descendants
 
     @cached_property
diff --git a/rdmo/questions/models/page.py b/rdmo/questions/models/page.py
index 22f3d0cfd1..7b273ce4db 100644
--- a/rdmo/questions/models/page.py
+++ b/rdmo/questions/models/page.py
@@ -6,7 +6,7 @@
 
 from rdmo.conditions.models import Condition
 from rdmo.core.models import Model, TranslationMixin
-from rdmo.core.utils import copy_model, join_url
+from rdmo.core.utils import join_url
 from rdmo.domain.models import Attribute
 
 from ..managers import PageManager
@@ -232,7 +232,7 @@ def elements(self):
     def descendants(self):
         descendants = []
         for element in self.elements:
-            descendants += [element] + element.descendants
+            descendants += [element, *element.descendants]
         return descendants
 
     def prefetch_elements(self):
diff --git a/rdmo/questions/models/question.py b/rdmo/questions/models/question.py
index 2b96a52354..d2a2e522c6 100644
--- a/rdmo/questions/models/question.py
+++ b/rdmo/questions/models/question.py
@@ -7,7 +7,7 @@
 from rdmo.conditions.models import Condition
 from rdmo.core.constants import VALUE_TYPE_CHOICES
 from rdmo.core.models import Model, TranslationMixin
-from rdmo.core.utils import copy_model, join_url
+from rdmo.core.utils import join_url
 from rdmo.domain.models import Attribute
 from rdmo.options.models import Option
 
@@ -284,8 +284,8 @@ def verbose_name_plural(self):
     @cached_property
     def is_locked(self):
         return self.locked or \
-            any([page.is_locked for page in self.pages.all()]) or \
-            any([questionset.is_locked for questionset in self.questionsets.all()])
+            any(page.is_locked for page in self.pages.all()) or \
+            any(questionset.is_locked for questionset in self.questionsets.all())
 
     @cached_property
     def has_conditions(self):
diff --git a/rdmo/questions/models/questionset.py b/rdmo/questions/models/questionset.py
index 7b60247c55..8fd6821ef7 100644
--- a/rdmo/questions/models/questionset.py
+++ b/rdmo/questions/models/questionset.py
@@ -6,7 +6,7 @@
 
 from rdmo.conditions.models import Condition
 from rdmo.core.models import Model, TranslationMixin
-from rdmo.core.utils import copy_model, join_url
+from rdmo.core.utils import join_url
 from rdmo.domain.models import Attribute
 
 from ..managers import QuestionSetManager
@@ -213,8 +213,8 @@ def verbose_name_plural(self):
     @cached_property
     def is_locked(self):
         return self.locked or \
-            any([page.is_locked for page in self.pages.all()]) or \
-            any([questionset.is_locked for questionset in self.questionsets.all()])
+            any(page.is_locked for page in self.pages.all()) or \
+            any(questionset.is_locked for questionset in self.questionsets.all())
 
     @cached_property
     def has_conditions(self):
@@ -223,7 +223,8 @@ def has_conditions(self):
     @cached_property
     def elements(self):
         questionset_elements = list(self.questionset_questionsets.all()) + list(self.questionset_questions.all())
-        return [questionset_element.element for questionset_element in sorted(questionset_elements, key=lambda e: e.order)]
+        return [questionset_element.element
+                for questionset_element in sorted(questionset_elements, key=lambda e: e.order)]
 
     @cached_property
     def descendants(self):
@@ -231,7 +232,7 @@ def descendants(self):
         for element in self.elements:
             if element == self:
                 raise RuntimeError(f'QuestionSet {self} is descendant of itself.')
-            descendants += [element] + element.descendants
+            descendants += [element, *element.descendants]
         return descendants
 
     def prefetch_elements(self):
diff --git a/rdmo/questions/models/section.py b/rdmo/questions/models/section.py
index 6a634e54e5..5feb4d9875 100644
--- a/rdmo/questions/models/section.py
+++ b/rdmo/questions/models/section.py
@@ -5,7 +5,7 @@
 from django.utils.translation import gettext_lazy as _
 
 from rdmo.core.models import Model, TranslationMixin
-from rdmo.core.utils import copy_model, join_url
+from rdmo.core.utils import join_url
 
 from ..managers import SectionManager
 
@@ -108,7 +108,7 @@ def title(self):
 
     @cached_property
     def is_locked(self):
-        return self.locked or any([catalog.is_locked for catalog in self.catalogs.all()])
+        return self.locked or any(catalog.is_locked for catalog in self.catalogs.all())
 
     @cached_property
     def elements(self):
@@ -119,7 +119,7 @@ def elements(self):
     def descendants(self):
         descendants = []
         for element in self.elements:
-            descendants += [element] + element.descendants
+            descendants += [element, *element.descendants]
         return descendants
 
     def prefetch_elements(self):
diff --git a/rdmo/questions/renderers/__init__.py b/rdmo/questions/renderers/__init__.py
index c500ce0f70..112ee856be 100644
--- a/rdmo/questions/renderers/__init__.py
+++ b/rdmo/questions/renderers/__init__.py
@@ -1,12 +1,15 @@
 from rdmo.conditions.renderers.mixins import ConditionRendererMixin
 from rdmo.core.renderers import BaseXMLRenderer
 from rdmo.domain.renderers.mixins import AttributeRendererMixin
-from rdmo.options.renderers.mixins import (OptionRendererMixin,
-                                           OptionSetRendererMixin)
-
-from .mixins import (CatalogRendererMixin, PageRendererMixin,
-                     QuestionRendererMixin, QuestionSetRendererMixin,
-                     SectionRendererMixin)
+from rdmo.options.renderers.mixins import OptionRendererMixin, OptionSetRendererMixin
+
+from .mixins import (
+    CatalogRendererMixin,
+    PageRendererMixin,
+    QuestionRendererMixin,
+    QuestionSetRendererMixin,
+    SectionRendererMixin,
+)
 
 
 class CatalogRenderer(CatalogRendererMixin, SectionRendererMixin, PageRendererMixin,
diff --git a/rdmo/questions/renderers/mixins.py b/rdmo/questions/renderers/mixins.py
index bff939d063..f22ef39a81 100644
--- a/rdmo/questions/renderers/mixins.py
+++ b/rdmo/questions/renderers/mixins.py
@@ -80,10 +80,14 @@ def render_page(self, xml, page):
             self.render_text_element(xml, 'is_collection', {}, page['is_collection'])
 
             for lang_code, lang_string, lang_field in get_languages():
-                self.render_text_element(xml, 'title', {'lang': lang_code}, page['title_%s' % lang_code])
-                self.render_text_element(xml, 'help', {'lang': lang_code}, page['help_%s' % lang_code])
-                self.render_text_element(xml, 'verbose_name', {'lang': lang_code}, page['verbose_name_%s' % lang_code])
-                self.render_text_element(xml, 'verbose_name_plural', {'lang': lang_code}, page['verbose_name_plural_%s' % lang_code])
+                self.render_text_element(xml, 'title', {'lang': lang_code},
+                                         page['title_%s' % lang_code])
+                self.render_text_element(xml, 'help', {'lang': lang_code},
+                                         page['help_%s' % lang_code])
+                self.render_text_element(xml, 'verbose_name', {'lang': lang_code},
+                                         page['verbose_name_%s' % lang_code])
+                self.render_text_element(xml, 'verbose_name_plural', {'lang': lang_code},
+                                         page['verbose_name_plural_%s' % lang_code])
 
             xml.startElement('questionsets', {})
             for page_questionset in page['page_questionsets']:
@@ -144,10 +148,14 @@ def render_questionset(self, xml, questionset):
             self.render_text_element(xml, 'is_collection', {}, questionset['is_collection'])
 
             for lang_code, lang_string, lang_field in get_languages():
-                self.render_text_element(xml, 'title', {'lang': lang_code}, questionset['title_%s' % lang_code])
-                self.render_text_element(xml, 'help', {'lang': lang_code}, questionset['help_%s' % lang_code])
-                self.render_text_element(xml, 'verbose_name', {'lang': lang_code}, questionset['verbose_name_%s' % lang_code])
-                self.render_text_element(xml, 'verbose_name_plural', {'lang': lang_code}, questionset['verbose_name_plural_%s' % lang_code])
+                self.render_text_element(xml, 'title', {'lang': lang_code},
+                                         questionset['title_%s' % lang_code])
+                self.render_text_element(xml, 'help', {'lang': lang_code},
+                                         questionset['help_%s' % lang_code])
+                self.render_text_element(xml, 'verbose_name', {'lang': lang_code},
+                                         questionset['verbose_name_%s' % lang_code])
+                self.render_text_element(xml, 'verbose_name_plural', {'lang': lang_code},
+                                         questionset['verbose_name_plural_%s' % lang_code])
 
             xml.startElement('questionsets', {})
             for questionset_questionset in questionset['questionset_questionsets']:
@@ -209,11 +217,16 @@ def render_question(self, xml, question):
             self.render_text_element(xml, 'is_optional', {}, question['is_optional'])
 
             for lang_code, lang_string, lang_field in get_languages():
-                self.render_text_element(xml, 'help', {'lang': lang_code}, question['help_%s' % lang_code])
-                self.render_text_element(xml, 'text', {'lang': lang_code}, question['text_%s' % lang_code])
-                self.render_text_element(xml, 'default_text', {'lang': lang_code}, question['default_text_%s' % lang_code])
-                self.render_text_element(xml, 'verbose_name', {'lang': lang_code}, question['verbose_name_%s' % lang_code])
-                self.render_text_element(xml, 'verbose_name_plural', {'lang': lang_code}, question['verbose_name_plural_%s' % lang_code])
+                self.render_text_element(xml, 'help', {'lang': lang_code},
+                                         question['help_%s' % lang_code])
+                self.render_text_element(xml, 'text', {'lang': lang_code},
+                                         question['text_%s' % lang_code])
+                self.render_text_element(xml, 'default_text', {'lang': lang_code},
+                                         question['default_text_%s' % lang_code])
+                self.render_text_element(xml, 'verbose_name', {'lang': lang_code},
+                                         question['verbose_name_%s' % lang_code])
+                self.render_text_element(xml, 'verbose_name_plural', {'lang': lang_code},
+                                         question['verbose_name_plural_%s' % lang_code])
 
             self.render_text_element(xml, 'default_option', {'dc:uri': question['default_option']}, None)
             self.render_text_element(xml, 'default_external_id', {}, question['default_external_id'])
diff --git a/rdmo/questions/serializers/export.py b/rdmo/questions/serializers/export.py
index cb6f9993fe..5f34bdb283 100644
--- a/rdmo/questions/serializers/export.py
+++ b/rdmo/questions/serializers/export.py
@@ -5,10 +5,19 @@
 from rdmo.domain.serializers.export import AttributeExportSerializer
 from rdmo.options.serializers.export import OptionSetExportSerializer
 
-from ..models import (Catalog, CatalogSection, Page, PageQuestion,
-                      PageQuestionSet, Question, QuestionSet,
-                      QuestionSetQuestion, QuestionSetQuestionSet, Section,
-                      SectionPage)
+from ..models import (
+    Catalog,
+    CatalogSection,
+    Page,
+    PageQuestion,
+    PageQuestionSet,
+    Question,
+    QuestionSet,
+    QuestionSetQuestion,
+    QuestionSetQuestionSet,
+    Section,
+    SectionPage,
+)
 
 
 class QuestionExportSerializer(TranslationSerializerMixin, serializers.ModelSerializer):
diff --git a/rdmo/questions/serializers/v1/__init__.py b/rdmo/questions/serializers/v1/__init__.py
index 0fde7e19c0..74c4f8b833 100644
--- a/rdmo/questions/serializers/v1/__init__.py
+++ b/rdmo/questions/serializers/v1/__init__.py
@@ -1,8 +1,5 @@
-from .catalog import (CatalogIndexSerializer, CatalogNestedSerializer,
-                      CatalogSerializer)
+from .catalog import CatalogIndexSerializer, CatalogNestedSerializer, CatalogSerializer
 from .page import PageIndexSerializer, PageNestedSerializer, PageSerializer
 from .question import QuestionIndexSerializer, QuestionSerializer
-from .questionset import (QuestionSetIndexSerializer,
-                          QuestionSetNestedSerializer, QuestionSetSerializer)
-from .section import (SectionIndexSerializer, SectionNestedSerializer,
-                      SectionSerializer)
+from .questionset import QuestionSetIndexSerializer, QuestionSetNestedSerializer, QuestionSetSerializer
+from .section import SectionIndexSerializer, SectionNestedSerializer, SectionSerializer
diff --git a/rdmo/questions/serializers/v1/catalog.py b/rdmo/questions/serializers/v1/catalog.py
index 4152f0df45..8d2290c1eb 100644
--- a/rdmo/questions/serializers/v1/catalog.py
+++ b/rdmo/questions/serializers/v1/catalog.py
@@ -1,10 +1,12 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   ThroughModelSerializerMixin,
-                                   TranslationSerializerMixin)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    ThroughModelSerializerMixin,
+    TranslationSerializerMixin,
+)
 
 from ...models import Catalog, CatalogSection
 from ...validators import CatalogLockedValidator, CatalogUniqueURIValidator
@@ -78,8 +80,9 @@ class CatalogNestedSerializer(CatalogSerializer):
     elements = serializers.SerializerMethodField()
 
     class Meta(CatalogSerializer.Meta):
-        fields = CatalogSerializer.Meta.fields + (
-            'elements',
+        fields = (
+            *CatalogSerializer.Meta.fields,
+            'elements'
         )
 
     def get_elements(self, obj):
diff --git a/rdmo/questions/serializers/v1/page.py b/rdmo/questions/serializers/v1/page.py
index 970ea65251..33dce4eaa0 100644
--- a/rdmo/questions/serializers/v1/page.py
+++ b/rdmo/questions/serializers/v1/page.py
@@ -1,10 +1,12 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   ThroughModelSerializerMixin,
-                                   TranslationSerializerMixin)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    ThroughModelSerializerMixin,
+    TranslationSerializerMixin,
+)
 
 from ...models import Page, PageQuestion, PageQuestionSet, QuestionSet, Section
 from ...validators import PageLockedValidator, PageUniqueURIValidator
@@ -105,8 +107,9 @@ class PageNestedSerializer(PageSerializer):
     elements = serializers.SerializerMethodField()
 
     class Meta(PageSerializer.Meta):
-        fields = PageSerializer.Meta.fields + (
-            'elements',
+        fields = (
+            *PageSerializer.Meta.fields,
+            'elements'
         )
 
     def get_elements(self, obj):
diff --git a/rdmo/questions/serializers/v1/question.py b/rdmo/questions/serializers/v1/question.py
index 5adab1511e..1a0f35ecda 100644
--- a/rdmo/questions/serializers/v1/question.py
+++ b/rdmo/questions/serializers/v1/question.py
@@ -1,13 +1,15 @@
-from rest_framework import serializers
-
 from django.utils.translation import gettext_lazy as _
 
-from rdmo.core.constants import VALUE_TYPE_DATETIME, VALUE_TYPE_BOOLEAN
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   ThroughModelSerializerMixin,
-                                   TranslationSerializerMixin)
+from rest_framework import serializers
+
+from rdmo.core.constants import VALUE_TYPE_BOOLEAN, VALUE_TYPE_DATETIME
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    ThroughModelSerializerMixin,
+    TranslationSerializerMixin,
+)
 
 from ...models import Page, Question, QuestionSet
 from ...validators import QuestionLockedValidator, QuestionUniqueURIValidator
diff --git a/rdmo/questions/serializers/v1/questionset.py b/rdmo/questions/serializers/v1/questionset.py
index 0a7d83c35f..1fbb50f498 100644
--- a/rdmo/questions/serializers/v1/questionset.py
+++ b/rdmo/questions/serializers/v1/questionset.py
@@ -1,16 +1,15 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   ThroughModelSerializerMixin,
-                                   TranslationSerializerMixin)
-
-from ...models import (Page, QuestionSet, QuestionSetQuestion,
-                       QuestionSetQuestionSet)
-from ...validators import (QuestionSetLockedValidator,
-                           QuestionSetQuestionSetValidator,
-                           QuestionSetUniqueURIValidator)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    ThroughModelSerializerMixin,
+    TranslationSerializerMixin,
+)
+
+from ...models import Page, QuestionSet, QuestionSetQuestion, QuestionSetQuestionSet
+from ...validators import QuestionSetLockedValidator, QuestionSetQuestionSetValidator, QuestionSetUniqueURIValidator
 from .question import QuestionSerializer
 
 
@@ -43,8 +42,10 @@ class QuestionSetSerializer(ThroughModelSerializerMixin, TranslationSerializerMi
 
     pages = serializers.PrimaryKeyRelatedField(queryset=Page.objects.all(), required=False, many=True)
     parents = serializers.PrimaryKeyRelatedField(queryset=QuestionSet.objects.all(), required=False, many=True)
-    questionsets = QuestionSetQuestionSetSerializer(source='questionset_questionsets', read_only=False, required=False, many=True)
-    questions = QuestionSetQuestionSerializer(source='questionset_questions', read_only=False, required=False, many=True)
+    questionsets = QuestionSetQuestionSetSerializer(source='questionset_questionsets',
+                                                    read_only=False, required=False, many=True)
+    questions = QuestionSetQuestionSerializer(source='questionset_questions',
+                                              read_only=False, required=False, many=True)
 
     warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
@@ -111,8 +112,9 @@ class QuestionSetNestedSerializer(QuestionSetSerializer):
     elements = serializers.SerializerMethodField()
 
     class Meta(QuestionSetSerializer.Meta):
-        fields = QuestionSetSerializer.Meta.fields + (
-            'elements',
+        fields = (
+            *QuestionSetSerializer.Meta.fields,
+            'elements'
         )
 
     def get_elements(self, obj):
diff --git a/rdmo/questions/serializers/v1/section.py b/rdmo/questions/serializers/v1/section.py
index 34d14fe6ac..fe5187084f 100644
--- a/rdmo/questions/serializers/v1/section.py
+++ b/rdmo/questions/serializers/v1/section.py
@@ -1,10 +1,12 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   ThroughModelSerializerMixin,
-                                   TranslationSerializerMixin)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    ThroughModelSerializerMixin,
+    TranslationSerializerMixin,
+)
 
 from ...models import Catalog, Section, SectionPage
 from ...validators import SectionLockedValidator, SectionUniqueURIValidator
@@ -74,8 +76,9 @@ class SectionNestedSerializer(SectionSerializer):
     elements = serializers.SerializerMethodField()
 
     class Meta(SectionSerializer.Meta):
-        fields = SectionSerializer.Meta.fields + (
-            'elements',
+        fields = (
+            *SectionSerializer.Meta.fields,
+            'elements'
         )
 
     def get_elements(self, obj):
diff --git a/rdmo/questions/urls/v1.py b/rdmo/questions/urls/v1.py
index 7eaad0d6bc..d200a4fcc7 100644
--- a/rdmo/questions/urls/v1.py
+++ b/rdmo/questions/urls/v1.py
@@ -2,8 +2,15 @@
 
 from rest_framework import routers
 
-from ..viewsets import (CatalogViewSet, PageViewSet, QuestionSetViewSet, QuestionViewSet,
-                        SectionViewSet, ValueTypeViewSet, WidgetTypeViewSet)
+from ..viewsets import (
+    CatalogViewSet,
+    PageViewSet,
+    QuestionSetViewSet,
+    QuestionViewSet,
+    SectionViewSet,
+    ValueTypeViewSet,
+    WidgetTypeViewSet,
+)
 
 app_name = 'v1-questions'
 
diff --git a/rdmo/questions/utils.py b/rdmo/questions/utils.py
index 95f793d884..84bed4874f 100644
--- a/rdmo/questions/utils.py
+++ b/rdmo/questions/utils.py
@@ -1,4 +1,4 @@
-from rdmo.core.plugins import get_plugins, get_plugin
+from rdmo.core.plugins import get_plugin, get_plugins
 
 
 def get_widgets():
diff --git a/rdmo/questions/validators.py b/rdmo/questions/validators.py
index 6f6d78e339..961d205f23 100644
--- a/rdmo/questions/validators.py
+++ b/rdmo/questions/validators.py
@@ -1,7 +1,6 @@
 from django.utils.translation import gettext_lazy as _
 
-from rdmo.core.validators import (InstanceValidator, LockedValidator,
-                                  UniqueURIValidator)
+from rdmo.core.validators import InstanceValidator, LockedValidator, UniqueURIValidator
 
 from .models import Catalog, Page, Question, QuestionSet, Section
 
@@ -55,9 +54,10 @@ def __call__(self, data, serializer=None):
                 # get the original from the view when cloning an attribute
                 obj = view.get_object()
                 for questionset in questionsets:
-                    if obj in [questionset] + questionset.descendants:
+                    if obj in [questionset, *questionset.descendants]:
                         self.raise_validation_error({
-                            'questionset': [_('A question set may not be cloned to be a child of itself or one of its descendants.')]
+                            'questionset': [_('A question set may not be cloned to be a child of itself or one of '
+                                              'its descendants.')]
                         })
             if not self.instance:
                 return
@@ -67,7 +67,7 @@ def __call__(self, data, serializer=None):
             return
 
         for questionset in questionsets:
-            if self.instance in [questionset] + questionset.descendants:
+            if self.instance in [questionset, *questionset.descendants]:
                 self.raise_validation_error({
                     'questionsets': [_('A question set may not be a child of itself or one of its descendants.')]
                 })
diff --git a/rdmo/questions/viewsets.py b/rdmo/questions/viewsets.py
index 4c18aa608d..4184669d42 100644
--- a/rdmo/questions/viewsets.py
+++ b/rdmo/questions/viewsets.py
@@ -1,11 +1,13 @@
 from django.db import models
-from django_filters.rest_framework import DjangoFilterBackend
+
 from rest_framework.decorators import action
 from rest_framework.filters import SearchFilter
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
 
+from django_filters.rest_framework import DjangoFilterBackend
+
 from rdmo.core.constants import VALUE_TYPE_CHOICES
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
@@ -13,20 +15,30 @@
 from rdmo.core.views import ChoicesViewSet
 
 from .models import Catalog, Page, Question, QuestionSet, Section
-from .renderers import (CatalogRenderer, PageRenderer, QuestionRenderer,
-                        QuestionSetRenderer, SectionRenderer)
-from .serializers.export import (CatalogExportSerializer, PageExportSerializer,
-                                 QuestionExportSerializer,
-                                 QuestionSetExportSerializer,
-                                 SectionExportSerializer)
-from .serializers.v1 import (CatalogIndexSerializer, CatalogNestedSerializer,
-                             CatalogSerializer, PageIndexSerializer,
-                             PageNestedSerializer, PageSerializer,
-                             QuestionIndexSerializer, QuestionSerializer,
-                             QuestionSetIndexSerializer,
-                             QuestionSetNestedSerializer,
-                             QuestionSetSerializer, SectionIndexSerializer,
-                             SectionNestedSerializer, SectionSerializer)
+from .renderers import CatalogRenderer, PageRenderer, QuestionRenderer, QuestionSetRenderer, SectionRenderer
+from .serializers.export import (
+    CatalogExportSerializer,
+    PageExportSerializer,
+    QuestionExportSerializer,
+    QuestionSetExportSerializer,
+    SectionExportSerializer,
+)
+from .serializers.v1 import (
+    CatalogIndexSerializer,
+    CatalogNestedSerializer,
+    CatalogSerializer,
+    PageIndexSerializer,
+    PageNestedSerializer,
+    PageSerializer,
+    QuestionIndexSerializer,
+    QuestionSerializer,
+    QuestionSetIndexSerializer,
+    QuestionSetNestedSerializer,
+    QuestionSetSerializer,
+    SectionIndexSerializer,
+    SectionNestedSerializer,
+    SectionSerializer,
+)
 from .utils import get_widget_type_choices
 
 
@@ -70,9 +82,11 @@ def export(self, request, export_format='xml'):
             xml = CatalogRenderer().render(serializer.data, context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name='catalogs')
         else:
-            return render_to_format(self.request, export_format, 'questions', 'questions/export/catalogs.html', {
-                'catalogs': queryset
-            })
+            return render_to_format(
+                self.request, export_format, 'questions', 'questions/export/catalogs.html', {
+                    'catalogs': queryset
+                }
+            )
 
     @action(detail=True, url_path='export(/(?P<export_format>[a-z]+))?')
     def detail_export(self, request, pk=None, export_format='xml'):
@@ -81,9 +95,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = CatalogRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'questions/export/catalogs.html', {
-                'catalogs': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'questions/export/catalogs.html', {
+                    'catalogs': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
@@ -138,9 +154,11 @@ def export(self, request, export_format='xml'):
             xml = SectionRenderer().render(serializer.data, context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name='sections')
         else:
-            return render_to_format(self.request, export_format, 'questions', 'questions/export/sections.html', {
-                'sections': queryset
-            })
+            return render_to_format(
+                self.request, export_format, 'questions', 'questions/export/sections.html', {
+                    'sections': queryset
+                }
+            )
 
     @action(detail=True, url_path='export(/(?P<export_format>[a-z]+))?')
     def detail_export(self, request, pk=None, export_format='xml'):
@@ -149,9 +167,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = SectionRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'questions/export/sections.html', {
-                'sections': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'questions/export/sections.html', {
+                    'sections': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
@@ -213,9 +233,11 @@ def export(self, request, export_format='xml'):
             xml = PageRenderer().render(serializer.data, context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name='pages')
         else:
-            return render_to_format(self.request, export_format, 'questions', 'questions/export/pages.html', {
-                'pages': queryset
-            })
+            return render_to_format(
+                self.request, export_format, 'questions', 'questions/export/pages.html', {
+                    'pages': queryset
+                }
+            )
 
     @action(detail=True, url_path='export(/(?P<export_format>[a-z]+))?')
     def detail_export(self, request, pk=None, export_format='xml'):
@@ -224,9 +246,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = PageRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'questions/export/pages.html', {
-                'pages': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'questions/export/pages.html', {
+                    'pages': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
@@ -288,9 +312,11 @@ def export(self, request, export_format='xml'):
             xml = QuestionSetRenderer().render(serializer.data, context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name='questionsets')
         else:
-            return render_to_format(self.request, export_format, 'questionsets', 'questions/export/questionsets.html', {
-                'questionsets': queryset
-            })
+            return render_to_format(
+                self.request, export_format, 'questionsets', 'questions/export/questionsets.html', {
+                    'questionsets': queryset
+                }
+            )
 
     @action(detail=True, url_path='export(/(?P<export_format>[a-z]+))?')
     def detail_export(self, request, pk=None, export_format='xml'):
@@ -299,9 +325,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = QuestionSetRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'questions/export/questionsets.html', {
-                'questionsets': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'questions/export/questionsets.html', {
+                    'questionsets': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
@@ -360,9 +388,11 @@ def export(self, request, export_format='xml'):
             xml = QuestionRenderer().render(serializer.data, context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name='questions')
         else:
-            return render_to_format(self.request, export_format, 'questions', 'questions/export/questions.html', {
-                'questions': queryset
-            })
+            return render_to_format(
+                self.request, export_format, 'questions', 'questions/export/questions.html', {
+                    'questions': queryset
+                }
+            )
 
     @action(detail=True, url_path='export(/(?P<export_format>[a-z]+))?')
     def detail_export(self, request, pk=None, export_format='xml'):
@@ -371,9 +401,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = QuestionRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'questions/export/questions.html', {
-                'questions': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'questions/export/questions.html', {
+                    'questions': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
diff --git a/rdmo/services/providers.py b/rdmo/services/providers.py
index 1edb70e360..41b6530224 100644
--- a/rdmo/services/providers.py
+++ b/rdmo/services/providers.py
@@ -1,22 +1,19 @@
-import hmac
-import json
 import logging
-from urllib.parse import quote, urlencode
+from urllib.parse import urlencode
 
-import requests
 from django.conf import settings
-from django.core.exceptions import ObjectDoesNotExist
-from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.http import HttpResponseRedirect
 from django.shortcuts import render
 from django.urls import reverse
 from django.utils.crypto import get_random_string
 from django.utils.translation import gettext_lazy as _
-from rdmo.core.plugins import Plugin
+
+import requests
 
 logger = logging.getLogger(__name__)
 
 
-class OauthProviderMixin(object):
+class OauthProviderMixin:
 
     def get(self, request, url):
         # get access token from the session
@@ -128,7 +125,7 @@ def post_success(self, request, response):
         raise NotImplementedError
 
     def get_session_key(self, key):
-        return '{}.{}'.format(self.class_name, key)
+        return f'{self.class_name}.{key}'
 
     def store_in_session(self, request, key, data):
         session_key = self.get_session_key(key)
@@ -143,7 +140,7 @@ def pop_from_session(self, request, key):
         return request.session.pop(session_key, None)
 
     def get_authorization_headers(self, access_token):
-        return {'Authorization': 'Bearer {}'.format(access_token)}
+        return {'Authorization': f'Bearer {access_token}'}
 
     def get_authorize_params(self, request, state):
         raise NotImplementedError
@@ -183,7 +180,7 @@ def redirect_path(self):
 
     def get_authorization_headers(self, access_token):
         return {
-            'Authorization': 'token {}'.format(access_token),
+            'Authorization': f'token {access_token}',
             'Accept': 'application/vnd.github.v3+json'
         }
 
@@ -213,15 +210,15 @@ def gitlab_url(self):
 
     @property
     def authorize_url(self):
-        return '{}/oauth/authorize'.format(self.gitlab_url)
+        return f'{self.gitlab_url}/oauth/authorize'
 
     @property
     def token_url(self):
-        return '{}/oauth/token'.format(self.gitlab_url)
+        return f'{self.gitlab_url}/oauth/token'
 
     @property
     def api_url(self):
-        return '{}/api/v4'.format(self.gitlab_url)
+        return f'{self.gitlab_url}/api/v4'
 
     @property
     def client_id(self):
diff --git a/rdmo/services/validators.py b/rdmo/services/validators.py
index d161e3506e..b6f02f68f0 100644
--- a/rdmo/services/validators.py
+++ b/rdmo/services/validators.py
@@ -3,7 +3,7 @@
 from rdmo.core.plugins import get_plugin
 
 
-class ProviderValidator():
+class ProviderValidator:
 
     def __call__(self, data):
         provider_key = data.get('provider_key')
@@ -15,15 +15,15 @@ def __call__(self, data):
 
         try:
             options = {option.get('key'): option.get('value') for option in data.get('options', [])}
-        except KeyError:
+        except KeyError as e:
             raise ValidationError({
                 'options': 'Options need to be of the form "{"key": "": "value": ""}".'
-            })
+            }) from e
 
         for key in options:
             if key not in [field.get('key') for field in provider.fields]:
                 raise ValidationError({
-                    'options': 'Key "{}" is not valid.'.format(key)
+                    'options': f'Key "{key}" is not valid.'
                 })
 
         for field in provider.fields:
diff --git a/rdmo/tasks/admin.py b/rdmo/tasks/admin.py
index b241070485..bb2c85c429 100644
--- a/rdmo/tasks/admin.py
+++ b/rdmo/tasks/admin.py
@@ -22,7 +22,7 @@ def clean(self):
 class TaskAdmin(admin.ModelAdmin):
     form = TaskAdminForm
 
-    search_fields = ['uri'] + get_language_fields('title') + get_language_fields('text')
+    search_fields = ['uri', *get_language_fields('title'), *get_language_fields('text')]
     list_display = ('uri', 'title', 'text', 'available')
     readonly_fields = ('uri', )
     list_filter = ('available', )
diff --git a/rdmo/tasks/imports.py b/rdmo/tasks/imports.py
index a6a14fdee4..7631d1be44 100644
--- a/rdmo/tasks/imports.py
+++ b/rdmo/tasks/imports.py
@@ -2,9 +2,14 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (check_permissions, set_common_fields,
-                               set_foreign_field, set_lang_field,
-                               set_m2m_instances, validate_instance)
+from rdmo.core.imports import (
+    check_permissions,
+    set_common_fields,
+    set_foreign_field,
+    set_lang_field,
+    set_m2m_instances,
+    validate_instance,
+)
 
 from .models import Task
 from .validators import TaskLockedValidator, TaskUniqueURIValidator
diff --git a/rdmo/tasks/managers.py b/rdmo/tasks/managers.py
index ee446e401f..0251eab9f0 100644
--- a/rdmo/tasks/managers.py
+++ b/rdmo/tasks/managers.py
@@ -1,10 +1,13 @@
 from django.db import models
 
-from rdmo.core.managers import (AvailabilityManagerMixin,
-                                AvailabilityQuerySetMixin,
-                                CurrentSiteManagerMixin,
-                                CurrentSiteQuerySetMixin, GroupsManagerMixin,
-                                GroupsQuerySetMixin)
+from rdmo.core.managers import (
+    AvailabilityManagerMixin,
+    AvailabilityQuerySetMixin,
+    CurrentSiteManagerMixin,
+    CurrentSiteQuerySetMixin,
+    GroupsManagerMixin,
+    GroupsQuerySetMixin,
+)
 
 
 class TaskQuestionSet(CurrentSiteQuerySetMixin, GroupsQuerySetMixin, AvailabilityQuerySetMixin, models.QuerySet):
diff --git a/rdmo/tasks/models.py b/rdmo/tasks/models.py
index 179badfb17..6edc0c16b9 100644
--- a/rdmo/tasks/models.py
+++ b/rdmo/tasks/models.py
@@ -3,9 +3,10 @@
 from django.contrib.sites.models import Site
 from django.db import models
 from django.utils.translation import gettext_lazy as _
+
 from rdmo.conditions.models import Condition
 from rdmo.core.models import TranslationMixin
-from rdmo.core.utils import copy_model, join_url
+from rdmo.core.utils import join_url
 from rdmo.domain.models import Attribute
 from rdmo.questions.models import Catalog
 
@@ -44,7 +45,8 @@ class Task(TranslationMixin, models.Model):
     catalogs = models.ManyToManyField(
         Catalog, blank=True,
         verbose_name=_('Catalogs'),
-        help_text=_('The catalogs this task can be used with. An empty list implies that this task can be used with every catalog.')
+        help_text=_('The catalogs this task can be used with. '
+                    'An empty list implies that this task can be used with every catalog.')
     )
     sites = models.ManyToManyField(
         Site, blank=True,
@@ -119,7 +121,8 @@ class Task(TranslationMixin, models.Model):
     end_attribute = models.ForeignKey(
         Attribute, blank=True, null=True, on_delete=models.SET_NULL, related_name='tasks_as_end',
         verbose_name=_('End date attribute'),
-        help_text=_('The attribute that is setting the end date for this task (optional, if no end date attribute is given, the start date attribute sets also the end date).')
+        help_text=_('The attribute that is setting the end date for this task '
+                    '(optional, if no end date attribute is given, the start date attribute sets also the end date).')
     )
     days_before = models.IntegerField(
         blank=True, null=True,
diff --git a/rdmo/tasks/serializers/v1.py b/rdmo/tasks/serializers/v1.py
index 8940268c2b..cebd38a415 100644
--- a/rdmo/tasks/serializers/v1.py
+++ b/rdmo/tasks/serializers/v1.py
@@ -1,9 +1,11 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   TranslationSerializerMixin)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    TranslationSerializerMixin,
+)
 
 from ..models import Task
 from ..validators import TaskLockedValidator, TaskUniqueURIValidator
diff --git a/rdmo/tasks/viewsets.py b/rdmo/tasks/viewsets.py
index 2d99061913..c07f90d1b0 100644
--- a/rdmo/tasks/viewsets.py
+++ b/rdmo/tasks/viewsets.py
@@ -1,10 +1,12 @@
 from django.db import models
-from django_filters.rest_framework import DjangoFilterBackend
+
 from rest_framework.decorators import action
 from rest_framework.filters import SearchFilter
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
 
+from django_filters.rest_framework import DjangoFilterBackend
+
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import is_truthy, render_to_format
@@ -59,9 +61,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = TaskRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'tasks/export/tasks.html', {
-                'tasks': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'tasks/export/tasks.html', {
+                    'tasks': [self.get_object()]
+                }
+            )
 
     def get_export_renderer_context(self, request):
         full = is_truthy(request.GET.get('full'))
diff --git a/rdmo/views/admin.py b/rdmo/views/admin.py
index ba4c17711c..1cfebec9af 100644
--- a/rdmo/views/admin.py
+++ b/rdmo/views/admin.py
@@ -22,7 +22,7 @@ def clean(self):
 class ViewAdmin(admin.ModelAdmin):
     form = ViewAdminForm
 
-    search_fields = ['uri'] + get_language_fields('title') + get_language_fields('help')
+    search_fields = ['uri', *get_language_fields('title'), *get_language_fields('help')]
     list_display = ('uri', 'title', 'help', 'available')
     readonly_fields = ('uri', )
     list_filter = ('available', )
diff --git a/rdmo/views/imports.py b/rdmo/views/imports.py
index e811625086..7c09061035 100644
--- a/rdmo/views/imports.py
+++ b/rdmo/views/imports.py
@@ -2,9 +2,7 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (check_permissions, set_common_fields,
-                               set_lang_field, set_m2m_instances,
-                               validate_instance)
+from rdmo.core.imports import check_permissions, set_common_fields, set_lang_field, set_m2m_instances, validate_instance
 
 from .models import View
 from .validators import ViewLockedValidator, ViewUniqueURIValidator
diff --git a/rdmo/views/managers.py b/rdmo/views/managers.py
index 73f0604c44..4c584e4839 100644
--- a/rdmo/views/managers.py
+++ b/rdmo/views/managers.py
@@ -1,10 +1,13 @@
 from django.db import models
 
-from rdmo.core.managers import (AvailabilityManagerMixin,
-                                AvailabilityQuerySetMixin,
-                                CurrentSiteManagerMixin,
-                                CurrentSiteQuerySetMixin, GroupsManagerMixin,
-                                GroupsQuerySetMixin)
+from rdmo.core.managers import (
+    AvailabilityManagerMixin,
+    AvailabilityQuerySetMixin,
+    CurrentSiteManagerMixin,
+    CurrentSiteQuerySetMixin,
+    GroupsManagerMixin,
+    GroupsQuerySetMixin,
+)
 
 
 class ViewQuestionSet(CurrentSiteQuerySetMixin, GroupsQuerySetMixin, AvailabilityQuerySetMixin, models.QuerySet):
diff --git a/rdmo/views/models.py b/rdmo/views/models.py
index 224c756b3c..510f502659 100644
--- a/rdmo/views/models.py
+++ b/rdmo/views/models.py
@@ -7,7 +7,7 @@
 
 from rdmo import __version__
 from rdmo.core.models import TranslationMixin
-from rdmo.core.utils import copy_model, get_pandoc_main_version, join_url
+from rdmo.core.utils import get_pandoc_main_version, join_url
 from rdmo.questions.models import Catalog
 
 from .managers import ViewManager
@@ -46,7 +46,8 @@ class View(models.Model, TranslationMixin):
     catalogs = models.ManyToManyField(
         Catalog, blank=True,
         verbose_name=_('Catalogs'),
-        help_text=_('The catalogs this view can be used with. An empty list implies that this view can be used with every catalog.')
+        help_text=_('The catalogs this view can be used with. '
+                    'An empty list implies that this view can be used with every catalog.')
     )
     sites = models.ManyToManyField(
         Site, blank=True,
diff --git a/rdmo/views/serializers/v1.py b/rdmo/views/serializers/v1.py
index a7e27a3d1b..5862537a29 100644
--- a/rdmo/views/serializers/v1.py
+++ b/rdmo/views/serializers/v1.py
@@ -1,10 +1,13 @@
 from django.template import Context, Template, TemplateSyntaxError
+
 from rest_framework import exceptions, serializers
 
-from rdmo.core.serializers import (ElementModelSerializerMixin,
-                                   ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionSerializerMixin,
-                                   TranslationSerializerMixin)
+from rdmo.core.serializers import (
+    ElementModelSerializerMixin,
+    ElementWarningSerializerMixin,
+    ReadOnlyObjectPermissionSerializerMixin,
+    TranslationSerializerMixin,
+)
 
 from ..models import View
 from ..validators import ViewLockedValidator, ViewUniqueURIValidator
@@ -29,7 +32,7 @@ def validate(self, data):
         except (KeyError, IndexError):
             pass
         except (TemplateSyntaxError, TypeError) as e:
-            raise exceptions.ValidationError({'template': '\n'.join(e.args)})
+            raise exceptions.ValidationError({'template': '\n'.join(e.args)}) from e
 
         return super().validate(data)
 
diff --git a/rdmo/views/templatetags/view_tags.py b/rdmo/views/templatetags/view_tags.py
index 9373cf0a0b..44f7b13319 100644
--- a/rdmo/views/templatetags/view_tags.py
+++ b/rdmo/views/templatetags/view_tags.py
@@ -1,9 +1,9 @@
 from django import template
 from django.utils.translation import gettext_lazy as _
 
-from rdmo.core.constants import (VALUE_TYPE_DATETIME, VALUE_TYPE_INTEGER,
-                                 VALUE_TYPE_TEXT)
+from rdmo.core.constants import VALUE_TYPE_DATETIME, VALUE_TYPE_INTEGER, VALUE_TYPE_TEXT
 from rdmo.projects.models import Value
+
 register = template.Library()
 
 
@@ -45,7 +45,8 @@ def get_numbers(context, attribute, set_prefix='*', set_index='*', index='*', pr
 @register.simple_tag(takes_context=True)
 def get_value(context, attribute, set_prefix='', set_index=0, index=0, project=None):
     try:
-        return get_values(context, attribute, set_prefix=set_prefix, set_index=set_index, index=index, project=project)[0]
+        return get_values(context, attribute, set_prefix=set_prefix, set_index=set_index,
+                          index=index, project=project)[0]
     except IndexError:
         return None
 
@@ -69,7 +70,8 @@ def get_set_values(context, set, attribute, set_prefix='', project=None):
 def get_set_value(context, set, attribute, set_prefix='', index=0, project=None):
     try:
         set_index = set.get('set_index')
-        return get_values(context, attribute, set_prefix=set_prefix, set_index=set_index, index=index, project=project)[0]
+        return get_values(context, attribute, set_prefix=set_prefix, set_index=set_index,
+                          index=index, project=project)[0]
     except IndexError:
         return None
 
@@ -77,7 +79,7 @@ def get_set_value(context, set, attribute, set_prefix='', index=0, project=None)
 @register.simple_tag(takes_context=True)
 def get_set_prefixes(context, attribute, project=None):
     try:
-        return sorted(set(map(lambda value: value['set_prefix'], get_values(context, attribute, project=project))))
+        return sorted({value['set_prefix'] for value in get_values(context, attribute, project=project)})
     except IndexError:
         return None
 
@@ -85,7 +87,9 @@ def get_set_prefixes(context, attribute, project=None):
 @register.simple_tag(takes_context=True)
 def get_set_indexes(context, attribute, set_prefix='', project=None):
     try:
-        return sorted(set(map(lambda value: value['set_index'], get_values(context, attribute, set_prefix=set_prefix, project=project))))
+        return sorted({
+            value['set_index'] for value in get_values(context, attribute, set_prefix=set_prefix, project=project)
+        })
     except IndexError:
         return None
 
@@ -109,7 +113,8 @@ def get_set(context, attribute, set_prefix='', project=None):
 
 @register.inclusion_tag('views/tags/value.html', takes_context=True)
 def render_value(context, attribute, set_prefix='', set_index=0, index=0, project=None):
-    context['value'] = get_value(context, attribute, set_prefix=set_prefix, set_index=set_index, index=index, project=project)
+    context['value'] = get_value(context, attribute, set_prefix=set_prefix, set_index=set_index,
+                                 index=index, project=project)
     return context
 
 
@@ -167,11 +172,12 @@ def get_labels(context, element, set_prefix='', set_index=0, project=None):
     set_labels = []
     for ancestor in element['ancestors']:
         if ancestor['is_collection']:
-            set_label = '#{}'.format(set_index + 1)
+            set_label = f'#{set_index + 1}'
 
             if ancestor['attribute']:
                 # get attribute value
-                value = get_value(context, ancestor['attribute'], set_prefix=set_prefix, set_index=set_index, index=0, project=project)
+                value = get_value(context, ancestor['attribute'], set_prefix=set_prefix, set_index=set_index,
+                                  index=0, project=project)
                 if value:
                     set_label = '"{}"'.format(value['value'])
 
diff --git a/rdmo/views/utils.py b/rdmo/views/utils.py
index cda712d1fb..76b710de7c 100644
--- a/rdmo/views/utils.py
+++ b/rdmo/views/utils.py
@@ -6,7 +6,7 @@
 from mptt.utils import get_cached_trees
 
 
-class ProjectWrapper(object):
+class ProjectWrapper:
 
     def __init__(self, project, snapshot=None):
         self._project = project
@@ -113,7 +113,7 @@ def _get_values(self, attribute, set_prefix='*', set_index='*', index='*'):
         if index != '*':
             values = filter(lambda value: value.collection_index == index, values)
 
-        return list(map(lambda value: value.as_dict, values))
+        return [value.as_dict for value in values]
 
     def _check_element(self, element, set_prefix=None, set_index=None):
         conditions = set(filter(lambda condition: condition.uri in element['conditions'], self._conditions))
diff --git a/rdmo/views/viewsets.py b/rdmo/views/viewsets.py
index 0e6ba73288..7d26002e0e 100644
--- a/rdmo/views/viewsets.py
+++ b/rdmo/views/viewsets.py
@@ -1,10 +1,12 @@
 from django.db import models
-from django_filters.rest_framework import DjangoFilterBackend
+
 from rest_framework.decorators import action
 from rest_framework.filters import SearchFilter
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
 
+from django_filters.rest_framework import DjangoFilterBackend
+
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import render_to_format
@@ -58,6 +60,8 @@ def detail_export(self, request, pk=None, export_format='xml'):
             xml = ViewRenderer().render([serializer.data])
             return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().uri_path, 'views/export/views.html', {
-                'views': [self.get_object()]
-            })
+            return render_to_format(
+                self.request, export_format, self.get_object().uri_path, 'views/export/views.html', {
+                    'views': [self.get_object()]
+                }
+            )


Test patch:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index eaf1ae0757..34440010df 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -36,7 +36,6 @@ jobs:
         key: lint-${{ hashFiles('.pre-commit-config.yaml') }}
     - name: Run ruff via pre-commit
       run: pre-commit run --all-files --color=always
-      continue-on-error: true # TODO: remove once files are reformatted
 
   test:
     runs-on: ubuntu-22.04
diff --git a/conftest.py b/conftest.py
index 0d621acf63..b62eb83355 100644
--- a/conftest.py
+++ b/conftest.py
@@ -3,6 +3,7 @@
 from pathlib import Path
 
 import pytest
+
 from django.conf import settings
 from django.core.management import call_command
 
diff --git a/rdmo/accounts/tests/test_admin.py b/rdmo/accounts/tests/test_admin.py
index c5d51dc50b..4f1a25eb5d 100644
--- a/rdmo/accounts/tests/test_admin.py
+++ b/rdmo/accounts/tests/test_admin.py
@@ -2,7 +2,6 @@
 
 from django.urls import reverse
 
-
 roles = ('member', 'manager', 'editor', 'reviewer')
 
 
diff --git a/rdmo/accounts/tests/test_utils.py b/rdmo/accounts/tests/test_utils.py
index 706f23166b..08a7c51220 100644
--- a/rdmo/accounts/tests/test_utils.py
+++ b/rdmo/accounts/tests/test_utils.py
@@ -1,10 +1,10 @@
 import pytest
+
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import AnonymousUser
 
 from rdmo.accounts.models import Role
-from rdmo.accounts.utils import get_full_name, is_site_manager, delete_user, get_user_from_db_or_none
-
+from rdmo.accounts.utils import delete_user, get_full_name, get_user_from_db_or_none, is_site_manager
 
 normal_users = (
     ('user', 'user', 'user@example.com'),
@@ -55,34 +55,33 @@ def test_is_site_manager_returns_false_when_role_doesnotexist_(db, client, usern
 
 
 @pytest.mark.parametrize('username,password,email', users)
-def test_delete_user_returns_true(db, username, password, email):
+def test_delete_user(db, username, password, email):
     user = get_user_model().objects.get(username=username, email=email)
     assert delete_user(user=user, email=email, password=password) is True
 
 
 @pytest.mark.parametrize('username,password,email', users)
-def test_delete_user_returns_false_when_user_or_email_is_none(db, username, password, email):
+def test_delete_user_when_user_or_email_is_none(db, username, password, email):
     user = get_user_model().objects.get(username=username, email=email)
     for test_user, test_email in ((user, None), (None, email), (None, None)):
         assert delete_user(user=test_user, email=test_email) is False
 
 
 @pytest.mark.parametrize('username,password,email', users)
-def test_delete_user_returns_false_when_user_is_not_equal_to_db_user(db, username, password, email):
+def test_delete_user_when_user_is_not_equal_to_db_user(db, username, password, email):
     user = get_user_model().objects.get(username=username, email=email)
     user.pk += 1
     assert delete_user(user=user, email=email, password=None) is False
 
 
-
 @pytest.mark.parametrize('username,password,email', users)
-def test_delete_user_returns_false_when_user_with_usable_password_gives_none_for_password(db, username, password, email):
+def test_delete_user_when_user_with_usable_password_gives_none_for_password(db, username, password, email):
     user = get_user_model().objects.get(username=username, email=email)
     assert delete_user(user=user, email=email, password=None) is False
 
 
 @pytest.mark.parametrize('username,password,email', users)
-def test_delete_user_returns_false_when_delete_user_raises_an_exception(db, username, password, email):
+def test_delete_user_when_delete_user_raises_an_exception(db, username, password, email):
     user = get_user_model().objects.get(username=username, email=email)
     def _delete(): raise ValueError
     user.delete = _delete
@@ -90,7 +89,7 @@ def _delete(): raise ValueError
 
 
 @pytest.mark.parametrize('username,password,email', users)
-def test_delete_user_returns_false_when_delete_is_called_for_user_without_usable_password_and_raises_an_exception(db, username, password, email):
+def test_delete_user_without_usable_password_and_raises_an_exception(db, username, password, email):
     user = get_user_model().objects.get(username=username, email=email)
     user.set_unusable_password()
     def _delete(): raise ValueError
diff --git a/rdmo/accounts/tests/test_views.py b/rdmo/accounts/tests/test_views.py
index a0b9dc2d86..955a68c4b2 100644
--- a/rdmo/accounts/tests/test_views.py
+++ b/rdmo/accounts/tests/test_views.py
@@ -8,8 +8,7 @@
 from django.urls import reverse
 from django.urls.exceptions import NoReverseMatch
 
-from pytest_django.asserts import assertTemplateUsed, assertRedirects, \
-                                  assertContains, assertNotContains, assertURLEqual
+from pytest_django.asserts import assertContains, assertNotContains, assertRedirects, assertTemplateUsed, assertURLEqual
 
 from rdmo.accounts.tests.utils import reload_urls
 
@@ -318,8 +317,8 @@ def test_password_reset_post_valid(db, client, settings, account):
         assert len(mail.outbox) == 1
 
         # get the link from the mail
-        urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', mail.outbox[0].body)  # complicated regex
-        # urls = [i.strip() for i in mail.outbox[0].body.splitlines() if i.strip().startswith('http')]  # simpler alternative
+        complicated_regex = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
+        urls = re.findall(complicated_regex, mail.outbox[0].body)
         assert len(urls) == 1
 
         # get the password_reset page
@@ -672,7 +671,7 @@ def test_token_post_for_anonymous(db, client, settings, account_allow_user_token
     settings.ACCOUNT_ALLOW_USER_TOKEN = account_allow_user_token
     reload_urls('accounts')
     client.login(username='anonymous', password=None)
-    # breakpoint()
+
     if settings.ACCOUNT_ALLOW_USER_TOKEN:
         url = reverse('account_token')
         response = client.post(url)
@@ -692,7 +691,8 @@ def test_home_login_form(db, client, settings, login_form, username, password):
     client.login(username='anonymous', password=None)
     url = reverse('home')
     response = client.get(url)
-    response.status_code == 200
+
+    assert response.status_code == 200
     if settings.LOGIN_FORM:
         assertContains(response, f'<form method="post" action="{reverse("account_login")}"')
     else:
@@ -712,7 +712,7 @@ def test_shibboleth_for_home_url(db, client, settings, shibboleth, username, pas
 
     if settings.SHIBBOLETH:
         # Anyonymous user is redirected to login
-        response.status_code == 200
+        assert response.status_code == 200
         assertContains(response, 'href="' + reverse('shibboleth_login'))
 
 
@@ -740,40 +740,44 @@ def test_shibboleth_login_view(db, client, settings, username, password):
             assertRedirects(response, reverse('projects'))
 
 
-@pytest.mark.parametrize('shibboleth', boolean_toggle)
-@pytest.mark.parametrize('shibboleth_login_url', (None, '/shibboleth/login'))
-@pytest.mark.parametrize('username,password', users)
-def test_shibboleth_for_projects_url(db, client, settings, shibboleth, shibboleth_login_url, username, password):
-    settings.SHIBBOLETH = shibboleth
-    settings.SHIBBOLETH_LOGIN_URL = shibboleth_login_url
-    settings.ACCOUNT = False
-    reload_urls('accounts')
-    client.login(username='anonymous', password=None)
-    # Try to access projects
-    url = reverse('projects')
-    response = client.get(url)
+# @pytest.mark.parametrize('shibboleth', boolean_toggle)
+# @pytest.mark.parametrize('shibboleth_login_url', (None, '/shibboleth/login'))
+# @pytest.mark.parametrize('username,password', users)
+# def test_shibboleth_for_projects_url(db, client, settings, shibboleth, shibboleth_login_url, username, password):
+#     settings.SHIBBOLETH = shibboleth
+#     settings.SHIBBOLETH_LOGIN_URL = shibboleth_login_url
+#     settings.ACCOUNT = False
+#     reload_urls('accounts')
 
-    if settings.SHIBBOLETH and settings.SHIBBOLETH_LOGIN_URL:
-        # Anyonymous user is redirected to login
-        response.status_code == 302
+#     client.login(username='anonymous', password=None)
 
-        assertRedirects(response, reverse('account_login') + '?next=' + reverse('projects'))
+#     # Try to access projects
+#     url = reverse('projects')
+#     response = client.get(url)
 
-        response = client.get(response.url)
-        # Shibboleth login is shown
-        response.status_code == 200
-        assertContains(response, 'href="/account/shibboleth/login/">')
+#     if settings.SHIBBOLETH and settings.SHIBBOLETH_LOGIN_URL:
+#         print(settings.SHIBBOLETH, settings.SHIBBOLETH_LOGIN_URL)
+#         # Anyonymous user is redirected to login
+#         assert response.status_code == 302
 
-        # Redirected user logs in
-        client.login(username=username, password=password)
-        response = client.get(response)
+#         assertRedirects(response, reverse('account_login') + '?next=' + reverse('projects'))
 
-        if password:
-            # Logged in user lands on projects
-            response.status_code == 200
-        else:
-            # Anonymous user is redirected to shibboleth login
-            response.status_code == 404
+#         response = client.get(response.url)
+
+#         # Shibboleth login is shown
+#         assert response.status_code == 200
+#         assertContains(response, 'href="/account/shibboleth/login/">')
+
+#         # Redirected user logs in
+#         client.login(username=username, password=password)
+#         response = client.get(response)
+
+#         if password:
+#             # Logged in user lands on projects
+#             assert response.status_code == 200
+#         else:
+#             # Anonymous user is redirected to shibboleth login
+#             assert response.status_code == 404
 
 
 @pytest.mark.parametrize('shibboleth', boolean_toggle)
diff --git a/rdmo/accounts/tests/test_viewsets.py b/rdmo/accounts/tests/test_viewsets.py
index 10fd0369f2..1ce0b5058e 100644
--- a/rdmo/accounts/tests/test_viewsets.py
+++ b/rdmo/accounts/tests/test_viewsets.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
diff --git a/rdmo/accounts/tests/utils.py b/rdmo/accounts/tests/utils.py
index a5c328d263..b9f796e2d7 100644
--- a/rdmo/accounts/tests/utils.py
+++ b/rdmo/accounts/tests/utils.py
@@ -1,6 +1,6 @@
 import sys
-
-from importlib import reload, import_module
+from importlib import import_module, reload
+from typing import Optional
 
 from django.urls import clear_url_caches
 
@@ -21,7 +21,7 @@ def reload_urlconf(urlconf=None, settings=None):
         import_module(urlconf)
 
 
-def reload_urls(app_name: str = None, settings=None) -> None:
+def reload_urls(app_name: Optional[str] = None, settings=None) -> None:
     # reload the urlconf of the app
     if app_name is not None:
         reload_urlconf(urlconf=f'rdmo.{app_name}.urls', settings=settings)
diff --git a/rdmo/conditions/tests/test_validator_locked.py b/rdmo/conditions/tests/test_validator_locked.py
index 582e69746c..f35cfe8976 100644
--- a/rdmo/conditions/tests/test_validator_locked.py
+++ b/rdmo/conditions/tests/test_validator_locked.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Condition
 from ..serializers.v1 import ConditionSerializer
diff --git a/rdmo/conditions/tests/test_validator_unique_uri.py b/rdmo/conditions/tests/test_validator_unique_uri.py
index f05dae536e..a0b203f160 100644
--- a/rdmo/conditions/tests/test_validator_unique_uri.py
+++ b/rdmo/conditions/tests/test_validator_unique_uri.py
@@ -1,8 +1,9 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Condition
 from ..serializers.v1 import ConditionSerializer
diff --git a/rdmo/conditions/tests/test_viewset_condition.py b/rdmo/conditions/tests/test_viewset_condition.py
index 94740e8a03..92d15cf6c4 100644
--- a/rdmo/conditions/tests/test_viewset_condition.py
+++ b/rdmo/conditions/tests/test_viewset_condition.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import Condition
@@ -97,7 +98,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'source': instance.source.pk,
             'relation': instance.relation,
@@ -119,7 +120,7 @@ def test_create_optionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -146,7 +147,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -173,7 +174,7 @@ def test_create_questionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -200,7 +201,7 @@ def test_create_question(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -227,7 +228,7 @@ def test_create_task(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
diff --git a/rdmo/conditions/tests/test_viewset_condition_multisite.py b/rdmo/conditions/tests/test_viewset_condition_multisite.py
index cbb8f59806..0455e1c22a 100644
--- a/rdmo/conditions/tests/test_viewset_condition_multisite.py
+++ b/rdmo/conditions/tests/test_viewset_condition_multisite.py
@@ -1,17 +1,14 @@
 import xml.etree.ElementTree as et
 
 import pytest
-from django.urls import reverse
-
-from ..models import Condition
 
-from .test_viewset_condition import export_formats
+from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
-from .test_viewset_condition import urlnames
+from ..models import Condition
+from .test_viewset_condition import export_formats, urlnames
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -68,7 +65,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'source': instance.source.pk,
             'relation': instance.relation,
@@ -90,7 +87,7 @@ def test_create_optionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -117,7 +114,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -144,7 +141,7 @@ def test_create_questionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -171,7 +168,7 @@ def test_create_question(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -198,7 +195,7 @@ def test_create_task(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
diff --git a/rdmo/conditions/tests/test_viewset_relation.py b/rdmo/conditions/tests/test_viewset_relation.py
index 7ff7baefcf..d569367481 100644
--- a/rdmo/conditions/tests/test_viewset_relation.py
+++ b/rdmo/conditions/tests/test_viewset_relation.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 users = (
diff --git a/rdmo/core/tests/__init__.py b/rdmo/core/tests/__init__.py
index a71a08afcb..943cba0608 100644
--- a/rdmo/core/tests/__init__.py
+++ b/rdmo/core/tests/__init__.py
@@ -125,8 +125,8 @@ def get_obj_perms_status_code(instance, username, method):
         try:
             if not instance.editors.exists():
                 return multisite_status_map[method][username]
-        except AttributeError as exc:
-            raise AttributeError(f'instance {instance} should have an editors attribute') from exc
+        except AttributeError as e:
+            raise AttributeError(f'instance {instance} should have an editors attribute') from e
     elif isinstance(instance, str):
         pass
 
@@ -139,8 +139,8 @@ def get_obj_perms_status_code(instance, username, method):
 
     try:
         method_instance_obj_perms_map = status_map_object_permissions[method][instance_obj_perms_key]
-    except KeyError as exc:
-        raise KeyError('instance (%s) should be defined in status_map_object_permissions' % instance_obj_perms_key) from exc
+    except KeyError as e:
+        raise KeyError(f'instance ({instance_obj_perms_key}) should be defined in status_map_object_permissions') from e
     try:
         return method_instance_obj_perms_map[username]
     except KeyError:
diff --git a/rdmo/core/tests/test_models.py b/rdmo/core/tests/test_models.py
index 8ace9596ba..11576a6837 100644
--- a/rdmo/core/tests/test_models.py
+++ b/rdmo/core/tests/test_models.py
@@ -2,7 +2,6 @@
 
 from rdmo.core.models import TranslationMixin
 
-
 boolean_toggle = (True, False)
 test_languages = ('en', 'de')
 
diff --git a/rdmo/core/tests/test_project_status.py b/rdmo/core/tests/test_project_status.py
index 923e47e6ba..a05afcab75 100644
--- a/rdmo/core/tests/test_project_status.py
+++ b/rdmo/core/tests/test_project_status.py
@@ -1,4 +1,3 @@
-import pytest
 
 from django.core.management import call_command
 
diff --git a/rdmo/core/tests/test_tags.py b/rdmo/core/tests/test_tags.py
index b354d46f05..0aa74a73fa 100644
--- a/rdmo/core/tests/test_tags.py
+++ b/rdmo/core/tests/test_tags.py
@@ -19,6 +19,6 @@ def test_i18n_switcher(rf):
     rendered_template = Template(template).render(context)
     for language in settings.LANGUAGES:
         if language == settings.LANGUAGES[0]:
-            assert '<a href="/i18n/%s/"><u>%s</u></a>' % language in rendered_template
+            assert '<a href="/i18n/{}/"><u>{}</u></a>'.format(*language) in rendered_template
         else:
-            assert'<a href="/i18n/%s/">%s</a>' % language in rendered_template
+            assert'<a href="/i18n/{}/">{}</a>'.format(*language) in rendered_template
diff --git a/rdmo/core/tests/test_validators.py b/rdmo/core/tests/test_validators.py
index 005a7963f5..8a5e9637f2 100644
--- a/rdmo/core/tests/test_validators.py
+++ b/rdmo/core/tests/test_validators.py
@@ -1,5 +1,7 @@
 import pytest
+
 from django.core.exceptions import ValidationError
+
 from rest_framework import serializers
 
 from ..validators import InstanceValidator
diff --git a/rdmo/core/tests/test_views.py b/rdmo/core/tests/test_views.py
index f15157ef87..77869e9526 100644
--- a/rdmo/core/tests/test_views.py
+++ b/rdmo/core/tests/test_views.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 users = (
diff --git a/rdmo/core/tests/test_viewset_settings.py b/rdmo/core/tests/test_viewset_settings.py
index 6b015d869a..f962cda1a7 100644
--- a/rdmo/core/tests/test_viewset_settings.py
+++ b/rdmo/core/tests/test_viewset_settings.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 users = (
diff --git a/rdmo/core/tests/test_xml.py b/rdmo/core/tests/test_xml.py
index 640454a8a9..9f7c2c4519 100644
--- a/rdmo/core/tests/test_xml.py
+++ b/rdmo/core/tests/test_xml.py
@@ -1,86 +1,31 @@
-# import os
-# import xml.etree.ElementTree as ET
+from pathlib import Path
 
-# from ..imports import read_xml_file
-# from ..xml import (flat_xml_to_dictlist, get_ns_map, get_ns_tag,
-#                    get_text_or_attribute, get_uri, node_type_from_dictlist,
-#                    sort_dictlist_by_key)
+from ..xml import flat_xml_to_elements, get_ns_map, get_ns_tag, get_uri, read_xml_file
 
 
-# # tests
-# def test_get_ns_map():
-#     xmls = read_all_test_xmls()
-#     for key in xmls:
-#         assert str(get_ns_map(xmls[key])) == "{'dc': 'http://purl.org/dc/elements/1.1/'}"
+def test_get_ns_map(settings):
+    xml_path = Path(settings.BASE_DIR) / 'xml/elements/attributes.xml'
+    root = read_xml_file(xml_path)
+    assert get_ns_map(root) == {'dc': 'http://purl.org/dc/elements/1.1/'}
 
 
-# def test_get_ns_tag():
-#     root = read_test_xml('domain')
-#     nsmap = get_ns_map(root)
-#     assert get_ns_tag('dc:uri', nsmap) == "{http://purl.org/dc/elements/1.1/}uri"
+def test_get_ns_tag(settings):
+    xml_path = Path(settings.BASE_DIR) / 'xml/elements/attributes.xml'
+    root = read_xml_file(xml_path)
+    nsmap = get_ns_map(root)
+    assert get_ns_tag('dc:uri', nsmap) == "{http://purl.org/dc/elements/1.1/}uri"
 
 
-# def test_get_text_or_attribute():
-#     root = read_test_xml('domain')
-#     el = root.find('attribute')
-#     assert get_text_or_attribute(el, 'key') == 'set'
-#     assert get_text_or_attribute(el, 'does_not_exist') is None
+def test_get_uri(settings):
+    xml_path = Path(settings.BASE_DIR) / 'xml/elements/attributes.xml'
+    root = read_xml_file(xml_path)
+    el = root.find('attribute')
+    nsmap = get_ns_map(root)
+    assert get_uri(el, nsmap) == 'http://example.com/terms/domain/blocks'
 
 
-# def test_get_uri():
-#     root = read_test_xml('domain')
-#     el = root.find('attribute')
-#     nsmap = get_ns_map(root)
-#     assert get_uri(el, nsmap) == 'http://example.com/terms/domain/set'
-
-
-# def test_etree_to_dict():
-#     root = read_test_xml('domain')
-#     dictlist = flat_xml_to_dictlist(root)
-#     assert dictlist[0]['uri'] == 'http://example.com/terms/domain/conditions'
-#     assert dictlist[1]['parent'] == 'http://example.com/terms/domain/conditions'
-
-#     root = read_test_xml('conditions')
-#     dictlist = flat_xml_to_dictlist(root)
-#     assert dictlist[0]['uri'] == 'http://example.com/terms/conditions/options_empty'
-
-#     root = read_test_xml('options')
-#     dictlist = flat_xml_to_dictlist(root)
-#     assert dictlist[0]['uri'] == 'http://example.com/terms/options/one_two_three'
-
-#     root = read_test_xml('tasks')
-#     dictlist = flat_xml_to_dictlist(root)
-#     assert dictlist[0]['uri'] == 'http://example.com/terms/tasks/options_contains_one'
-
-#     root = read_test_xml('questions')
-#     dictlist = flat_xml_to_dictlist(root)
-#     assert dictlist[0]['uri'] == 'http://example.com/terms/questions/catalog'
-
-#     root = read_test_xml('views')
-#     dictlist = flat_xml_to_dictlist(root)
-#     assert dictlist[0]['uri'] == 'http://example.com/terms/views/view_a'
-
-
-# def test_node_type_from_dictlist():
-#     root = read_test_xml('options')
-#     dictlist = flat_xml_to_dictlist(root)
-#     optionsets = node_type_from_dictlist(dictlist, 'optionset')
-#     assert optionsets[0]['node_type'] == 'optionset'
-
-
-# # test utils
-# def read_all_test_xmls():
-#     xml = {}
-#     xml['domain'] = read_test_xml('domain')
-#     xml['conditions'] = read_test_xml('conditions')
-#     xml['options'] = read_test_xml('options')
-#     xml['project'] = read_test_xml('project')
-#     xml['questions'] = read_test_xml('questions')
-#     xml['tasks'] = read_test_xml('tasks')
-#     xml['views'] = read_test_xml('views')
-#     return xml
-
-
-# def read_test_xml(filename):
-#     xml_folder = os.getcwd().split('rdmo')[0] + 'rdmo/testing/xml/'
-#     return read_xml_file(xml_folder + filename + '.xml')
+def test_flat_xml_to_elements(settings):
+    xml_path = Path(settings.BASE_DIR) / 'xml/elements/attributes.xml'
+    root = read_xml_file(xml_path)
+    elements = flat_xml_to_elements(root)
+    assert elements['http://example.com/terms/domain/blocks']['uri'] == 'http://example.com/terms/domain/blocks'
diff --git a/rdmo/domain/tests/test_validator_locked.py b/rdmo/domain/tests/test_validator_locked.py
index 90741c8e0e..b3f5f35247 100644
--- a/rdmo/domain/tests/test_validator_locked.py
+++ b/rdmo/domain/tests/test_validator_locked.py
@@ -1,8 +1,8 @@
 import pytest
-from django.conf import settings
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Attribute
 from ..serializers.v1 import AttributeSerializer
diff --git a/rdmo/domain/tests/test_validator_parent.py b/rdmo/domain/tests/test_validator_parent.py
index 501fd5606d..30baee9dbe 100644
--- a/rdmo/domain/tests/test_validator_parent.py
+++ b/rdmo/domain/tests/test_validator_parent.py
@@ -1,8 +1,9 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Attribute
 from ..serializers.v1 import AttributeSerializer
@@ -37,7 +38,7 @@ def test_update_error(db):
 
 
 def test_serializer_create(db):
-    class MockedView(object):
+    class MockedView:
         action = 'create'
 
     validator = AttributeParentValidator()
diff --git a/rdmo/domain/tests/test_validator_unique_uri.py b/rdmo/domain/tests/test_validator_unique_uri.py
index fb41240567..a92f3c2fed 100644
--- a/rdmo/domain/tests/test_validator_unique_uri.py
+++ b/rdmo/domain/tests/test_validator_unique_uri.py
@@ -1,8 +1,9 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Attribute
 from ..serializers.v1 import AttributeSerializer
diff --git a/rdmo/domain/tests/test_viewset_attribute.py b/rdmo/domain/tests/test_viewset_attribute.py
index e1607df934..09a1b2d27f 100644
--- a/rdmo/domain/tests/test_viewset_attribute.py
+++ b/rdmo/domain/tests/test_viewset_attribute.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import Attribute
@@ -98,7 +99,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'key': f'{instance.key}_new_{username}',
             'comment': '',
             'parent': instance.parent.pk if instance.parent else ''
         }
@@ -117,7 +118,7 @@ def test_create_condition(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'key': f'{instance.key}_new_{username}',
                 'comment': '',
                 'parent': instance.parent.pk if instance.parent else '',
                 'conditions': [condition.id]
@@ -141,7 +142,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'key': f'{instance.key}_new_{username}',
                 'comment': '',
                 'parent': instance.parent.pk if instance.parent else '',
                 'pages': [page.id]
@@ -165,7 +166,7 @@ def test_create_questionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'key': f'{instance.key}_new_{username}',
                 'comment': '',
                 'parent': instance.parent.pk if instance.parent else '',
                 'questionsets': [questionset.id]
@@ -189,7 +190,7 @@ def test_create_question(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'key': f'{instance.key}_new_{username}',
                 'comment': '',
                 'parent': instance.parent.pk if instance.parent else '',
                 'questions': [question.id]
diff --git a/rdmo/domain/tests/test_viewset_attribute_multisite.py b/rdmo/domain/tests/test_viewset_attribute_multisite.py
index aa8cd26e9e..674469a694 100644
--- a/rdmo/domain/tests/test_viewset_attribute_multisite.py
+++ b/rdmo/domain/tests/test_viewset_attribute_multisite.py
@@ -4,13 +4,10 @@
 
 from django.urls import reverse
 
-from ..models import Attribute
-
-
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
+from ..models import Attribute
 from .test_viewset_attribute import urlnames
 
 
@@ -73,7 +70,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'key': f'{instance.key}_new_{username}',
             'comment': '',
             'parent': instance.parent.pk if instance.parent else ''
         }
diff --git a/rdmo/management/tests/test_commands.py b/rdmo/management/tests/test_commands.py
index 22d10d7688..b9b252d776 100644
--- a/rdmo/management/tests/test_commands.py
+++ b/rdmo/management/tests/test_commands.py
@@ -1,9 +1,8 @@
-import pytest
-
 import io
-
 from pathlib import Path
 
+import pytest
+
 from django.core.management import call_command
 from django.core.management.base import CommandError
 
diff --git a/rdmo/management/tests/test_import_conditions.py b/rdmo/management/tests/test_import_conditions.py
index b19ad8e70c..4c97ced3fd 100644
--- a/rdmo/management/tests/test_import_conditions.py
+++ b/rdmo/management/tests/test_import_conditions.py
@@ -1,8 +1,7 @@
 from pathlib import Path
 
 from rdmo.conditions.models import Condition
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.management.imports import import_elements
 
 
@@ -20,8 +19,8 @@ def test_create_conditions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Condition.objects.count() == 15
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_conditions(db, settings):
@@ -36,8 +35,8 @@ def test_update_conditions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements)
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_legacy_conditions(db, settings):
@@ -54,8 +53,8 @@ def test_create_legacy_conditions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Condition.objects.count() == 15
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_legacy_conditions(db, settings):
@@ -70,5 +69,5 @@ def test_update_legacy_conditions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 15
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
diff --git a/rdmo/management/tests/test_import_domain.py b/rdmo/management/tests/test_import_domain.py
index 33ae0d53c7..b9f032b92f 100644
--- a/rdmo/management/tests/test_import_domain.py
+++ b/rdmo/management/tests/test_import_domain.py
@@ -1,7 +1,6 @@
 from pathlib import Path
 
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.domain.models import Attribute
 from rdmo.management.imports import import_elements
 
@@ -20,8 +19,8 @@ def test_create_domain(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Attribute.objects.count() == 86
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_domain(db, settings):
@@ -36,8 +35,8 @@ def test_update_domain(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements)
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_legacy_domain(db, settings):
@@ -55,8 +54,8 @@ def test_create_legacy_domain(db, settings):
 
     assert len(root) == len(elements) == 86
     assert Attribute.objects.count() == 86
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_legacy_domain(db, settings):
@@ -71,5 +70,5 @@ def test_update_legacy_domain(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 86
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
diff --git a/rdmo/management/tests/test_import_options.py b/rdmo/management/tests/test_import_options.py
index 45eb499f96..8d98989901 100644
--- a/rdmo/management/tests/test_import_options.py
+++ b/rdmo/management/tests/test_import_options.py
@@ -1,7 +1,6 @@
 from pathlib import Path
 
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.management.imports import import_elements
 from rdmo.options.models import Option, OptionSet
 
@@ -22,8 +21,8 @@ def test_create_optionsets(db, settings):
     assert len(root) == len(elements) == 12
     assert OptionSet.objects.count() == 4
     assert Option.objects.count() == 8
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_optionsets(db, settings):
@@ -37,8 +36,8 @@ def test_update_optionsets(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 12
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_options(db, settings):
@@ -54,8 +53,8 @@ def test_create_options(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Option.objects.count() == 8
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_options(db, settings):
@@ -69,8 +68,8 @@ def test_update_options(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 8
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_legacy_options(db, settings):
@@ -90,8 +89,8 @@ def test_create_legacy_options(db, settings):
     assert len(root) == len(elements) == 12
     assert OptionSet.objects.count() == 4
     assert Option.objects.count() == 8
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_legacy_options(db, settings):
@@ -106,5 +105,5 @@ def test_update_legacy_options(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 12
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
diff --git a/rdmo/management/tests/test_import_questions.py b/rdmo/management/tests/test_import_questions.py
index c52db2924c..fa79bdf28b 100644
--- a/rdmo/management/tests/test_import_questions.py
+++ b/rdmo/management/tests/test_import_questions.py
@@ -1,7 +1,6 @@
 from pathlib import Path
 
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.management.imports import import_elements
 from rdmo.questions.models import Catalog, Page, Question, QuestionSet, Section
 
@@ -29,8 +28,8 @@ def test_create_catalogs(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_catalogs(db, settings):
@@ -45,8 +44,8 @@ def test_update_catalogs(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 148
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_sections(db, settings):
@@ -70,8 +69,8 @@ def test_create_sections(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_sections(db, settings):
@@ -86,8 +85,8 @@ def test_update_sections(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 146
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_pages(db, settings):
@@ -109,8 +108,8 @@ def test_create_pages(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_pages(db, settings):
@@ -125,8 +124,8 @@ def test_update_pages(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 140
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_questionsets(db, settings):
@@ -148,8 +147,8 @@ def test_create_questionsets(db, settings):
     assert len(elements) == 8
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 5
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_questionsets(db, settings):
@@ -164,8 +163,8 @@ def test_update_questionsets(db, settings):
     import_elements(elements)
 
     assert len(root) == 10  # two questionsets apear twice in the export file
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_questions(db, settings):
@@ -185,8 +184,8 @@ def test_create_questions(db, settings):
 
     assert len(root) == len(elements) == 89
     assert Question.objects.count() == 89
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_questions(db, settings):
@@ -201,8 +200,8 @@ def test_update_questions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 89
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_legacy_questions(db, settings):
@@ -228,13 +227,13 @@ def test_create_legacy_questions(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
     # check that all elements ended up in the catalog
     catalog = Catalog.objects.prefetch_elements().first()
-    descendant_uris = set(element.uri for element in catalog.descendants)
-    element_uris = set(element['uri'] for element in elements if element['uri'] != catalog.uri)
+    descendant_uris = {element.uri for element in catalog.descendants}
+    element_uris = {element['uri'] for element in elements if element['uri'] != catalog.uri}
     assert descendant_uris == element_uris
 
 
@@ -250,11 +249,11 @@ def test_update_legacy_questions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 147
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
     # check that all elements ended up in the catalog
     catalog = Catalog.objects.prefetch_elements().first()
-    descendant_uris = set(element.uri for element in catalog.descendants)
-    element_uris = set(element['uri'] for element in elements if element['uri'] != catalog.uri)
+    descendant_uris = {element.uri for element in catalog.descendants}
+    element_uris = {element['uri'] for element in elements if element['uri'] != catalog.uri}
     assert descendant_uris == element_uris
diff --git a/rdmo/management/tests/test_import_tasks.py b/rdmo/management/tests/test_import_tasks.py
index 6f80c35d78..08071990cf 100644
--- a/rdmo/management/tests/test_import_tasks.py
+++ b/rdmo/management/tests/test_import_tasks.py
@@ -1,7 +1,6 @@
 from pathlib import Path
 
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.management.imports import import_elements
 from rdmo.tasks.models import Task
 
@@ -20,8 +19,8 @@ def test_create_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Task.objects.count() == 2
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_tasks(db, settings):
@@ -36,8 +35,8 @@ def test_update_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 2
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_legacy_tasks(db, settings):
@@ -54,8 +53,8 @@ def test_create_legacy_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Task.objects.count() == 2
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_legacy_tasks(db, settings):
@@ -70,5 +69,5 @@ def test_update_legacy_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 2
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
diff --git a/rdmo/management/tests/test_import_views.py b/rdmo/management/tests/test_import_views.py
index cec79e5dab..a6c7bc7d06 100644
--- a/rdmo/management/tests/test_import_views.py
+++ b/rdmo/management/tests/test_import_views.py
@@ -1,7 +1,6 @@
 from pathlib import Path
 
-from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
-                           order_elements, read_xml_file)
+from rdmo.core.xml import convert_elements, flat_xml_to_elements, order_elements, read_xml_file
 from rdmo.management.imports import import_elements
 from rdmo.views.models import View
 
@@ -20,8 +19,8 @@ def test_create_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == View.objects.count() == 3
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_tasks(db, settings):
@@ -36,8 +35,8 @@ def test_update_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 3
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
 
 
 def test_create_legacy_tasks(db, settings):
@@ -54,8 +53,8 @@ def test_create_legacy_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == View.objects.count() == 3
-    assert all([element['created'] is True for element in elements])
-    assert all([element['updated'] is False for element in elements])
+    assert all(element['created'] is True for element in elements)
+    assert all(element['updated'] is False for element in elements)
 
 
 def test_update_legacy_tasks(db, settings):
@@ -70,5 +69,5 @@ def test_update_legacy_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 3
-    assert all([element['created'] is False for element in elements])
-    assert all([element['updated'] is True for element in elements])
+    assert all(element['created'] is False for element in elements)
+    assert all(element['updated'] is True for element in elements)
diff --git a/rdmo/management/tests/test_view.py b/rdmo/management/tests/test_view.py
index 46df0ff0fb..8e049f1602 100644
--- a/rdmo/management/tests/test_view.py
+++ b/rdmo/management/tests/test_view.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 users = (
diff --git a/rdmo/management/tests/test_viewset_import_multisite.py b/rdmo/management/tests/test_viewset_import_multisite.py
index 0be8805755..5a0e2f3d35 100644
--- a/rdmo/management/tests/test_viewset_import_multisite.py
+++ b/rdmo/management/tests/test_viewset_import_multisite.py
@@ -2,12 +2,10 @@
 
 from django.urls import reverse
 
-from rdmo.core.tests import multisite_users as users
 from rdmo.core.tests import get_obj_perms_status_code
-
+from rdmo.core.tests import multisite_users as users
 from rdmo.questions.models import Catalog, Page, Question, QuestionSet, Section
 
-
 status_map = {
     'list': {
         'default': 405, 'anonymous': 401
@@ -89,7 +87,7 @@ def test_create_update(db, client, username, password, json_data):
 def test_create_update_certain_catalog(db, client, username, password, catalog_uri_path, json_data):
     client.login(username=username, password=password)
 
-    instance_json = [i for i in json_data['elements'] if i['uri_path'] == catalog_uri_path][0]
+    instance_json = next(i for i in json_data['elements'] if i['uri_path'] == catalog_uri_path)
     instance_json['title_en'] += ' (updated)'
     instance_json['title_de'] += ' (updated)'
     instance_data = {'elements': [instance_json]}
@@ -114,7 +112,8 @@ def test_create_empty(db, client, username, password):
 
     url = reverse(urlnames['list'])
     response = client.post(url, {}, content_type='application/json')
-    assert response.status_code == status_map['create_error'].get(username, status_map['create_error']['default']), response.json()
+    assert response.status_code == status_map['create_error'].get(username, status_map['create_error']['default']), \
+           response.json()
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -125,4 +124,5 @@ def test_create_error(db, client, username, password):
 
     url = reverse(urlnames['list'])
     response = client.post(url, json_data, content_type='application/json')
-    assert response.status_code == status_map['create_error'].get(username, status_map['create_error']['default']), response.json()
+    assert response.status_code == status_map['create_error'].get(username, status_map['create_error']['default']), \
+           response.json()
diff --git a/rdmo/management/tests/test_viewset_upload.py b/rdmo/management/tests/test_viewset_upload.py
index 3f8ccb44f3..3715a44e0f 100644
--- a/rdmo/management/tests/test_viewset_upload.py
+++ b/rdmo/management/tests/test_viewset_upload.py
@@ -1,6 +1,7 @@
 from pathlib import Path
 
 import pytest
+
 from django.conf import settings
 from django.urls import reverse
 
diff --git a/rdmo/options/tests/test_validator_locked_options.py b/rdmo/options/tests/test_validator_locked_options.py
index 7ba31764e8..4af2ecacba 100644
--- a/rdmo/options/tests/test_validator_locked_options.py
+++ b/rdmo/options/tests/test_validator_locked_options.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Option, OptionSet
 from ..serializers.v1 import OptionSerializer
diff --git a/rdmo/options/tests/test_validator_locked_optionsets.py b/rdmo/options/tests/test_validator_locked_optionsets.py
index f9ca4660da..511b2b733e 100644
--- a/rdmo/options/tests/test_validator_locked_optionsets.py
+++ b/rdmo/options/tests/test_validator_locked_optionsets.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import OptionSet
 from ..serializers.v1 import OptionSetSerializer
diff --git a/rdmo/options/tests/test_validator_unique_uri_options.py b/rdmo/options/tests/test_validator_unique_uri_options.py
index cd807cd620..b29ec193c3 100644
--- a/rdmo/options/tests/test_validator_unique_uri_options.py
+++ b/rdmo/options/tests/test_validator_unique_uri_options.py
@@ -1,8 +1,9 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Option, OptionSet
 from ..serializers.v1 import OptionSerializer
diff --git a/rdmo/options/tests/test_validator_unique_uri_optionsets.py b/rdmo/options/tests/test_validator_unique_uri_optionsets.py
index f7ef3ac7b2..bf77068096 100644
--- a/rdmo/options/tests/test_validator_unique_uri_optionsets.py
+++ b/rdmo/options/tests/test_validator_unique_uri_optionsets.py
@@ -1,10 +1,11 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
 
-from ..models import OptionSet, Option
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
+
+from ..models import Option, OptionSet
 from ..serializers.v1 import OptionSetSerializer
 from ..validators import OptionSetUniqueURIValidator
 
diff --git a/rdmo/options/tests/test_viewset_options.py b/rdmo/options/tests/test_viewset_options.py
index 833b68b084..47542460cd 100644
--- a/rdmo/options/tests/test_viewset_options.py
+++ b/rdmo/options/tests/test_viewset_options.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
@@ -98,7 +99,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'text_en': instance.text_lang1,
             'text_de': instance.text_lang2
@@ -121,7 +122,7 @@ def test_create_optionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'text_en': instance.text_lang1,
                 'text_de': instance.text_lang2,
@@ -133,7 +134,7 @@ def test_create_optionset(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Option.objects.get(id=response.json().get('id'))
                 optionset.refresh_from_db()
-                assert catalog_sections + [(new_instance.id, order)] == \
+                assert [*catalog_sections, (new_instance.id, order)] == \
                     list(optionset.optionset_options.values_list('option', 'order'))
 
 
diff --git a/rdmo/options/tests/test_viewset_options_multisite.py b/rdmo/options/tests/test_viewset_options_multisite.py
index bae8a3aba9..9062de7f74 100644
--- a/rdmo/options/tests/test_viewset_options_multisite.py
+++ b/rdmo/options/tests/test_viewset_options_multisite.py
@@ -1,14 +1,13 @@
 import xml.etree.ElementTree as et
 
 import pytest
-from django.urls import reverse
 
-from ..models import Option
+from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
+from ..models import Option
 from .test_viewset_options import urlnames
 
 
@@ -65,7 +64,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'text_en': instance.text_lang1,
             'text_de': instance.text_lang2
diff --git a/rdmo/options/tests/test_viewset_optionsets.py b/rdmo/options/tests/test_viewset_optionsets.py
index 881d35d9d8..8ad51c009b 100644
--- a/rdmo/options/tests/test_viewset_optionsets.py
+++ b/rdmo/options/tests/test_viewset_optionsets.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import OptionSet
@@ -109,7 +110,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'order': instance.order
         }
@@ -128,7 +129,7 @@ def test_create_question(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'order': instance.order,
                 'questions': [question.id]
@@ -156,7 +157,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'order': instance.order,
             'options': optionset_options,
diff --git a/rdmo/options/tests/test_viewset_optionsets_multisite.py b/rdmo/options/tests/test_viewset_optionsets_multisite.py
index 483b8e3005..2c74bd8e79 100644
--- a/rdmo/options/tests/test_viewset_optionsets_multisite.py
+++ b/rdmo/options/tests/test_viewset_optionsets_multisite.py
@@ -1,16 +1,13 @@
 import xml.etree.ElementTree as et
 
 import pytest
-from django.contrib.sites.models import Site
-from django.urls import reverse
 
-from ..models import OptionSet
+from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
-from .test_viewset_optionsets import export_formats
+from ..models import OptionSet
 from .test_viewset_optionsets import urlnames
 
 
@@ -78,7 +75,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'order': instance.order,
             'conditions': [condition.pk for condition in instance.conditions.all()],
diff --git a/rdmo/overlays/tests/test_views.py b/rdmo/overlays/tests/test_views.py
index 8d3a26a7ae..8f6fa8aa0e 100644
--- a/rdmo/overlays/tests/test_views.py
+++ b/rdmo/overlays/tests/test_views.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Overlay
diff --git a/rdmo/overlays/tests/test_viewsets.py b/rdmo/overlays/tests/test_viewsets.py
index f875a66d6d..931a71944a 100644
--- a/rdmo/overlays/tests/test_viewsets.py
+++ b/rdmo/overlays/tests/test_viewsets.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Overlay
diff --git a/rdmo/projects/tests/test_commands.py b/rdmo/projects/tests/test_commands.py
index f698364f91..cdb0cd3450 100644
--- a/rdmo/projects/tests/test_commands.py
+++ b/rdmo/projects/tests/test_commands.py
@@ -1,6 +1,7 @@
 import io
 
 import pytest
+
 from django.core.management import call_command
 from django.core.management.base import CommandError
 
@@ -18,7 +19,7 @@
 def get_prune_output(projects, remove=False):
     project_output = ""
     for proj in projects:
-        project_output += '%s (id=%s)\n' % (proj.title, proj.id)
+        project_output += f'{proj.title} (id={proj.id})\n'
         if remove:
             project_output += "...removing...OK\n"
     return project_output
@@ -36,12 +37,12 @@ def test_prune_projects_error(db, settings):
 @pytest.mark.parametrize('min_role,role_list,projects', prune_assignments)
 def test_prune_projects_output(db, settings, min_role, role_list, projects):
     stdout, stderr = io.StringIO(), io.StringIO()
-    
+
     instances = Project.objects.filter(id__in=projects).all()
     call_command('prune_projects', '--min_role', min_role, stdout=stdout, stderr=stderr)
 
     assert stdout.getvalue() == \
-        "Found projects without %s:\n%s" % (role_list, get_prune_output(instances))
+        f"Found projects without {role_list}:\n{get_prune_output(instances)}"
     assert not stderr.getvalue()
 
 
diff --git a/rdmo/projects/tests/test_handlers.py b/rdmo/projects/tests/test_handlers.py
index 33ea718603..83a9c3c3c8 100644
--- a/rdmo/projects/tests/test_handlers.py
+++ b/rdmo/projects/tests/test_handlers.py
@@ -1,4 +1,5 @@
 import itertools
+
 import pytest
 
 from django.contrib.auth.models import Group
@@ -49,9 +50,9 @@ def test_update_projects(db, view_id, sites, catalogs, groups, project_id, proje
     view.catalogs.set(Catalog.objects.filter(pk__in=catalogs))
     view.groups.set(Group.objects.filter(pk__in=groups))
 
-    assert sorted(list(itertools.chain.from_iterable(view.sites.all().values_list('pk')))) == sites
-    assert sorted(list(itertools.chain.from_iterable(view.catalogs.all().values_list('pk')))) == catalogs
-    assert sorted(list(itertools.chain.from_iterable(view.groups.all().values_list('pk')))) == groups
+    assert sorted(itertools.chain.from_iterable(view.sites.all().values_list('pk'))) == sites
+    assert sorted(itertools.chain.from_iterable(view.catalogs.all().values_list('pk'))) == catalogs
+    assert sorted(itertools.chain.from_iterable(view.groups.all().values_list('pk'))) == groups
 
     if not project_exists:
         with pytest.raises(Project.DoesNotExist):
diff --git a/rdmo/projects/tests/test_utils.py b/rdmo/projects/tests/test_utils.py
index 86f3d99dce..edbc5a8d3b 100644
--- a/rdmo/projects/tests/test_utils.py
+++ b/rdmo/projects/tests/test_utils.py
@@ -2,8 +2,8 @@
 
 from django.http import QueryDict
 
-from ..utils import set_context_querystring_with_filter_and_page
 from ..filters import ProjectFilter
+from ..utils import set_context_querystring_with_filter_and_page
 
 GET_queries = [
     'page=2&title=project',
diff --git a/rdmo/projects/tests/test_view_integration.py b/rdmo/projects/tests/test_view_integration.py
index d5df46746c..fe626862c0 100644
--- a/rdmo/projects/tests/test_view_integration.py
+++ b/rdmo/projects/tests/test_view_integration.py
@@ -2,6 +2,7 @@
 import json
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import Integration, Issue
@@ -122,7 +123,8 @@ def test_integration_update_post(db, client, username, password, project_id, int
     if integration:
         if project_id in change_integration_permission_map.get(username, []):
             assert response.status_code == 302
-            values = Integration.objects.filter(project_id=project_id, id=integration_id).first().options.values('key', 'value', 'secret')
+            values = Integration.objects.filter(project_id=project_id, id=integration_id).first() \
+                                        .options.values('key', 'value', 'secret')
             assert sorted(values, key=lambda obj: obj['key']) == [
                 {
                     'key': 'repo',
diff --git a/rdmo/projects/tests/test_view_issue.py b/rdmo/projects/tests/test_view_issue.py
index ead06d1473..5404ca277e 100644
--- a/rdmo/projects/tests/test_view_issue.py
+++ b/rdmo/projects/tests/test_view_issue.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.core import mail
 from django.urls import reverse
 
diff --git a/rdmo/projects/tests/test_view_membership.py b/rdmo/projects/tests/test_view_membership.py
index e9e38a0175..4eef8c5c98 100644
--- a/rdmo/projects/tests/test_view_membership.py
+++ b/rdmo/projects/tests/test_view_membership.py
@@ -97,10 +97,12 @@ def test_membership_create_post_mail(db, client, settings,
     if project_id in add_membership_permission_map.get(username, []):
         if not PROJECT_SEND_INVITE:
             assert response.status_code == 200
-            assert not Invite.objects.filter(project_id=project_id, user=None, email='someuser@example.com', role=membership_role)
+            assert not Invite.objects.filter(project_id=project_id, user=None,
+                                             email='someuser@example.com', role=membership_role)
         else:
             assert response.status_code == 302
-            assert Invite.objects.get(project_id=project_id, user=None, email='someuser@example.com', role=membership_role)
+            assert Invite.objects.get(project_id=project_id, user=None,
+                                      email='someuser@example.com', role=membership_role)
         assert len(mail.outbox) == int(PROJECT_SEND_INVITE)
     else:
         if password:
@@ -184,8 +186,10 @@ def test_membership_create_post_silent(db, client, username, password, membershi
             assert Membership.objects.get(project_id=project_id, user__username='user', role=membership_role)
             assert len(mail.outbox) == 0
         else:
-            assert Invite.objects.get(project_id=project_id, user__username='user', email='user@example.com', role=membership_role)
-            assert not Membership.objects.filter(project_id=project_id, user__username='user', role=membership_role).exists()
+            assert Invite.objects.get(project_id=project_id, user__username='user',
+                                      email='user@example.com', role=membership_role)
+            assert not Membership.objects.filter(project_id=project_id, user__username='user',
+                                                 role=membership_role).exists()
             assert len(mail.outbox) == 1
 
     else:
diff --git a/rdmo/projects/tests/test_view_membership_multisite.py b/rdmo/projects/tests/test_view_membership_multisite.py
index 7107a9344b..6bf58c3af4 100644
--- a/rdmo/projects/tests/test_view_membership_multisite.py
+++ b/rdmo/projects/tests/test_view_membership_multisite.py
@@ -7,6 +7,7 @@
 
 from ..models import Invite, Project
 from ..utils import get_invite_email_project_path
+
 users = (
     ('owner', 'owner'),
     ('manager', 'manager'),
@@ -30,22 +31,26 @@
 
 sites_domains = ('example.com', 'foo.com', 'bar.com')
 
+
 @pytest.fixture()
-def multisite_setting(settings):
+def multisite(settings):
     settings.MULTISITE = True
 
+
 @pytest.mark.parametrize('username,password', users)
 @pytest.mark.parametrize('project_id', projects)
 @pytest.mark.parametrize('membership_role', membership_roles)
 @pytest.mark.parametrize('site_domain', sites_domains)
-def test_get_invite_email_project_path_function(db, client, username, password, project_id, membership_role, site_domain, multisite_setting):
+def test_get_invite_email_project_path_function(db, client, username, password, project_id,
+                                                membership_role, site_domain, multisite):
     client.login(username=username, password=password)
 
     current_site = Site.objects.get_current()
     foo_site, _created = Site.objects.get_or_create(domain=site_domain, name=site_domain)
     foo_username = f'{site_domain}-test-user'
     foo_email = f'{foo_username}@{site_domain}'
-    foo_user, _created = get_user_model().objects.get_or_create(username=foo_username, email=foo_email, password=foo_username)
+    foo_user, _created = get_user_model().objects.get_or_create(username=foo_username, email=foo_email,
+                                                                password=foo_username)
     foo_user.role.member.set([foo_site])
     project = Project.objects.get(pk=project_id)
 
@@ -59,21 +64,24 @@ def test_get_invite_email_project_path_function(db, client, username, password,
     else:
         assert invite_email_project_path.startswith('http://' + site_domain + '/projects')
 
+
 @pytest.mark.parametrize('username,password', users)
 @pytest.mark.parametrize('project_id', projects)
 @pytest.mark.parametrize('membership_role', membership_roles)
 @pytest.mark.parametrize('site_domain', sites_domains)
-def test_invite_email_project_path_email_body(db, client, username, password, project_id, membership_role, site_domain, multisite_setting):
+def test_invite_email_project_path_email_body(db, client, username, password, project_id,
+                                              membership_role, site_domain, multisite):
     client.login(username=username, password=password)
 
     current_site = Site.objects.get_current()
     foo_site, _created = Site.objects.get_or_create(domain=site_domain, name=site_domain)
     foo_username = f'{site_domain}-user'
     foo_email = f'{foo_username}@{site_domain}'
-    foo_user, _created = get_user_model().objects.get_or_create(username=f'{site_domain}-user', email=foo_email, password=foo_username)
+    foo_user, _created = get_user_model().objects.get_or_create(username=f'{site_domain}-user', email=foo_email,
+                                                                password=foo_username)
     foo_user.role.member.set([foo_site])
     project = Project.objects.get(pk=project_id)
-    
+
     url = reverse('membership_create', args=[project_id])
     data = {
         'username_or_email': foo_email,
diff --git a/rdmo/projects/tests/test_view_project.py b/rdmo/projects/tests/test_view_project.py
index ee04828a70..7d63410f9c 100644
--- a/rdmo/projects/tests/test_view_project.py
+++ b/rdmo/projects/tests/test_view_project.py
@@ -1,9 +1,11 @@
 import re
 
 import pytest
-from pytest_django.asserts import assertContains, assertNotContains, assertTemplateUsed
+
 from django.urls import reverse
 
+from pytest_django.asserts import assertContains, assertNotContains, assertTemplateUsed
+
 from rdmo.questions.models import Catalog
 from rdmo.views.models import View
 
@@ -80,7 +82,7 @@ def test_list(db, client, username, password):
             assertContains(response, 'View all projects on')
         else:
             user_projects_map = view_project_permission_map.get(username, [])
-            assert sorted(list(set(map(int, projects)))) == user_projects_map
+            assert sorted(set(map(int, projects))) == user_projects_map
             assert response.context['number_of_projects'] == len(user_projects_map)
             assertNotContains(response, 'View all projects on')
     else:
@@ -101,7 +103,7 @@ def test_site(db, client, username, password):
             assert response.status_code == 200
             assertTemplateUsed(response, 'projects/site_projects.html')
             user_projects_map = view_project_permission_map.get(username, [])
-            assert sorted(list(set(map(int, projects)))) == user_projects_map
+            assert sorted(set(map(int, projects))) == user_projects_map
             assert response.context['number_of_projects'] == len(user_projects_map)
         else:
             assert response.status_code == 403
diff --git a/rdmo/projects/tests/test_view_project_create_import.py b/rdmo/projects/tests/test_view_project_create_import.py
index 068832068b..a1c3880719 100644
--- a/rdmo/projects/tests/test_view_project_create_import.py
+++ b/rdmo/projects/tests/test_view_project_create_import.py
@@ -3,6 +3,7 @@
 from pathlib import Path
 
 import pytest
+
 from django.urls import reverse
 
 from rdmo.core.constants import VALUE_TYPE_FILE
@@ -160,7 +161,7 @@ def test_project_create_import_post_import_file(db, settings, client, files, use
         if password:
             project = Project.objects.order_by('updated').last()
             assert response.status_code == 302
-            assert response.url == '/projects/{}/'.format(project.pk)
+            assert response.url == f'/projects/{project.pk}/'
 
             # a new project, new values values
             assert Project.objects.count() == projects_count + 1
@@ -265,7 +266,7 @@ def test_project_create_import_post_import_empty(db, settings, client, username,
         if password:
             new_project = Project.objects.order_by('updated').last()
             assert response.status_code == 302
-            assert response.url == '/projects/{}/'.format(new_project.id)
+            assert response.url == f'/projects/{new_project.id}/'
 
             # a new project, but no values
             assert Project.objects.count() == projects_count + 1
diff --git a/rdmo/projects/tests/test_view_project_join.py b/rdmo/projects/tests/test_view_project_join.py
index 1c4e03cd74..ecde517d49 100644
--- a/rdmo/projects/tests/test_view_project_join.py
+++ b/rdmo/projects/tests/test_view_project_join.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
diff --git a/rdmo/projects/tests/test_view_project_leave.py b/rdmo/projects/tests/test_view_project_leave.py
index db134daca5..fa0fd55bbd 100644
--- a/rdmo/projects/tests/test_view_project_leave.py
+++ b/rdmo/projects/tests/test_view_project_leave.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Membership
diff --git a/rdmo/projects/tests/test_view_project_update_import.py b/rdmo/projects/tests/test_view_project_update_import.py
index e1358463a2..d2da3816b3 100644
--- a/rdmo/projects/tests/test_view_project_update_import.py
+++ b/rdmo/projects/tests/test_view_project_update_import.py
@@ -3,6 +3,7 @@
 from pathlib import Path
 
 import pytest
+
 from django.urls import reverse
 
 from rdmo.core.constants import VALUE_TYPE_FILE
@@ -206,7 +207,7 @@ def test_project_update_import_post_import_file(db, settings, client, files, use
 
         if project_id in change_project_permission_map.get(username, []):
             assert response.status_code == 302
-            assert response.url == '/projects/{}/'.format(project_id)
+            assert response.url == f'/projects/{project_id}/'
 
         else:
             if password:
@@ -274,7 +275,7 @@ def test_project_update_import_post_import_file_cancel(db, settings, client, fil
         assert project.updated == project_updated
 
         assert response.status_code == 302
-        assert response.url == '/projects/{}/'.format(project_id)
+        assert response.url == f'/projects/{project_id}/'
     elif password:
         assert response.status_code == 403
     else:
@@ -330,7 +331,7 @@ def test_project_update_import_post_import_file_empty(db, settings, client, user
         assert project.updated == project_updated
 
         assert response.status_code == 302
-        assert response.url == '/projects/{}/'.format(project_id)
+        assert response.url == f'/projects/{project_id}/'
     elif password:
         assert response.status_code == 403
     else:
@@ -341,7 +342,8 @@ def test_project_update_import_post_import_file_empty(db, settings, client, user
 @pytest.mark.parametrize('username,password', users)
 @pytest.mark.parametrize('project_id', projects)
 @pytest.mark.parametrize('source_id', projects)
-def test_project_update_import_post_import_project_step1(db, settings, client, username, password, project_id, source_id):
+def test_project_update_import_post_import_project_step1(db, settings, client, username, password,
+                                                         project_id, source_id):
     client.login(username=username, password=password)
 
     url = reverse('project_update_import', args=[project_id])
@@ -366,7 +368,8 @@ def test_project_update_import_post_import_project_step1(db, settings, client, u
 @pytest.mark.parametrize('username,password', users)
 @pytest.mark.parametrize('project_id', projects)
 @pytest.mark.parametrize('source_id', projects)
-def test_project_update_import_post_import_project_step2(db, settings, client, username, password, project_id, source_id):
+def test_project_update_import_post_import_project_step2(db, settings, client, username, password,
+                                                         project_id, source_id):
     client.login(username=username, password=password)
     projects_count = Project.objects.count()
 
@@ -430,7 +433,7 @@ def test_project_update_import_post_import_project_step2(db, settings, client, u
 
             if project_id in change_project_permission_map.get(username, []):
                 assert response.status_code == 302
-                assert response.url == '/projects/{}/'.format(project_id)
+                assert response.url == f'/projects/{project_id}/'
 
             else:
                 if password:
diff --git a/rdmo/projects/tests/test_view_snapshot.py b/rdmo/projects/tests/test_view_snapshot.py
index 2b8a21c00d..a49b161d0f 100644
--- a/rdmo/projects/tests/test_view_snapshot.py
+++ b/rdmo/projects/tests/test_view_snapshot.py
@@ -1,6 +1,7 @@
 from pathlib import Path
 
 import pytest
+
 from django.conf import settings
 from django.urls import reverse
 
diff --git a/rdmo/projects/tests/test_viewset_integration.py b/rdmo/projects/tests/test_viewset_integration.py
index 481d5e91d7..7034498134 100644
--- a/rdmo/projects/tests/test_viewset_integration.py
+++ b/rdmo/projects/tests/test_viewset_integration.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Integration
diff --git a/rdmo/projects/tests/test_viewset_invite.py b/rdmo/projects/tests/test_viewset_invite.py
index ffa24601f8..e95b2c6533 100644
--- a/rdmo/projects/tests/test_viewset_invite.py
+++ b/rdmo/projects/tests/test_viewset_invite.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Invite
diff --git a/rdmo/projects/tests/test_viewset_issue.py b/rdmo/projects/tests/test_viewset_issue.py
index 6966c46cac..96f354478f 100644
--- a/rdmo/projects/tests/test_viewset_issue.py
+++ b/rdmo/projects/tests/test_viewset_issue.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Issue
diff --git a/rdmo/projects/tests/test_viewset_membership.py b/rdmo/projects/tests/test_viewset_membership.py
index 47519ca9f7..6bad007774 100644
--- a/rdmo/projects/tests/test_viewset_membership.py
+++ b/rdmo/projects/tests/test_viewset_membership.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Membership
diff --git a/rdmo/projects/tests/test_viewset_project.py b/rdmo/projects/tests/test_viewset_project.py
index 88b940eb2a..c5d08d0c40 100644
--- a/rdmo/projects/tests/test_viewset_project.py
+++ b/rdmo/projects/tests/test_viewset_project.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Project
@@ -243,7 +244,7 @@ def test_overview(db, client, username, password, project_id, condition_id):
 def test_resolve(db, client, username, password, project_id, condition_id):
     client.login(username=username, password=password)
 
-    url = reverse(urlnames['resolve'], args=[project_id]) + '?condition={}'.format(condition_id)
+    url = reverse(urlnames['resolve'], args=[project_id]) + f'?condition={condition_id}'
     response = client.get(url)
 
     if project_id in view_project_permission_map.get(username, []):
diff --git a/rdmo/projects/tests/test_viewset_project_integration.py b/rdmo/projects/tests/test_viewset_project_integration.py
index 1702af757a..94129a3d06 100644
--- a/rdmo/projects/tests/test_viewset_project_integration.py
+++ b/rdmo/projects/tests/test_viewset_project_integration.py
@@ -1,6 +1,7 @@
 import json
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import Integration
diff --git a/rdmo/projects/tests/test_viewset_project_invite.py b/rdmo/projects/tests/test_viewset_project_invite.py
index f0d11b958f..a99fc9fd97 100644
--- a/rdmo/projects/tests/test_viewset_project_invite.py
+++ b/rdmo/projects/tests/test_viewset_project_invite.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
diff --git a/rdmo/projects/tests/test_viewset_project_issue.py b/rdmo/projects/tests/test_viewset_project_issue.py
index 2cbefa4d26..16bd036b41 100644
--- a/rdmo/projects/tests/test_viewset_project_issue.py
+++ b/rdmo/projects/tests/test_viewset_project_issue.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Issue
diff --git a/rdmo/projects/tests/test_viewset_project_membership.py b/rdmo/projects/tests/test_viewset_project_membership.py
index 6bbb1ccb49..d935bff060 100644
--- a/rdmo/projects/tests/test_viewset_project_membership.py
+++ b/rdmo/projects/tests/test_viewset_project_membership.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
diff --git a/rdmo/projects/tests/test_viewset_project_page.py b/rdmo/projects/tests/test_viewset_project_page.py
index 15265c0444..e64eb4bce3 100644
--- a/rdmo/projects/tests/test_viewset_project_page.py
+++ b/rdmo/projects/tests/test_viewset_project_page.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 users = (
diff --git a/rdmo/projects/tests/test_viewset_project_snapshot.py b/rdmo/projects/tests/test_viewset_project_snapshot.py
index 770faf5e22..2c3c7c7337 100644
--- a/rdmo/projects/tests/test_viewset_project_snapshot.py
+++ b/rdmo/projects/tests/test_viewset_project_snapshot.py
@@ -1,6 +1,7 @@
 from pathlib import Path
 
 import pytest
+
 from django.conf import settings
 from django.urls import reverse
 
diff --git a/rdmo/projects/tests/test_viewset_project_value.py b/rdmo/projects/tests/test_viewset_project_value.py
index 4f1885b860..ecccedb347 100644
--- a/rdmo/projects/tests/test_viewset_project_value.py
+++ b/rdmo/projects/tests/test_viewset_project_value.py
@@ -1,12 +1,11 @@
 from pathlib import Path
 
 import pytest
+
 from django.conf import settings
 from django.urls import reverse
 
-from rdmo.core.constants import (VALUE_TYPE_CHOICES, VALUE_TYPE_FILE,
-                                 VALUE_TYPE_TEXT)
-from rdmo.questions.models import Question
+from rdmo.core.constants import VALUE_TYPE_CHOICES, VALUE_TYPE_FILE, VALUE_TYPE_TEXT
 
 from ..models import Value
 
@@ -269,7 +268,7 @@ def test_file_get(db, client, files, username, password, project_id, value_id):
     if value and value.value_type == VALUE_TYPE_FILE and project_id in view_value_permission_map.get(username, []):
         assert response.status_code == 200
         assert response['Content-Type'] == value.file_type
-        assert response['Content-Disposition'] == 'attachment; filename={}'.format(value.file_name)
+        assert response['Content-Disposition'] == f'attachment; filename={value.file_name}'
         assert response.content == value.file.read()
     else:
         assert response.status_code == 404
diff --git a/rdmo/projects/tests/test_viewset_snapshot.py b/rdmo/projects/tests/test_viewset_snapshot.py
index 57653a7f16..40fedc1bda 100644
--- a/rdmo/projects/tests/test_viewset_snapshot.py
+++ b/rdmo/projects/tests/test_viewset_snapshot.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from ..models import Snapshot
diff --git a/rdmo/projects/tests/test_viewset_value.py b/rdmo/projects/tests/test_viewset_value.py
index 62c32f4957..a2b3dfbf80 100644
--- a/rdmo/projects/tests/test_viewset_value.py
+++ b/rdmo/projects/tests/test_viewset_value.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 from rdmo.core.constants import VALUE_TYPE_FILE
@@ -62,7 +63,7 @@ def test_list(db, client, username, password):
 def test_list_snapshot(db, client, username, password, snapshot_id):
     client.login(username=username, password=password)
 
-    url = reverse(urlnames['list']) + '?snapshot={}'.format(snapshot_id)
+    url = reverse(urlnames['list']) + f'?snapshot={snapshot_id}'
     response = client.get(url)
 
     if password:
@@ -153,7 +154,7 @@ def test_file(db, client, files, username, password, value_id):
     if value.value_type == VALUE_TYPE_FILE and value.project.id in view_value_permission_map.get(username, []):
         assert response.status_code == 200
         assert response['Content-Type'] == value.file_type
-        assert response['Content-Disposition'] == 'attachment; filename={}'.format(value.file_name)
+        assert response['Content-Disposition'] == f'attachment; filename={value.file_name}'
         assert response.content == value.file.read()
     elif password:
         assert response.status_code == 404
diff --git a/rdmo/questions/tests/test_models.py b/rdmo/questions/tests/test_models.py
index a2fcb5f657..8fb1ab6b96 100644
--- a/rdmo/questions/tests/test_models.py
+++ b/rdmo/questions/tests/test_models.py
@@ -33,15 +33,19 @@ def test_catalog_descendants(db):
                 page = section_page.page
                 descendant_ids.append(page.id)
 
-                page_elements = list(page.page_questionsets.all()) + list(page.page_questions.all())
-                page_elements = sorted(page_elements, key=lambda e: e.order)
+                page_elements = sorted([
+                    *page.page_questionsets.all(),
+                    *page.page_questions.all()
+                ], key=lambda e: e.order)
                 for page_element in page_elements:
                     element = page_element.element
                     descendant_ids.append(element.id)
 
                     try:
-                        element_elements = list(element.questionset_questionsets.all()) + list(element.questionset_questions.all())
-                        element_elements = sorted(element_elements, key=lambda e: e.order)
+                        element_elements = sorted([
+                            *element.questionset_questionsets.all(),
+                            *element.questionset_questions.all()
+                        ], key=lambda e: e.order)
                     except AttributeError:
                         element_elements = []
 
@@ -50,8 +54,10 @@ def test_catalog_descendants(db):
                         descendant_ids.append(element2.id)
 
                         try:
-                            element_elements2 = list(element2.questionset_questionsets.all()) + list(element2.questionset_questions.all())
-                            element_elements2 = sorted(element_elements2, key=lambda e: e.order)
+                            element_elements2 = sorted([
+                                *element2.questionset_questionsets.all(),
+                                *element2.questionset_questions.all()
+                            ], key=lambda e: e.order)
                         except AttributeError:
                             element_elements2 = []
 
@@ -89,15 +95,19 @@ def test_section_descendants(db):
             page = section_page.page
             descendant_ids.append(page.id)
 
-            page_elements = list(page.page_questionsets.all()) + list(page.page_questions.all())
-            page_elements = sorted(page_elements, key=lambda e: e.order)
+            page_elements = sorted([
+                *page.page_questionsets.all(),
+                *page.page_questions.all()
+            ], key=lambda e: e.order)
             for page_element in page_elements:
                 element = page_element.element
                 descendant_ids.append(element.id)
 
                 try:
-                    element_elements = list(element.questionset_questionsets.all()) + list(element.questionset_questions.all())
-                    element_elements = sorted(element_elements, key=lambda e: e.order)
+                    element_elements = sorted([
+                        *element.questionset_questionsets.all(),
+                        *element.questionset_questions.all()
+                    ], key=lambda e: e.order)
                 except AttributeError:
                     element_elements = []
 
@@ -106,8 +116,10 @@ def test_section_descendants(db):
                     descendant_ids.append(element2.id)
 
                     try:
-                        element_elements2 = list(element2.questionset_questionsets.all()) + list(element2.questionset_questions.all())
-                        element_elements2 = sorted(element_elements2, key=lambda e: e.order)
+                        element_elements2 = sorted([
+                            *element2.questionset_questionsets.all(),
+                            *element2.questionset_questions.all()
+                        ], key=lambda e: e.order)
                     except AttributeError:
                         element_elements2 = []
 
@@ -134,15 +146,19 @@ def test_page_descendants(db):
     for instance in instances:
         descendant_ids = []
 
-        page_elements = list(instance.page_questionsets.all()) + list(instance.page_questions.all())
-        page_elements = sorted(page_elements, key=lambda e: e.order)
+        page_elements = sorted([
+            *instance.page_questionsets.all(),
+            *instance.page_questions.all()
+        ], key=lambda e: e.order)
         for page_element in page_elements:
             element = page_element.element
             descendant_ids.append(element.id)
 
             try:
-                element_elements = list(element.questionset_questionsets.all()) + list(element.questionset_questions.all())
-                element_elements = sorted(element_elements, key=lambda e: e.order)
+                element_elements = sorted([
+                    *element.questionset_questionsets.all(),
+                    *element.questionset_questions.all()
+                ], key=lambda e: e.order)
             except AttributeError:
                 element_elements = []
 
@@ -151,8 +167,10 @@ def test_page_descendants(db):
                 descendant_ids.append(element2.id)
 
                 try:
-                    element_elements2 = list(element2.questionset_questionsets.all()) + list(element2.questionset_questions.all())
-                    element_elements2 = sorted(element_elements2, key=lambda e: e.order)
+                    element_elements2 = sorted([
+                        *element2.questionset_questionsets.all(),
+                        *element2.questionset_questions.all()
+                    ], key=lambda e: e.order)
                 except AttributeError:
                     element_elements2 = []
 
@@ -187,8 +205,10 @@ def test_questionset_descendants(db):
         descendant_ids = []
 
         try:
-            element_elements = list(instance.questionset_questionsets.all()) + list(instance.questionset_questions.all())
-            element_elements = sorted(element_elements, key=lambda e: e.order)
+            element_elements = sorted([
+                *instance.questionset_questionsets.all(),
+                *instance.questionset_questions.all()
+            ], key=lambda e: e.order)
         except AttributeError:
             element_elements = []
 
@@ -197,8 +217,10 @@ def test_questionset_descendants(db):
             descendant_ids.append(element2.id)
 
             try:
-                element_elements2 = list(element2.questionset_questionsets.all()) + list(element2.questionset_questions.all())
-                element_elements2 = sorted(element_elements2, key=lambda e: e.order)
+                element_elements2 = sorted([
+                    *element2.questionset_questionsets.all(),
+                    *element2.questionset_questions.all()
+                ], key=lambda e: e.order)
             except AttributeError:
                 element_elements2 = []
 
diff --git a/rdmo/questions/tests/test_validator_locked_catalogs.py b/rdmo/questions/tests/test_validator_locked_catalogs.py
index 4345b65f72..3951e32e81 100644
--- a/rdmo/questions/tests/test_validator_locked_catalogs.py
+++ b/rdmo/questions/tests/test_validator_locked_catalogs.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Catalog
 from ..serializers.v1 import CatalogSerializer
diff --git a/rdmo/questions/tests/test_validator_locked_pages.py b/rdmo/questions/tests/test_validator_locked_pages.py
index ed117b66dd..9ebc011e54 100644
--- a/rdmo/questions/tests/test_validator_locked_pages.py
+++ b/rdmo/questions/tests/test_validator_locked_pages.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Page, Section
 from ..serializers.v1 import PageSerializer
diff --git a/rdmo/questions/tests/test_validator_locked_questions.py b/rdmo/questions/tests/test_validator_locked_questions.py
index 4668a6e999..5b82e13359 100644
--- a/rdmo/questions/tests/test_validator_locked_questions.py
+++ b/rdmo/questions/tests/test_validator_locked_questions.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Page, Question, QuestionSet
 from ..serializers.v1 import QuestionSerializer
diff --git a/rdmo/questions/tests/test_validator_locked_questionsets.py b/rdmo/questions/tests/test_validator_locked_questionsets.py
index 56183101a4..d4e5f6fc59 100644
--- a/rdmo/questions/tests/test_validator_locked_questionsets.py
+++ b/rdmo/questions/tests/test_validator_locked_questionsets.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Page, QuestionSet
 from ..serializers.v1 import QuestionSetSerializer
diff --git a/rdmo/questions/tests/test_validator_locked_sections.py b/rdmo/questions/tests/test_validator_locked_sections.py
index de59406b78..80f50bbcd2 100644
--- a/rdmo/questions/tests/test_validator_locked_sections.py
+++ b/rdmo/questions/tests/test_validator_locked_sections.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Catalog, Section
 from ..serializers.v1 import SectionSerializer
diff --git a/rdmo/questions/tests/test_validator_unique_uri_catalogs.py b/rdmo/questions/tests/test_validator_unique_uri_catalogs.py
index 411fae4311..5e418d47de 100644
--- a/rdmo/questions/tests/test_validator_unique_uri_catalogs.py
+++ b/rdmo/questions/tests/test_validator_unique_uri_catalogs.py
@@ -1,10 +1,11 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
 
-from ..models import Question, QuestionSet, Page, Catalog, Section
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
+
+from ..models import Catalog, Page, Question, QuestionSet, Section
 from ..serializers.v1 import SectionSerializer
 from ..validators import CatalogUniqueURIValidator
 
diff --git a/rdmo/questions/tests/test_validator_unique_uri_pages.py b/rdmo/questions/tests/test_validator_unique_uri_pages.py
index 133d3722ad..a2878451e7 100644
--- a/rdmo/questions/tests/test_validator_unique_uri_pages.py
+++ b/rdmo/questions/tests/test_validator_unique_uri_pages.py
@@ -1,10 +1,11 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
 
-from ..models import Question, QuestionSet, Page, Catalog, Section
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
+
+from ..models import Catalog, Page, Question, QuestionSet, Section
 from ..serializers.v1 import PageSerializer
 from ..validators import PageUniqueURIValidator
 
diff --git a/rdmo/questions/tests/test_validator_unique_uri_questions.py b/rdmo/questions/tests/test_validator_unique_uri_questions.py
index 0412afa49a..bcd53d8fec 100644
--- a/rdmo/questions/tests/test_validator_unique_uri_questions.py
+++ b/rdmo/questions/tests/test_validator_unique_uri_questions.py
@@ -1,10 +1,11 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
 
-from ..models import Question, QuestionSet, Page, Catalog, Section
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
+
+from ..models import Catalog, Page, Question, QuestionSet, Section
 from ..serializers.v1 import QuestionSerializer
 from ..validators import QuestionUniqueURIValidator
 
diff --git a/rdmo/questions/tests/test_validator_unique_uri_questionsets.py b/rdmo/questions/tests/test_validator_unique_uri_questionsets.py
index 1d93d0787d..5b4c9a7341 100644
--- a/rdmo/questions/tests/test_validator_unique_uri_questionsets.py
+++ b/rdmo/questions/tests/test_validator_unique_uri_questionsets.py
@@ -1,10 +1,11 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
 
-from ..models import Question, QuestionSet, Page, Catalog, Section
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
+
+from ..models import Catalog, Page, Question, QuestionSet, Section
 from ..serializers.v1 import QuestionSetSerializer
 from ..validators import QuestionSetUniqueURIValidator
 
diff --git a/rdmo/questions/tests/test_validator_unique_uri_sections.py b/rdmo/questions/tests/test_validator_unique_uri_sections.py
index 57c85f2d65..3af509afa9 100644
--- a/rdmo/questions/tests/test_validator_unique_uri_sections.py
+++ b/rdmo/questions/tests/test_validator_unique_uri_sections.py
@@ -1,10 +1,11 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
 
-from ..models import Question, QuestionSet, Page, Catalog, Section
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
+
+from ..models import Catalog, Page, Question, QuestionSet, Section
 from ..serializers.v1 import SectionSerializer
 from ..validators import SectionUniqueURIValidator
 
diff --git a/rdmo/questions/tests/test_viewset_catalog.py b/rdmo/questions/tests/test_viewset_catalog.py
index c84c6204b8..cddf9291a5 100644
--- a/rdmo/questions/tests/test_viewset_catalog.py
+++ b/rdmo/questions/tests/test_viewset_catalog.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import Catalog
@@ -112,7 +113,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'order': instance.order,
             'title_en': instance.title_lang1,
@@ -136,7 +137,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'order': instance.order,
             'title_en': instance.title_lang1,
diff --git a/rdmo/questions/tests/test_viewset_catalog_multisite.py b/rdmo/questions/tests/test_viewset_catalog_multisite.py
index d8b14db237..1a69aff5af 100644
--- a/rdmo/questions/tests/test_viewset_catalog_multisite.py
+++ b/rdmo/questions/tests/test_viewset_catalog_multisite.py
@@ -1,15 +1,14 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
 from ..models import Catalog
-
-from .test_viewset_catalog import urlnames, export_formats
+from .test_viewset_catalog import export_formats, urlnames
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -77,7 +76,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'order': instance.order,
             'title_en': instance.title_lang1,
@@ -101,7 +100,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'order': instance.order,
             'title_en': instance.title_lang1,
diff --git a/rdmo/questions/tests/test_viewset_page.py b/rdmo/questions/tests/test_viewset_page.py
index 7b3ec2d748..7d292e1b95 100644
--- a/rdmo/questions/tests/test_viewset_page.py
+++ b/rdmo/questions/tests/test_viewset_page.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
@@ -113,7 +114,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
@@ -144,7 +145,7 @@ def test_create_section(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -164,7 +165,7 @@ def test_create_section(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Page.objects.get(id=response.json().get('id'))
                 section.refresh_from_db()
-                assert section_pages + [(new_instance.id, order)] == \
+                assert [*section_pages, (new_instance.id, order)] == \
                     list(section.section_pages.values_list('page', 'order'))
 
 
@@ -187,7 +188,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
diff --git a/rdmo/questions/tests/test_viewset_page_multisite.py b/rdmo/questions/tests/test_viewset_page_multisite.py
index 16f001af38..20abffb078 100644
--- a/rdmo/questions/tests/test_viewset_page_multisite.py
+++ b/rdmo/questions/tests/test_viewset_page_multisite.py
@@ -1,16 +1,15 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
 from ..models import Page
-
-from .test_viewset_page import urlnames, export_formats
+from .test_viewset_page import export_formats, urlnames
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -78,7 +77,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
@@ -109,7 +108,7 @@ def test_create_section(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -129,7 +128,7 @@ def test_create_section(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Page.objects.get(id=response.json().get('id'))
                 section.refresh_from_db()
-                assert section_pages + [(new_instance.id, order)] == \
+                assert [*section_pages, (new_instance.id, order)] == \
                     list(section.section_pages.values_list('page', 'order'))
 
 
@@ -152,7 +151,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
diff --git a/rdmo/questions/tests/test_viewset_question.py b/rdmo/questions/tests/test_viewset_question.py
index 8cc192b2bd..929a7a97e6 100644
--- a/rdmo/questions/tests/test_viewset_question.py
+++ b/rdmo/questions/tests/test_viewset_question.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
@@ -102,7 +103,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment or '',
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
@@ -139,7 +140,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment or '',
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -165,7 +166,7 @@ def test_create_page(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Question.objects.get(id=response.json().get('id'))
                 page.refresh_from_db()
-                assert page_questions + [(new_instance.id, order)] == \
+                assert [*page_questions, (new_instance.id, order)] == \
                     list(page.page_questions.values_list('question', 'order'))
 
 
@@ -183,7 +184,7 @@ def test_create_questionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment or '',
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -209,7 +210,7 @@ def test_create_questionset(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Question.objects.get(id=response.json().get('id'))
                 questionset.refresh_from_db()
-                assert questionset_questions + [(new_instance.id, order)] == \
+                assert [*questionset_questions, (new_instance.id, order)] == \
                     list(questionset.questionset_questions.values_list('question', 'order'))
 
 
@@ -225,7 +226,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment or '',
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
diff --git a/rdmo/questions/tests/test_viewset_question_multisite.py b/rdmo/questions/tests/test_viewset_question_multisite.py
index 495bade005..230c611633 100644
--- a/rdmo/questions/tests/test_viewset_question_multisite.py
+++ b/rdmo/questions/tests/test_viewset_question_multisite.py
@@ -1,16 +1,15 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
 from ..models import Question
-
-from .test_viewset_question import urlnames, export_formats
+from .test_viewset_question import export_formats, urlnames
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -67,7 +66,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment or '',
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
@@ -104,7 +103,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment or '',
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -130,7 +129,7 @@ def test_create_page(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Question.objects.get(id=response.json().get('id'))
                 page.refresh_from_db()
-                assert page_questions + [(new_instance.id, order)] == \
+                assert [*page_questions, (new_instance.id, order)] == \
                     list(page.page_questions.values_list('question', 'order'))
 
 
@@ -148,7 +147,7 @@ def test_create_questionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment or '',
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -174,7 +173,7 @@ def test_create_questionset(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Question.objects.get(id=response.json().get('id'))
                 questionset.refresh_from_db()
-                assert questionset_questions + [(new_instance.id, order)] == \
+                assert [*questionset_questions, (new_instance.id, order)] == \
                     list(questionset.questionset_questions.values_list('question', 'order'))
 
 
@@ -190,7 +189,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment or '',
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
diff --git a/rdmo/questions/tests/test_viewset_questionset.py b/rdmo/questions/tests/test_viewset_questionset.py
index 1462562b5f..0ab31c35cd 100644
--- a/rdmo/questions/tests/test_viewset_questionset.py
+++ b/rdmo/questions/tests/test_viewset_questionset.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
@@ -113,7 +114,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
@@ -144,7 +145,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -164,7 +165,7 @@ def test_create_page(db, client, username, password):
             if response.status_code == 201:
                 new_instance = QuestionSet.objects.get(id=response.json().get('id'))
                 page.refresh_from_db()
-                assert page_questionsets + [(new_instance.id, order)] == \
+                assert [*page_questionsets, (new_instance.id, order)] == \
                     list(page.page_questionsets.values_list('questionset', 'order'))
 
 
@@ -182,7 +183,7 @@ def test_create_parent(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -202,7 +203,7 @@ def test_create_parent(db, client, username, password):
             if response.status_code == 201:
                 new_instance = QuestionSet.objects.get(id=response.json().get('id'))
                 parent.refresh_from_db()
-                assert parent_questionsets + [(new_instance.id, order)] == \
+                assert [*parent_questionsets, (new_instance.id, order)] == \
                     list(parent.questionset_questionsets.values_list('questionset', 'order'))
 
 
@@ -225,7 +226,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
diff --git a/rdmo/questions/tests/test_viewset_questionset_multisite.py b/rdmo/questions/tests/test_viewset_questionset_multisite.py
index f7789c869b..1d5f42003f 100644
--- a/rdmo/questions/tests/test_viewset_questionset_multisite.py
+++ b/rdmo/questions/tests/test_viewset_questionset_multisite.py
@@ -1,16 +1,16 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
 from ..models import QuestionSet
+from .test_viewset_questionset import export_formats, urlnames
 
-from .test_viewset_questionset import urlnames, export_formats
 
 @pytest.mark.parametrize('username,password', users)
 def test_list(db, client, username, password):
@@ -77,7 +77,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
@@ -108,7 +108,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -128,7 +128,7 @@ def test_create_page(db, client, username, password):
             if response.status_code == 201:
                 new_instance = QuestionSet.objects.get(id=response.json().get('id'))
                 page.refresh_from_db()
-                assert page_questionsets + [(new_instance.id, order)] == \
+                assert [*page_questionsets, (new_instance.id, order)] == \
                     list(page.page_questionsets.values_list('questionset', 'order'))
 
 
@@ -146,7 +146,7 @@ def test_create_parent(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'attribute': instance.attribute.pk if instance.attribute else '',
                 'is_collection': instance.is_collection,
@@ -166,7 +166,7 @@ def test_create_parent(db, client, username, password):
             if response.status_code == 201:
                 new_instance = QuestionSet.objects.get(id=response.json().get('id'))
                 parent.refresh_from_db()
-                assert parent_questionsets + [(new_instance.id, order)] == \
+                assert [*parent_questionsets, (new_instance.id, order)] == \
                     list(parent.questionset_questionsets.values_list('questionset', 'order'))
 
 
@@ -189,7 +189,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'attribute': instance.attribute.pk if instance.attribute else '',
             'is_collection': instance.is_collection,
diff --git a/rdmo/questions/tests/test_viewset_section.py b/rdmo/questions/tests/test_viewset_section.py
index a60d068720..425c202f43 100644
--- a/rdmo/questions/tests/test_viewset_section.py
+++ b/rdmo/questions/tests/test_viewset_section.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
@@ -113,7 +114,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2
@@ -136,7 +137,7 @@ def test_create_catalog(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'title_en': instance.title_lang1,
                 'title_de': instance.title_lang2,
@@ -148,7 +149,7 @@ def test_create_catalog(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Section.objects.get(id=response.json().get('id'))
                 catalog.refresh_from_db()
-                assert catalog_sections + [(new_instance.id, order)] == \
+                assert [*catalog_sections, (new_instance.id, order)] == \
                     list(catalog.catalog_sections.values_list('section', 'order'))
 
 
@@ -166,7 +167,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'pages': section_pages,
             'title_en': instance.title_lang1,
diff --git a/rdmo/questions/tests/test_viewset_section_multisite.py b/rdmo/questions/tests/test_viewset_section_multisite.py
index 85cc44dfac..fcb166fd49 100644
--- a/rdmo/questions/tests/test_viewset_section_multisite.py
+++ b/rdmo/questions/tests/test_viewset_section_multisite.py
@@ -1,16 +1,15 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.db.models import Max
 from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
 from ..models import Section
-
-from .test_viewset_section import urlnames, export_formats
+from .test_viewset_section import export_formats, urlnames
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -78,7 +77,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2
@@ -101,7 +100,7 @@ def test_create_catalog(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'uri_path': '%s_new_%s' % (instance.uri_path, username),
+                'uri_path': f'{instance.uri_path}_new_{username}',
                 'comment': instance.comment,
                 'title_en': instance.title_lang1,
                 'title_de': instance.title_lang2,
@@ -113,7 +112,7 @@ def test_create_catalog(db, client, username, password):
             if response.status_code == 201:
                 new_instance = Section.objects.get(id=response.json().get('id'))
                 catalog.refresh_from_db()
-                assert catalog_sections + [(new_instance.id, order)] == \
+                assert [*catalog_sections, (new_instance.id, order)] == \
                     list(catalog.catalog_sections.values_list('section', 'order'))
 
 
@@ -131,7 +130,7 @@ def test_create_m2m(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'pages': section_pages,
             'title_en': instance.title_lang1,
diff --git a/rdmo/questions/tests/test_viewset_widgettype.py b/rdmo/questions/tests/test_viewset_widgettype.py
index 9ccd419a5c..df845783f4 100644
--- a/rdmo/questions/tests/test_viewset_widgettype.py
+++ b/rdmo/questions/tests/test_viewset_widgettype.py
@@ -1,4 +1,5 @@
 import pytest
+
 from django.urls import reverse
 
 users = (
diff --git a/rdmo/tasks/tests/test_validator_locked.py b/rdmo/tasks/tests/test_validator_locked.py
index f6722589b5..45e1c16039 100644
--- a/rdmo/tasks/tests/test_validator_locked.py
+++ b/rdmo/tasks/tests/test_validator_locked.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Task
 from ..serializers.v1 import TaskSerializer
diff --git a/rdmo/tasks/tests/test_validator_unique_uri.py b/rdmo/tasks/tests/test_validator_unique_uri.py
index 0efd84da3b..14db7d394c 100644
--- a/rdmo/tasks/tests/test_validator_unique_uri.py
+++ b/rdmo/tasks/tests/test_validator_unique_uri.py
@@ -1,8 +1,9 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import Task
 from ..serializers.v1 import TaskSerializer
diff --git a/rdmo/tasks/tests/test_viewset_task.py b/rdmo/tasks/tests/test_viewset_task.py
index 2e3a35db97..92f31dc9c0 100644
--- a/rdmo/tasks/tests/test_viewset_task.py
+++ b/rdmo/tasks/tests/test_viewset_task.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import Task
@@ -97,7 +98,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2,
diff --git a/rdmo/tasks/tests/test_viewset_task_multisite.py b/rdmo/tasks/tests/test_viewset_task_multisite.py
index 98ecb8a823..2a1ee40f60 100644
--- a/rdmo/tasks/tests/test_viewset_task_multisite.py
+++ b/rdmo/tasks/tests/test_viewset_task_multisite.py
@@ -1,16 +1,14 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
 from ..models import Task
-
-from .test_viewset_task import export_formats
-from .test_viewset_task import urlnames
+from .test_viewset_task import export_formats, urlnames
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -67,7 +65,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2,
diff --git a/rdmo/views/tests/test_validator_locked.py b/rdmo/views/tests/test_validator_locked.py
index 51d1612136..d3091ed12d 100644
--- a/rdmo/views/tests/test_validator_locked.py
+++ b/rdmo/views/tests/test_validator_locked.py
@@ -1,7 +1,8 @@
 import pytest
+
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import View
 from ..serializers.v1 import ViewSerializer
diff --git a/rdmo/views/tests/test_validator_unique_uri.py b/rdmo/views/tests/test_validator_unique_uri.py
index aa4ce39225..fbf44d9346 100644
--- a/rdmo/views/tests/test_validator_unique_uri.py
+++ b/rdmo/views/tests/test_validator_unique_uri.py
@@ -1,8 +1,9 @@
 import pytest
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
-from rest_framework.exceptions import \
-    ValidationError as RestFameworkValidationError
+
+from rest_framework.exceptions import ValidationError as RestFameworkValidationError
 
 from ..models import View
 from ..serializers.v1 import ViewSerializer
diff --git a/rdmo/views/tests/test_view_tags.py b/rdmo/views/tests/test_view_tags.py
index 96ef380af0..6ff168b370 100644
--- a/rdmo/views/tests/test_view_tags.py
+++ b/rdmo/views/tests/test_view_tags.py
@@ -1,9 +1,9 @@
 import pytest
+
 from django.template import Context
 
 from rdmo.projects.models import Project
-from rdmo.views.templatetags.view_tags import (get_set_value, get_set_values,
-                                               get_sets, get_value, get_values)
+from rdmo.views.templatetags.view_tags import get_set_value, get_set_values, get_sets, get_value, get_values
 from rdmo.views.utils import ProjectWrapper
 
 project_pk = 1
@@ -22,7 +22,9 @@ def context(db):
 
 @pytest.fixture
 def values(db):
-    return Project.objects.get(pk=project_pk).values.filter(snapshot=None).order_by('set_index').order_by('collection_index')
+    return Project.objects.get(pk=project_pk).values.filter(snapshot=None) \
+                                                    .order_by('set_index') \
+                                                    .order_by('collection_index')
 
 
 def test_get_value_project_title(context):
@@ -81,10 +83,12 @@ def test_get_value(context, values):
 def test_get_set_values(context, values):
     path = 'set/single/text'
     for value_set in get_sets(context, 'set'):
-        assertListEqual(get_set_values(context, value_set, path), values.filter(attribute__path=path, set_index=value_set['set_index']))
+        assertListEqual(get_set_values(context, value_set, path),
+                        values.filter(attribute__path=path, set_index=value_set['set_index']))
 
 
 def test_get_set_value(context, values):
     path = 'set/single/text'
     for value_set in get_sets(context, 'set'):
-        assert get_set_value(context, value_set, path)['id'] == values.filter(attribute__path=path, set_index=value_set['set_index']).first().id
+        assert get_set_value(context, value_set, path)['id'] == \
+               values.filter(attribute__path=path, set_index=value_set['set_index']).first().id
diff --git a/rdmo/views/tests/test_viewset_view.py b/rdmo/views/tests/test_viewset_view.py
index 310afb4662..64079c9a3e 100644
--- a/rdmo/views/tests/test_viewset_view.py
+++ b/rdmo/views/tests/test_viewset_view.py
@@ -1,6 +1,7 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
 from ..models import View
@@ -97,7 +98,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'template': instance.template,
             'title_en': instance.title_lang1,
diff --git a/rdmo/views/tests/test_viewset_view_multisite.py b/rdmo/views/tests/test_viewset_view_multisite.py
index fa84517ec9..31be9c2fbe 100644
--- a/rdmo/views/tests/test_viewset_view_multisite.py
+++ b/rdmo/views/tests/test_viewset_view_multisite.py
@@ -1,16 +1,14 @@
 import xml.etree.ElementTree as et
 
 import pytest
+
 from django.urls import reverse
 
+from ...core.tests import get_obj_perms_status_code
 from ...core.tests import multisite_status_map as status_map
 from ...core.tests import multisite_users as users
-from ...core.tests import get_obj_perms_status_code
-
 from ..models import View
-
-from .test_viewset_view import export_formats
-from .test_viewset_view import urlnames
+from .test_viewset_view import export_formats, urlnames
 
 
 @pytest.mark.parametrize('username,password', users)
@@ -67,7 +65,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'uri_path': '%s_new_%s' % (instance.uri_path, username),
+            'uri_path': f'{instance.uri_path}_new_{username}',
             'comment': instance.comment,
             'template': instance.template,
             'title_en': instance.title_lang1,
diff --git a/testing/config/settings/__init__.py b/testing/config/settings/__init__.py
index f52a1cc4f5..b0e6094d54 100644
--- a/testing/config/settings/__init__.py
+++ b/testing/config/settings/__init__.py
@@ -2,27 +2,3 @@
 
 from .base import *
 from .local import *
-
-# add static and templates from local.THEME_DIR to STATICFILES_DIRS and TEMPLATES
-try:
-    STATICFILES_DIRS = [
-        os.path.join(THEME_DIR, 'static/')
-    ]
-    TEMPLATES[0]['DIRS'].append(os.path.join(THEME_DIR, 'templates/'))
-except NameError:
-    pass
-
-# prepend the local.BASE_URL to the different URL settings
-try:
-    LOGIN_URL = BASE_URL + LOGIN_URL
-    LOGIN_REDIRECT_URL = BASE_URL + LOGIN_REDIRECT_URL
-    LOGOUT_URL = BASE_URL + LOGOUT_URL
-    ACCOUNT_LOGOUT_REDIRECT_URL = BASE_URL
-    MEDIA_URL = BASE_URL + MEDIA_URL
-    STATIC_URL = BASE_URL + STATIC_URL
-
-    CSRF_COOKIE_PATH = BASE_URL + '/'
-    LANGUAGE_COOKIE_PATH = BASE_URL + '/'
-    SESSION_COOKIE_PATH = BASE_URL + '/'
-except NameError:
-    pass
diff --git a/testing/config/urls.py b/testing/config/urls.py
index e7a4a453bb..2b8747a97a 100644
--- a/testing/config/urls.py
+++ b/testing/config/urls.py
@@ -1,5 +1,6 @@
 from django.contrib import admin
 from django.urls import include, path
+
 from rdmo.core.views import about, home
 
 urlpatterns = [
diff --git a/testing/fixtures/groups.json b/testing/fixtures/groups.json
index 81187dcba4..66df260b5d 100644
--- a/testing/fixtures/groups.json
+++ b/testing/fixtures/groups.json
@@ -31,4 +31,4 @@
       "permissions": []
     }
   }
-]
\ No newline at end of file
+]
diff --git a/testing/fixtures/overlays.json b/testing/fixtures/overlays.json
index c8e843e1a3..6a64b39281 100644
--- a/testing/fixtures/overlays.json
+++ b/testing/fixtures/overlays.json
@@ -19,4 +19,4 @@
       "current": "create-project"
     }
   }
-]
\ No newline at end of file
+]
diff --git a/testing/fixtures/projects.json b/testing/fixtures/projects.json
index 7ac5ec8ae5..791f1a7493 100644
--- a/testing/fixtures/projects.json
+++ b/testing/fixtures/projects.json
@@ -5768,4 +5768,4 @@
       "timestamp": "2023-07-10T14:06:55.112Z"
     }
   }
-]
\ No newline at end of file
+]
diff --git a/testing/fixtures/users.json b/testing/fixtures/users.json
index 6776a47a2b..960be64813 100644
--- a/testing/fixtures/users.json
+++ b/testing/fixtures/users.json
@@ -413,4 +413,4 @@
       "user_permissions": []
     }
   }
-]
\ No newline at end of file
+]
diff --git a/testing/import/catalogs.json b/testing/import/catalogs.json
index a018cd5adb..f5af5985c4 100644
--- a/testing/import/catalogs.json
+++ b/testing/import/catalogs.json
@@ -5393,4 +5393,4 @@
     "help_de": null,
     "sections": null
   }
-]
\ No newline at end of file
+]