diff --git a/reviewboard/reviews/apps.py b/reviewboard/reviews/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..a051f1dbfd81c557449da301b774314026060680
--- /dev/null
+++ b/reviewboard/reviews/apps.py
@@ -0,0 +1,27 @@
+"""The app definition for reviewboard.reviews.
+
+Version Added:
+    6.0
+"""
+
+from __future__ import annotations
+
+from django.apps import AppConfig
+
+
+class ReviewsAppConfig(AppConfig):
+    """App configuration for reviewboard.reviews."""
+
+    name = 'reviewboard.reviews'
+
+    def ready(self) -> None:
+        """Configure the app once it's ready.
+
+        This will connect signal handlers for the app.
+
+        Version Added:
+            6.0
+        """
+        from reviewboard.reviews.signal_handlers import connect_signal_handlers
+
+        connect_signal_handlers()
diff --git a/reviewboard/reviews/signal_handlers.py b/reviewboard/reviews/signal_handlers.py
new file mode 100644
index 0000000000000000000000000000000000000000..9393e519b9263c4a709c4239407994633e0c23f3
--- /dev/null
+++ b/reviewboard/reviews/signal_handlers.py
@@ -0,0 +1,70 @@
+"""Signal handlers for review and review request related objects.
+
+Version Added:
+    6.0
+"""
+
+from __future__ import annotations
+
+from typing import Type
+
+from django.db.models.signals import pre_delete
+
+from reviewboard.reviews.models import ReviewRequestDraft
+from reviewboard.reviews.models.review_request import FileAttachmentState
+
+
+def _on_review_request_draft_deleted(
+    sender: Type[ReviewRequestDraft],
+    instance: ReviewRequestDraft,
+    using: str,
+    **kwargs,
+) -> None:
+    """Handle any extra cleanup when a review request draft is deleted.
+
+    Version Added:
+        6.0
+
+    Args:
+        sender (type, unused):
+            The sender of the signal.
+
+        instance (reviewboard.reviews.models.ReviewRequestDraft):
+            The review request draft being deleted.
+
+        using (str, unused):
+            The database alias being used.
+
+        **kwargs (dict, unused):
+            Unused additional keyword arguments.
+    """
+    draft_attachments = instance.get_file_attachments()
+
+    if draft_attachments:
+        # Load the data for the file attachments states.
+        instance.get_file_attachments_data(
+            draft_active_attachments=draft_attachments)
+
+    for attachment in draft_attachments:
+        state = instance.get_file_attachment_state(attachment)
+
+        if state in (FileAttachmentState.NEW_REVISION,
+                     FileAttachmentState.NEW):
+            # This never has been and never will be published, so delete it.
+            attachment.delete()
+
+    review_request = instance.get_review_request()
+
+    if hasattr(review_request, '_file_attachments_data'):
+        # Clear the cached file attachments data.
+        del review_request._file_attachments_data
+
+
+def connect_signal_handlers() -> None:
+    """Connect review and review request related signal handlers.
+
+    Version Added:
+        6.0
+    """
+    pre_delete.connect(_on_review_request_draft_deleted,
+                       sender=ReviewRequestDraft)
diff --git a/reviewboard/reviews/tests/test_signal_handlers.py b/reviewboard/reviews/tests/test_signal_handlers.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6602db1ee55fbcab78b37e232266f881128259b
--- /dev/null
+++ b/reviewboard/reviews/tests/test_signal_handlers.py
@@ -0,0 +1,103 @@
+"""Tests for reviewboard.reviews.signal_handlers.
+
+Version Added:
+    6.0
+"""
+
+from __future__ import annotations
+
+import kgb
+
+from reviewboard.attachments.models import FileAttachment
+from reviewboard.reviews.signal_handlers import \
+    _on_review_request_draft_deleted
+from reviewboard.testing import TestCase
+
+
+class OnReviewRequestDraftDeletedTests(kgb.SpyAgency, TestCase):
+    """Tests _on_review_request_draft_deleted() signal handler.
+
+    Version Added:
+        6.0
+    """
+
+    fixtures = ['test_scmtools', 'test_users']
+
+    def setUp(self) -> None:
+        """Set up the test case."""
+        super().setUp()
+        self.review_request = self.create_review_request(
+            create_repository=True,
+            publish=True)
+        self.draft = self.create_review_request_draft(self.review_request)
+
+    def test_with_file_attachments(self) -> None:
+        """Testing _on_review_request_draft_deleted deletes new and new
+        revision draft file attachments
+        """
+        self.spy_on(_on_review_request_draft_deleted)
+
+        published = self.create_file_attachment(self.review_request)
+        new = self.create_file_attachment(self.review_request, draft=True)
+        new_revision = self.create_file_attachment(
+            self.review_request,
+            attachment_history=published.attachment_history,
+            attachment_revision=published.attachment_revision + 1,
+            draft=True)
+
+        # 34 queries:
+        #
+        #   1-7. Fetch review request draft info and relations
+        #  8-13. Fetch file attachments info
+        # 14-17. Build file attachments data for getting states
+        # 18-33. Delete the file attachments from the review request draft
+        #    34. Delete the review request draft
+        with self.assertNumQueries(34):
+            self.draft.delete()
+
+        all_attachments = FileAttachment.objects.all()
+
+        self.assertSpyCalled(_on_review_request_draft_deleted)
+        self.assertNotIn(new, all_attachments)
+        self.assertNotIn(new_revision, all_attachments)
+        self.assertIn(published, all_attachments)
+
+    def test_with_one_file_attachment(self) -> None:
+        """Testing _on_review_request_draft_deleted deletes a new file
+        attachment
+        """
+        self.spy_on(_on_review_request_draft_deleted)
+
+        published = self.create_file_attachment(self.review_request)
+        new = self.create_file_attachment(self.review_request, draft=True)
+
+        # 23 queries:
+        #
+        #   1-7. Fetch review request draft info and relations
+        #  8-13. Fetch file attachments info
+        # 14-17. Build file attachments data for getting states
+        # 18-22. Delete the file attachment from the review request draft
+        #    23. Delete the review request draft
+        with self.assertNumQueries(23):
+            self.draft.delete()
+
+        all_attachments = FileAttachment.objects.all()
+
+        self.assertSpyCalled(_on_review_request_draft_deleted)
+        self.assertNotIn(new, all_attachments)
+        self.assertIn(published, all_attachments)
+
+    def test_with_no_file_attachments(self) -> None:
+        """Testing _on_review_request_draft_deleted when there's no
+        draft file attachments on the review request
+        """
+        self.spy_on(_on_review_request_draft_deleted)
+
+        # 8 queries:
+        #
+        # 1-7. Fetch review request draft info and relations
+        #   8. Delete the review request draft
+        with self.assertNumQueries(8):
+            self.draft.delete()
+
+        self.assertSpyCalled(_on_review_request_draft_deleted)
