diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 22c521abb6a6d63b934e5092a25ecbc86722c007..3f0ba122cd4c7d65c8629a43cca0805ebe33e1cf 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,5 +13,6 @@ before_script:
 
 test:
   script:
+    - make lint
     - make test
 
diff --git a/.pylintrc b/.pylintrc
index 5e0c54507106001be4dc8957d072b258c9cfe868..5774872c0bb9c0b5ec06a055bdaee2760c318fc3 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,2 +1,18 @@
 [MASTER]
 load-plugins=pylint_django
+ignore=gregsite/settings
+django-settings-module=gregsite.settings
+
+[MESSAGES CONTROL]
+disable=
+    fixme,
+    import-outside-toplevel,
+    invalid-name,
+    line-too-long,
+    missing-class-docstring,
+    missing-function-docstring,
+    missing-module-docstring,
+    no-self-use,
+    too-few-public-methods,
+    too-many-ancestors,
+    unused-argument,
diff --git a/Makefile b/Makefile
index d9f977c642a58714b12ae12e61905427944c55e9..1172547917e9365efa0a0576cd694c6fe157e6ae 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,8 @@
+BLACK ?= black -q
 MYPY ?= mypy
 PIP ?= pip -q
 POETRY ?= poetry
+PYLINT ?= pylint -sn
 PYTHON ?= python3.9
 VENV ?= venv
 
@@ -9,6 +11,8 @@ pip = python -m $(PIP)
 poetry = python -m $(POETRY)
 venv = . $(VENV)/bin/activate &&
 
+PACKAGES = greg/ gregsite/
+
 all: test
 
 .PHONY: clean
@@ -28,8 +32,18 @@ test: $(VENV)
 	$(venv) $(mypy) -p greg
 	$(venv) python manage.py test
 
+.PHONY: lint
+lint: $(VENV)
+	$(venv) $(poetry) -q check
+	$(venv) $(PYLINT) $(PACKAGES)
+	$(venv) $(BLACK) --check --diff $(PACKAGES)
+
+.PHONY: fmt
+fmt: $(VENV)
+	$(venv) $(BLACK) $(PACKAGES)
+
 .PHONY: deps
 deps: poetry.lock
 
 poetry.lock: pyproject.toml | $(VENV)
-	$(venv) $(poetry) update $(PACKAGES)
+	$(venv) $(poetry) update
diff --git a/greg/api/serializers/role.py b/greg/api/serializers/role.py
index 06f9eaf4cfaaf2a35deb3c459f38522e141f1c58..3fd2d03d884330f481e529919126ee6350221e2f 100644
--- a/greg/api/serializers/role.py
+++ b/greg/api/serializers/role.py
@@ -1,9 +1,9 @@
-from rest_framework import fields, serializers
+from rest_framework.serializers import ModelSerializer
 
 from greg.models import Role
 
 
-class RoleSerializer(serializers.ModelSerializer):
+class RoleSerializer(ModelSerializer):
     class Meta:
         model = Role
         fields = "__all__"
diff --git a/greg/api/views/role.py b/greg/api/views/role.py
index 80de78c392220b921e364c6bf902cbbf4f24bb97..fbfce2fd85e3b515ccbce00cbe9778aa3b7d8994 100644
--- a/greg/api/views/role.py
+++ b/greg/api/views/role.py
@@ -1,7 +1,4 @@
-from django_filters import rest_framework as filters
-
 from rest_framework import viewsets
-from rest_framework.schemas.openapi import AutoSchema
 
 from greg.api.pagination import PrimaryKeyCursorPagination
 from greg.api.serializers.role import RoleSerializer
diff --git a/greg/apps.py b/greg/apps.py
index 6d47fa17fa0a439edb19333cbd8646162973e176..d3307bd4c24714e32a3e648524e5a9724a5a8a5a 100644
--- a/greg/apps.py
+++ b/greg/apps.py
@@ -7,4 +7,4 @@ class GregAppConfig(AppConfig):
     verbose_name = _("Greg")
 
     def ready(self):
-        import greg.signals
+        import greg.signals  # pylint: disable=unused-import
diff --git a/greg/models.py b/greg/models.py
index 9e77c2626f97217679c15ca1a27711f413eca6bc..4db81b57b4d6150a68a501e9ffb786ef927e3fbc 100644
--- a/greg/models.py
+++ b/greg/models.py
@@ -69,7 +69,7 @@ class Role(BaseModel):
     default_duration_days = models.IntegerField(null=True)
 
     def __str__(self):
