diff --git a/arkindex/documents/dates.py b/arkindex/documents/dates.py
index dc3012bef8a3276bb890e8cb6bb5607c5876e52d..6e0cdd57f797dcddd7ea6a12b8caf35e20890632 100644
--- a/arkindex/documents/dates.py
+++ b/arkindex/documents/dates.py
@@ -16,7 +16,7 @@ class DatePrecision(Enum):
     Day = "d"
 
 
-class InterpretedDate(object):
+class InterpretedDate:
 
     def __init__(self, year, month=None, day=None, type=DateType.Exact):
         self.year = int(year)
@@ -73,7 +73,7 @@ class InterpretedDate(object):
         return "-".join("{:02d}".format(e) for e in tuple(self) if e)
 
 
-class InterpretedDateMixin(object):
+class InterpretedDateMixin:
     """
     Adds on-demand date parsing from a text field to InterpretedDates.
     Requires a `raw_dates` property that returns the date string.
diff --git a/arkindex/documents/indexer.py b/arkindex/documents/indexer.py
index 203722ec319c6fadd9c9ba537d582693a05d26b7..37f7e200e4133c4d982bcb2ba5349a6c5a2977d7 100644
--- a/arkindex/documents/indexer.py
+++ b/arkindex/documents/indexer.py
@@ -59,7 +59,7 @@ INNER JOIN documents_elementtype elementtype ON (element.type_id = elementtype.i
 """
 
 
-class Indexer(object):
+class Indexer:
 
     # The query yielding all the elements to run on will look for all the child elements of all indexable elements
     # The joins can take a very long time, so the query gets split into one to fetch all the indexable elements,
diff --git a/arkindex/documents/tests/test_edit_elementpath.py b/arkindex/documents/tests/test_edit_elementpath.py
index ac0f945e98d688a1481e61d08693a88beb374a00..5633763ceace96fd5fa7a00c21313f4224cd3002 100644
--- a/arkindex/documents/tests/test_edit_elementpath.py
+++ b/arkindex/documents/tests/test_edit_elementpath.py
@@ -358,7 +358,7 @@ class TestEditElementPath(FixtureTestCase):
         # B will only have one remaining path: the first path that got picked by remove_child for the update.
         # The other path will have been deleted. We can therefore get this remaining path and compare it by its ID
         # to the two paths that we had before, and pick the parent that was in the old version of this path.
-        class FirstParent(object):
+        class FirstParent:
             def __str__(self):
                 path_id = elements["B"].paths.get().id
                 if path1.id == path_id:
diff --git a/arkindex/ponos/serializer_fields.py b/arkindex/ponos/serializer_fields.py
index 28231333920adead31076f4bfa8ca02aed19bcfa..1bd83378c89a4b1c2b71abdfe3d8a48ebb40988a 100644
--- a/arkindex/ponos/serializer_fields.py
+++ b/arkindex/ponos/serializer_fields.py
@@ -56,7 +56,7 @@ class Base64Field(serializers.CharField):
         return base64.b64encode(obj)
 
 
