diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index da61f5362f13dd2fb2736d6787230debf546d937..de6e66acf42576e81832dd5fb4911bb97f73fa8e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.1.7
+    rev: v0.3.3
     hooks:
       # Run the linter.
       - id: ruff
diff --git a/Makefile b/Makefile
index 4a861fb35bf4ce093b8a1098abc09dd5c5b6e54c..8b473548268fdd28bdcd1c6e5f92c1c9b6fc550c 100644
--- a/Makefile
+++ b/Makefile
@@ -32,7 +32,7 @@ build:
 .PHONY: fmt
 fmt:
 	@echo "run code formatters on all code."
-	python -m ruff --fix .
+	python -m ruff check --fix .
 	python -m ruff format .
 	npx prettier . --write
 	djhtml hypha/
@@ -62,7 +62,7 @@ lint:
 .PHONY: lint-fix
 lint-fix:
 	@echo "Try fixing plausible python linting issues."
-	ruff --fix .
+	ruff check --fix .
 
 .PHONY: py-test
 py-test:
diff --git a/docs/scripts/doc_macros.py b/docs/scripts/doc_macros.py
index cc617fd2dffa5a73b168da451a527d3dac2b1a22..c24f521805497f38db32dfb274f89b7d62574c40 100644
--- a/docs/scripts/doc_macros.py
+++ b/docs/scripts/doc_macros.py
@@ -34,9 +34,9 @@ def define_env(env):
         env.variables.versions["python"]["version"] = ".".join(py_ver.split(".")[:-1])
 
         if env.variables.versions["python"]["packages"]["macos"] is None:
-            env.variables.versions["python"]["packages"][
-                "macos"
-            ] = f"python@{'.'.join(py_ver.split('.')[:-1])}"
+            env.variables.versions["python"]["packages"]["macos"] = (
+                f"python@{'.'.join(py_ver.split('.')[:-1])}"
+            )
 
     if env.variables.versions["node"]["version"] is None:
         node_ver = get_node_version()
diff --git a/hypha/apply/funds/templatetags/submission_tags.py b/hypha/apply/funds/templatetags/submission_tags.py
index 825f9481614130484281737939faa5596393e4d5..db565999ec498b73cbe5d5d02d10ffb056bf71c2 100644
--- a/hypha/apply/funds/templatetags/submission_tags.py
+++ b/hypha/apply/funds/templatetags/submission_tags.py
@@ -15,9 +15,9 @@ def submission_links(value):
     links = {}
     if matches:
         for submission in ApplicationSubmission.objects.filter(id__in=matches):
-            links[
-                rf"\#{submission.id}"
-            ] = f'<a href="{submission.get_absolute_url()}">{submission.title} <span class="mid-grey-text">#{submission.id}</span></a>'
+            links[rf"\#{submission.id}"] = (
+                f'<a href="{submission.get_absolute_url()}">{submission.title} <span class="mid-grey-text">#{submission.id}</span></a>'
+            )
 
     if links:
         for sid, link in links.items():
diff --git a/hypha/apply/funds/wagtail_hooks.py b/hypha/apply/funds/wagtail_hooks.py
index c51a306b971f4707f27a218dc0d995cda01d3942..3dbbafdc6f371ac0982e7e48009a326cb729e1fc 100644
--- a/hypha/apply/funds/wagtail_hooks.py
+++ b/hypha/apply/funds/wagtail_hooks.py
@@ -14,9 +14,9 @@ def before_create_page(request, parent_page, page_class):
     if issubclass(page_class, RoundBase) and request.POST:
         if not hasattr(page_class, "parent_page"):
             page_class.parent_page = {}
-        page_class.parent_page.setdefault(page_class, {})[
-            request.POST["title"]
-        ] = parent_page
+        page_class.parent_page.setdefault(page_class, {})[request.POST["title"]] = (
+            parent_page
+        )
     return page_class
 
 
diff --git a/hypha/apply/projects/forms/project.py b/hypha/apply/projects/forms/project.py
index b341b31be2cf7973467f1f954fd71fdfac426e54..b92d2d3b2d022aa806778437bce13f4177bacd86 100644
--- a/hypha/apply/projects/forms/project.py
+++ b/hypha/apply/projects/forms/project.py
@@ -323,15 +323,15 @@ class AssignApproversForm(forms.ModelForm):
                 list(current_paf_reviewer_role.user_roles.all()), exact_match=True
             )
 