-        return str(self.name_nb or self.name_en or self.slug)
+        return str(self.name_nb or self.name_en)
 
     def __repr__(self):
         return "{}(id={!r}, type={!r}, name_nb={!r}, name_en={!r})".format(
diff --git a/greg/signals.py b/greg/signals.py
index c34c1a19dc030958454f35e9798b38fb708220d7..3a3a52726fde5a9651633d7c0e658f3e79c0dfe7 100644
--- a/greg/signals.py
+++ b/greg/signals.py
@@ -75,7 +75,9 @@ def add_changed_fields_callback(sender, instance, raw, *args, **kwargs):
     changed = instance.is_dirty()
     if not changed:
         return
-    instance._changed_fields = list(instance.get_dirty_fields().keys())
+    instance._changed_fields = list(  # pylint: disable=protected-access
+        instance.get_dirty_fields().keys()
+    )
 
 
 @receiver(models.signals.post_save, dispatch_uid="save_notification_callback")
diff --git a/greg/tests/test_api_person.py b/greg/tests/test_api_person.py
index b504534d7e162491b4ccbbf19ddee32c8dc19c41..cc0b0932a73859aee37327cb38ced81f4187f77b 100644
--- a/greg/tests/test_api_person.py
+++ b/greg/tests/test_api_person.py
@@ -1,13 +1,13 @@
 from django.contrib.auth import get_user_model
-from django.utils.dateparse import parse_datetime
 from rest_framework import status
-from rest_framework.reverse import reverse
-from rest_framework.test import APIClient, APITestCase
 from rest_framework.authtoken.models import Token
+from rest_framework.reverse import reverse
+from rest_framework.test import (
+    APIClient,
+    APITestCase,
+)
 
-from django.test import TestCase
-
-from greg.models import Person, PersonRole, Role
+from greg.models import Person
 
 
 class GregAPITestCase(APITestCase):
@@ -15,14 +15,14 @@ class GregAPITestCase(APITestCase):
         self.client = self.get_token_client()
 
     def get_token_client(self, username="test") -> APIClient:
-        self.user, created = get_user_model().objects.get_or_create(username=username)
-        token, created = Token.objects.get_or_create(user=self.user)
+        self.user, _ = get_user_model().objects.get_or_create(username=username)
+        token, _ = Token.objects.get_or_create(user=self.user)
         client = APIClient()
         client.credentials(HTTP_AUTHORIZATION="Token {}".format(token.key))
         return client
 
 
-class PersonTestData:
+class PersonTestData(GregAPITestCase):
     def setUp(self):
         super().setUp()
         self.person_foo_data = dict(
@@ -43,7 +43,7 @@ class PersonTestData:
         self.person_bar = Person.objects.create(**self.person_bar_data)
 
 
-class PersonAPITestCase(PersonTestData, GregAPITestCase):
+class PersonAPITestCase(PersonTestData):
     def test_get_person(self):
         url = reverse("person-detail", kwargs={"id": self.person_foo.id})
         response = self.client.get(url)
diff --git a/greg/urls.py b/greg/urls.py
index 44f0031d68b75dc8ea3a23672dfff2e6b88b321a..aae406a6ca1128569d8b7efd1140e97e1b912da4 100644
--- a/greg/urls.py
+++ b/greg/urls.py
@@ -14,7 +14,6 @@ Including another URLconf
     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 """
 from typing import List
-from django.contrib import admin
 from django.urls import path, include
 from django.urls.resolvers import URLResolver
 
diff --git a/pyproject.toml b/pyproject.toml
index 5af76aa9efa7dd1f48e0b5e7608c93095ad4edb7..7f4109fc9ebf95e7bcba7d7daf5fa1cb1b77598e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,6 @@ whitenoise = "*"
 
 [tool.poetry.dev-dependencies]
 Faker = "^8.10.1"
-autopep8 = "*"
 black = "*"
 django-stubs = "*"
 djangorestframework-stubs = "*"
@@ -31,6 +30,9 @@ pylint = "*"
 pylint-django = "*"
 rope = "*"
 
+[tool.black]
+extend-exclude = '^/greg/migrations/.*\.py'
+
 [build-system]
 build-backend = "poetry.core.masonry.api"
 requires = ["poetry-core>=1.0.0"]