-class CurrentProcessDefault(object):
+class CurrentProcessDefault:
     """
     Use the process of the currently authenticated task as a default value.
     If Ponos task authentication is not in use, returns None.
diff --git a/arkindex/process/api.py b/arkindex/process/api.py
index f527305661111a9e60b334b52f2e63ccaed632c4..6b6e2416bf7dd8acb5729bae13f54091e02ae709 100644
--- a/arkindex/process/api.py
+++ b/arkindex/process/api.py
@@ -270,7 +270,7 @@ class ProcessList(ProcessACLMixin, ListAPIView):
         return qs.order_by("-date_order")
 
 
-class ProcessQuerysetMixin(object):
+class ProcessQuerysetMixin:
     """
     Optimized queryset for Retrieve/Update/PartialUpdate/Destroy/RetryProcess
     """
diff --git a/arkindex/process/builder.py b/arkindex/process/builder.py
index 74619b170ed571970446e106b51d35595f3052be..af735708e23d1ed17043e4a226da74cd7b93d8d5 100644
--- a/arkindex/process/builder.py
+++ b/arkindex/process/builder.py
@@ -16,7 +16,7 @@ from arkindex.images.models import ImageServer
 from arkindex.ponos.models import GPU, Task, task_token_default
 
 
-class ProcessBuilder(object):
+class ProcessBuilder:
 
     def __init__(self, process) -> None:
         self.process = process
diff --git a/arkindex/project/argparse.py b/arkindex/project/argparse.py
index bbb3dcf17ccbee051c11c0c33d2236d32bde414a..66cbd6946973ecba1d31638689fcbe2644d5c474 100644
--- a/arkindex/project/argparse.py
+++ b/arkindex/project/argparse.py
@@ -7,7 +7,7 @@ from arkindex.process.models import Process, Repository, WorkerVersion
 from arkindex.users.models import User
 
 
-class ModelArgument(object):
+class ModelArgument:
     model = None
     text_search_field = "name"
     text_search_lookup = "icontains"
diff --git a/arkindex/project/aws.py b/arkindex/project/aws.py
index 659f37326a0e132505e1fb4b63a7729e668ba328..1bde384677f18d635a5268a8618136d21156570a 100644
--- a/arkindex/project/aws.py
+++ b/arkindex/project/aws.py
@@ -88,7 +88,7 @@ def _retry_delete_predicate(exception):
     )
 
 
-class S3FileMixin(object):
+class S3FileMixin:
 
     def get_s3_object(self):
         if not self.s3_bucket or not self.s3_key:
diff --git a/arkindex/project/fields.py b/arkindex/project/fields.py
index e66f28bb1347a0cdc538ecf48493ef621c70712d..69f974302a739cedc04406b8ecaa641b2d330fac 100644
--- a/arkindex/project/fields.py
+++ b/arkindex/project/fields.py
@@ -127,7 +127,7 @@ class LastItemTransform(Transform):
         return self.base_field
 
 
-class LastItemTransformFactory(object):
+class LastItemTransformFactory:
     """
     Create a LastItemTransform with a given base field
     """
diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py
index 6f04942aa21809a57f434e94eab942e7bf9801a8..744da257c3a26d54d78e2aa73704681039041d48 100644
--- a/arkindex/project/mixins.py
+++ b/arkindex/project/mixins.py
@@ -13,7 +13,7 @@ from arkindex.users.models import Role
 from arkindex.users.utils import filter_rights, get_max_level, has_access
 
 
-class ACLMixin(object):
+class ACLMixin:
     """
     Access control mixin using the generic Right table.
     """
@@ -140,7 +140,7 @@ class ProcessACLMixin(ACLMixin):
         return get_max_level(self.user, process.corpus)
 
 
-class SelectionMixin(object):
+class SelectionMixin:
 
     def get_selection(self, corpus_id=None):
         assert settings.ARKINDEX_FEATURES["selection"], "Selection feature is unavailable"
@@ -165,7 +165,7 @@ class DeprecatedAPIException(APIException):
     default_code = "deprecated"
 
 
-class DeprecatedMixin(object):
+class DeprecatedMixin:
     # Add this mixin to an APIView to make it deprecated.
 
     serializer_class = DeprecatedExceptionSerializer
@@ -196,7 +196,7 @@ class DeprecatedMixin(object):
             raise DeprecatedAPIException(detail=getattr(self, "deprecation_message", None))
 
 
-class CachedViewMixin(object):
+class CachedViewMixin:
     """
     Add this mixin to any class-based view to cache it.
     """
diff --git a/arkindex/project/tests/__init__.py b/arkindex/project/tests/__init__.py
index cca5e05eb7ad2ffcf75194ba50cda1c6c01183cb..9db13cd9877d602b76806f3582bbe14103924a1f 100644
--- a/arkindex/project/tests/__init__.py
+++ b/arkindex/project/tests/__init__.py
@@ -96,7 +96,7 @@ class _AssertExactQueriesContext(CaptureQueriesContext):
         self.test_case.assertEqual(expected_sql, actual_sql)
 
 
-class FixtureMixin(object):
+class FixtureMixin:
     """
     Add the database fixture to a test case
     """
diff --git a/arkindex/project/validators.py b/arkindex/project/validators.py
index 15fb732317819916a9bcae0c15d42596c0fab3f3..980e2636869c9c65cb63dfc789e122a53150c7d2 100644
--- a/arkindex/project/validators.py
+++ b/arkindex/project/validators.py
@@ -2,7 +2,7 @@ from django.core import validators
 from rest_framework import serializers
 
 
-class XorValidator(object):
+class XorValidator:
     """
     A generic validator for when two fields can't be used simultaneously
     """
@@ -45,7 +45,7 @@ class ConditionalUniqueValidator(serializers.UniqueTogetherValidator):
         return super().__call__(attrs, serializer)
 
 
-class ForbiddenValidator(object):
+class ForbiddenValidator:
     """
     A validator that will show an error message any time a value is set.
 
@@ -65,7 +65,7 @@ class ForbiddenValidator(object):
             raise serializers.ValidationError(self.message)
 
 
-class HiddenCallableValidatorMixin(object):
+class HiddenCallableValidatorMixin:
     """
     Implements a workaround for some issues with error messages in DRF
     and with drf-spectacular OpenAPI schema generation when the `limit_value`
diff --git a/arkindex/users/tests/test_jobs.py b/arkindex/users/tests/test_jobs.py
index e008cb2eeb7ab807cac01067ee1fb2b107f1c039..ad20177b79245b7bf63f982086b6c61efddd2540 100644
--- a/arkindex/users/tests/test_jobs.py
+++ b/arkindex/users/tests/test_jobs.py
@@ -10,7 +10,7 @@ from rq.job import JobStatus
 from arkindex.project.tests import FixtureAPITestCase
 
 
-class MockedJob(object):
+class MockedJob:
 
     def __init__(self, id=None, user_id=None, status=JobStatus.QUEUED, **kwargs):
         self.id = id or str(uuid4())
diff --git a/ruff.toml b/ruff.toml
index 943a2b2ee4070b7c70eeb9a89b38c455c8f4bea3..066da2b9e241ee408729812c7b7b1f7a0e4a008d 100644
--- a/ruff.toml
+++ b/ruff.toml
@@ -25,6 +25,8 @@ select = [
     "RET",
     # eradicate
     "ERA",
+    # useless-object-inheritance
+    "UP004",
 ]
 ignore = ["E501", "RET502", "RET503"]