-            self.fields[
-                slugify(current_paf_reviewer_role.label)
-            ] = forms.ModelChoiceField(
-                queryset=users,
-                required=False,
-                blank=True,
-                label=current_paf_reviewer_role.label,
-                initial=paf_approval.user,
-                disabled=paf_approval.approved,
+            self.fields[slugify(current_paf_reviewer_role.label)] = (
+                forms.ModelChoiceField(
+                    queryset=users,
+                    required=False,
+                    blank=True,
+                    label=current_paf_reviewer_role.label,
+                    initial=paf_approval.user,
+                    disabled=paf_approval.approved,
+                )
             )
 
     def save(self, commit=True):
diff --git a/hypha/apply/projects/services/sageintacct/__init__.py b/hypha/apply/projects/services/sageintacct/__init__.py
index 159af5c2d06a959a52b0bff6a2af403d3e476ce4..cd576e36d6a325573f0b5aea88323c322f73a189 100644
--- a/hypha/apply/projects/services/sageintacct/__init__.py
+++ b/hypha/apply/projects/services/sageintacct/__init__.py
@@ -1,6 +1,7 @@
 """
 Sage Intacct init
 """
+
 from .exceptions import (
     ExpiredTokenError,
     InternalServerError,
diff --git a/hypha/apply/projects/services/sageintacct/wrapper/project.py b/hypha/apply/projects/services/sageintacct/wrapper/project.py
index 3664c57d037629289df244db4b15c3986105eb5b..99ed9f7eb1e1445be3293a96353d5e2cfa4c13b1 100644
--- a/hypha/apply/projects/services/sageintacct/wrapper/project.py
+++ b/hypha/apply/projects/services/sageintacct/wrapper/project.py
@@ -1,6 +1,7 @@
 """
 Sage Intacct contract
 """
+
 from .api_base import ApiBase
 
 
diff --git a/hypha/apply/projects/services/sageintacct/wrapper/purchasing.py b/hypha/apply/projects/services/sageintacct/wrapper/purchasing.py
index d6e7f4da46ef2ba2817b79c3c06b6538c7cbfae2..8181a8ee9f76128d9436f5a6655fde6dcc666b33 100644
--- a/hypha/apply/projects/services/sageintacct/wrapper/purchasing.py
+++ b/hypha/apply/projects/services/sageintacct/wrapper/purchasing.py
@@ -1,6 +1,7 @@
 """
 Sage Intacct purchasing
 """
+
 from .api_base import ApiBase
 
 
diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py
index b43d9cd6e003e17d401e7d4f0ad711c033bd91f6..12eb26cfc7a00a86fcd01c1c8006d4e6d2930fa8 100644
--- a/hypha/apply/projects/views/project.py
+++ b/hypha/apply/projects/views/project.py
@@ -1234,13 +1234,13 @@ class AdminProjectDetailView(
         context["remaining_document_categories"] = DocumentCategory.objects.filter(
             ~Q(packet_files__project=self.object)
         )
-        context[
-            "all_contract_document_categories"
-        ] = ContractDocumentCategory.objects.all()
-        context[
-            "remaining_contract_document_categories"
-        ] = ContractDocumentCategory.objects.filter(
-            ~Q(contract_packet_files__project=self.object)
+        context["all_contract_document_categories"] = (
+            ContractDocumentCategory.objects.all()
+        )
+        context["remaining_contract_document_categories"] = (
+            ContractDocumentCategory.objects.filter(
+                ~Q(contract_packet_files__project=self.object)
+            )
         )
 
         if (
@@ -1295,13 +1295,13 @@ class ApplicantProjectDetailView(
         context["current_status_index"] = [
             status for status, _ in PROJECT_PUBLIC_STATUSES
         ].index(self.object.status)
-        context[
-            "all_contract_document_categories"
-        ] = ContractDocumentCategory.objects.all()
-        context[
-            "remaining_contract_document_categories"
-        ] = ContractDocumentCategory.objects.filter(
-            ~Q(contract_packet_files__project=self.object)
+        context["all_contract_document_categories"] = (
+            ContractDocumentCategory.objects.all()
+        )
+        context["remaining_contract_document_categories"] = (
+            ContractDocumentCategory.objects.filter(
+                ~Q(contract_packet_files__project=self.object)
+            )
         )
         return context
 
diff --git a/hypha/apply/projects/views/vendor.py b/hypha/apply/projects/views/vendor.py
index 315c3a2f6e45bbb025da1ba1309ab28c82f2e438..2820ea6fec96e0b8152974521fbf3df2ea38ea3c 100644
--- a/hypha/apply/projects/views/vendor.py
+++ b/hypha/apply/projects/views/vendor.py
@@ -215,13 +215,13 @@ class CreateVendorView(CreateVendorAccessMixin, SessionWizardView):
                 }
                 iba_info = bank_info.iba_info
                 if iba_info:
-                    initial_dict["other"][
-                        "ib_account_routing_number"
-                    ] = iba_info.account_routing_number
+                    initial_dict["other"]["ib_account_routing_number"] = (
+                        iba_info.account_routing_number
+                    )
                     initial_dict["other"]["ib_account_number"] = iba_info.account_number
-                    initial_dict["other"][
-                        "ib_account_currency"
-                    ] = iba_info.account_currency
+                    initial_dict["other"]["ib_account_currency"] = (
+                        iba_info.account_currency
+                    )
                     initial_dict["other"]["ib_branch_address"] = iba_info.branch_address
         return initial_dict.get(step, {})
 
diff --git a/hypha/apply/stream_forms/models.py b/hypha/apply/stream_forms/models.py
index 27c0f0cf4a4676a81b5fe71acc0eb4affd60a039..73e2ff22ff3916e77a4b906514ce75ec7b36f94f 100644
--- a/hypha/apply/stream_forms/models.py
+++ b/hypha/apply/stream_forms/models.py
@@ -106,9 +106,9 @@ class BaseStreamForm:
                 if isinstance(block, MultiInputCharFieldBlock):
                     number_of_inputs = struct_value.get("number_of_inputs")
                     for index in range(number_of_inputs):
-                        form_fields[
-                            struct_child.id + "_" + str(index)
-                        ] = field_from_block
+                        form_fields[struct_child.id + "_" + str(index)] = (
+                            field_from_block
+                        )
                         field_from_block.multi_input_id = struct_child.id
                         field_from_block.add_button_text = struct_value.get(
                             "add_button_text"
diff --git a/hypha/settings/example.py b/hypha/settings/example.py
index f5ae8e333da2e3b21570c4dc77620e5d6d80c265..302418b3251badf5ba35568cad85e0129ec18bb3 100644
--- a/hypha/settings/example.py
+++ b/hypha/settings/example.py
@@ -18,5 +18,6 @@ be stored in the DEFAULT_FILE_STORAGE. This may be acceptable if the correct
 access permissions exist to prevent access to the sub-directory of applicant
 media.
 """
+
 DEFAULT_FILE_STORAGE = "path.to.my.StorageClass"
 PRIVATE_FILE_STORAGE = "path.to.my.PrivateStorageClass"
diff --git a/pyproject.toml b/pyproject.toml
index bb863ac598256b8cdf9d8025b86b0d196369d4ef..535cb0b7474a3e55acb6943bb77d735ba7c350f9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,7 @@ omit = [
 ]
 
 # https://github.com/charliermarsh/ruff#ruff
-[tool.ruff]
+[tool.ruff.lint]
 ignore = [
     "E501",  # line too long
     "C901",  # too complex
@@ -45,9 +45,9 @@ select = [
     'W',  # pycodestyle warnings
 ]
 
-[tool.ruff.per-file-ignores]
+[tool.ruff.lint.per-file-ignores]
 "hypha/settings/*.py" = ["F405"]
 "*migrations/*.py" = ["I001"]
 
-[tool.ruff.isort]
+[tool.ruff.lint.isort]
 known-first-party = ["hypha", "addressfield"]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index c4f1ca7c91fd65cf60604655236129b9226ba79d..e076c1b8e1ce04da48c8f0e7d5cf5d212201d63c 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,22 +1,22 @@
 -r requirements.txt
 
-coverage==7.3.2
+coverage==7.4.4
 django-browser-reload==1.12.1
 django-coverage-plugin==3.1.0
-django-debug-toolbar==4.2.0
+django-debug-toolbar==4.3.0
 django-dynamic-fixture==4.0.1
 djhtml==3.0.6
 dslr==0.4.0
 factory_boy==3.2.1
 Faker==19.13.0
-model-bakery==1.10.1
-pre-commit==3.5.0
+model-bakery==1.17.0
+pre-commit==3.6.2
 pytest-cov==4.1.0
-pytest-django==4.7.0
-pytest-split==0.8.1
-pytest-xdist[psutil]==3.3.1
-responses==0.23.3
-ruff==0.1.7
-time-machine==2.13.0
+pytest-django==4.8.0
+pytest-split==0.8.2
+pytest-xdist[psutil]==3.5.0
+responses==0.25.0
+ruff==0.3.3
+time-machine==2.14.0
 wagtail-factories==2.1.0
 Werkzeug==3.0.1
diff --git a/requirements.txt b/requirements.txt
index 1ab5b86ba21da4361635d5ff006df92b0396e979..f32e4424b0efa3089fe61925a12ef8c3cdb826f9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,62 +1,62 @@
 # Monitor dependencies
-scout-apm==2.26.1
-sentry-sdk==1.16.0
+scout-apm==3.1.0
+sentry-sdk==1.42.0
 
 # Production dependencies
-Babel==2.13.1
-boto3==1.28.82
-celery==5.2.7
+Babel==2.14.0
+boto3==1.34.66
+celery==5.3.6
 click==8.1.7
 dj-database-url==2.1.0
-django-anymail==10.2
+django-anymail==10.3
 django-basic-auth-ip-whitelist==0.5
 django-nh3==0.1.1
 django-countries==7.5.1
 django-elevate==2.0.3
 django-extensions==3.2.3
-django-file-form==3.4.3
-django-filter==2.4.0
-django-formtools==2.4.1
+django-file-form==3.6.0
+django-filter==23.5
+django-formtools==2.5.1
 django-fsm==2.8.1
 django-heroku==0.3.1
-django-hijack==3.4.2
-django-htmx==1.17.0
+django-hijack==3.4.5
+django-htmx==1.17.3
 django-pagedown==2.2.1
 # django-pwned-passwords==4.1.0
 https://github.com/slinkymanbyday/django-pwned-passwords/archive/58c7b832df7360a21fd8edeaaf9f897c7517baf1.zip
 django-ratelimit==4.1.0
 django-referrer-policy==1.0
-django-select2==8.0.0
+django-select2==8.1.2
 django-slack==5.19.0
-django-storages==1.13.2
-django-tables2==2.5.1
-django-tinymce==3.5.0
-django-two-factor-auth==1.15.5
-django-web-components==0.1.1
+django-storages==1.14.2
+django-tables2==2.7.0
+django-tinymce==3.7.1
+django-two-factor-auth==1.16.0
+django-web-components==0.2.0
 django==4.2.11
-djangorestframework-api-key==2.3.0
-djangorestframework==3.14.0
-drf-nested-routers==0.93.4
-drf-yasg==1.21.4
-environs==9.5.0
+djangorestframework-api-key==3.0.0
+djangorestframework==3.15.0
+drf-nested-routers==0.93.5
+drf-yasg==1.21.7
+environs==11.0.0
 gunicorn==21.2.0
 heroicons==2.6.0
-python-docx<1.0.0
+python-docx==1.1.0
 htmldocx==0.0.6
-lark==1.1.8
-mistune==3.0.1
-more-itertools==10.1.0
-phonenumberslite==8.13.26
-Pillow>=10.0.1
-psycopg[binary]==3.1.14
+lark==1.1.9
+mistune==3.0.2
+more-itertools==10.2.0
+phonenumberslite==8.13.32
+Pillow==10.2.0
+psycopg[binary]==3.1.18
 qrcode==7.4.2
-reportlab==3.6.13
-social_auth_app_django==5.0.0
+reportlab==4.0.9
+social_auth_app_django==5.4.0
 tablib==3.5.0
 tomd==0.1.3
-wagtail-cache==2.3.0
+wagtail-cache==2.4.0
 wagtail-purge==0.3.0
 wagtail==5.1.3
 whitenoise==6.6.0
-xhtml2pdf==0.2.11
+xhtml2pdf==0.2.15
 xmltodict==0.13.0