diff --git a/reviewboard/accounts/backends/__init__.py b/reviewboard/accounts/backends/__init__.py
index 009a8905abab230e936e49a9ea06e9bba71659bc..e62352e1b94c678bfec5739d14eb2b004455ce8b 100644
--- a/reviewboard/accounts/backends/__init__.py
+++ b/reviewboard/accounts/backends/__init__.py
@@ -36,6 +36,7 @@ from reviewboard.accounts.backends.registry import (AuthBackendRegistry,
                                                     get_enabled_auth_backends)
 from reviewboard.accounts.backends.standard import StandardAuthBackend
 from reviewboard.accounts.backends.x509 import X509Backend
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 
 
 # Legacy references.
@@ -64,7 +65,7 @@ def get_registered_auth_backends():
     warn('reviewboard.accounts.backends.get_registered_auth_backends() is '
          'deprecated. Iterate over '
          'reviewboard.accounts.backends.auth_backends instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
 
     for backend in auth_backends:
         yield backend
@@ -90,7 +91,7 @@ def get_registered_auth_backend(backend_id):
     warn('reviewboard.accounts.backends.get_registered_auth_backend() is '
          'deprecated. Use '
          'reviewboard.accounts.backends.auth_backends.register() instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
 
     return auth_backends.get('backend_id', backend_id)
 
@@ -121,7 +122,7 @@ def register_auth_backend(backend_cls):
     warn('reviewboard.accounts.backends.register_auth_backend() is '
          'deprecated. Use '
          'reviewboard.accounts.backends.auth_backends.register() instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
 
     auth_backends.register(backend_cls)
 
@@ -144,7 +145,7 @@ def unregister_auth_backend(backend_cls):
     warn('reviewboard.accounts.backends.unregister_auth_backend() is '
          'deprecated. Use '
          'reviewboard.accounts.backends.auth_backends.unregister() instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
 
     auth_backends.unregister(backend_cls)
 
diff --git a/reviewboard/accounts/backends/base.py b/reviewboard/accounts/backends/base.py
index 9b1a9c60d16277338d86e88d7ac55bc1df103832..1dce2529fed4568ffd2bb90b95fc8ce81435caef 100644
--- a/reviewboard/accounts/backends/base.py
+++ b/reviewboard/accounts/backends/base.py
@@ -8,6 +8,8 @@ import warnings
 from django.contrib.auth.models import User
 from djblets.db.query import get_object_or_none
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
+
 
 class BaseAuthBackend(object):
     """Base class for a Review Board authentication backend."""
@@ -199,7 +201,7 @@ class BaseAuthBackend(object):
                           'rename it and change the function signature to '
                           'that of query_users().'
                           % self.__class__.__name__,
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
 
             self.query_users(query, request)
 
@@ -239,7 +241,7 @@ class BaseAuthBackend(object):
                           'rename it and change the function signature to '
                           'that of build_search_users_query().'
                           % self.__class__.__name__,
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
 
             return self.search_users(query, request)
 
diff --git a/reviewboard/accounts/backends/registry.py b/reviewboard/accounts/backends/registry.py
index 488d7c7a0ff2de1220b3258e157c2fe3c96c775f..b0555cea86a352ff2d92f13781811c74865213a3 100644
--- a/reviewboard/accounts/backends/registry.py
+++ b/reviewboard/accounts/backends/registry.py
@@ -13,6 +13,7 @@ from djblets.registries.registry import (ALREADY_REGISTERED, LOAD_ENTRY_POINT,
 
 from reviewboard.accounts.backends.base import BaseAuthBackend
 from reviewboard.accounts.backends.standard import StandardAuthBackend
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.registries.registry import EntryPointRegistry
 
 
@@ -152,7 +153,7 @@ def get_enabled_auth_backends():
                      'reviewboard.accounts.backends.base.BaseAuthBackend. '
                      'Please update %s.'
                      % backend.__class__,
-                     DeprecationWarning)
+                     RemovedInReviewBoard40Warning)
 
                 for field, default in (('name', None),
                                        ('supports_registration', False),
@@ -164,7 +165,7 @@ def get_enabled_auth_backends():
                              "attribute. Please define it in %s or inherit "
                              "from BaseAuthBackend."
                              % (field, backend.__class__),
-                             DeprecationWarning)
+                             RemovedInReviewBoard40Warning)
 
                         setattr(backend, field, False)
 
diff --git a/reviewboard/accounts/pages.py b/reviewboard/accounts/pages.py
index 98ceed240be1429b5078eeb13db5d8ac9f1591d0..332c8edba9e665a0d05ada263df978d0ecabd1f5 100644
--- a/reviewboard/accounts/pages.py
+++ b/reviewboard/accounts/pages.py
@@ -21,6 +21,7 @@ from reviewboard.accounts.forms.pages import (AccountSettingsForm,
                                               OAuthApplicationsForm,
                                               OAuthTokensForm,
                                               ProfileForm)
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 
 
 class AccountPageRegistry(ExceptionFreeGetterMixin, ConfigPageRegistry):
@@ -159,7 +160,7 @@ def register_account_page_class(cls):
     """
     warn('register_account_page_class is deprecated in Review Board 3.0 and '
          'will be removed; use AccountPage.registry.register instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
     AccountPage.registry.register(cls)
 
 
@@ -173,7 +174,7 @@ def unregister_account_page_class(page_cls):
     """
     warn('unregister_account_page_class is deprecated in Review Board 3.0 and '
          'will be removed; use AccountPage.registry.unregister instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
     AccountPage.registry.unregister(page_cls)
 
 
@@ -191,7 +192,7 @@ def get_page_class(page_id):
     """
     warn('get_page_class is deprecated in Review Board 3.0 and will be '
          'removed; use AccountPage.registry.get instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
     return AccountPage.registry.get('page_id', page_id)
 
 
@@ -204,5 +205,5 @@ def get_page_classes():
     """
     warn('get_page_classes is deprecated in Review Board 3.0 and will be '
          'removed; iterate through AccountPage.registry instead.',
-         DeprecationWarning)
+         RemovedInReviewBoard40Warning)
     return iter(AccountPage.registry)
diff --git a/reviewboard/accounts/trophies.py b/reviewboard/accounts/trophies.py
index 406082c7803147e02886eb763960755658cdf591..5a455fc90b46ddbf9306534a5e5cc29cc16f7fba 100644
--- a/reviewboard/accounts/trophies.py
+++ b/reviewboard/accounts/trophies.py
@@ -16,6 +16,8 @@ from djblets.registries.registry import (ALREADY_REGISTERED,
 from djblets.urls.staticfiles import static_lazy
 from djblets.util.decorators import augment_method_from
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
+
 
 class TrophyType(object):
     """Base class for a type of trophy.
@@ -97,7 +99,7 @@ class TrophyType(object):
             warnings.warn('%r should define "name" as a class attribute '
                           'instead of passing "title" to the constructor.'
                           % self.__class__,
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
 
             self.name = title
 
@@ -105,7 +107,7 @@ class TrophyType(object):
             warnings.warn('%r should define "image_urls" as a class attribute '
                           'instead of passing "image_url" to the constructor.'
                           % self.__class__,
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
 
             self.image_urls = {
                 '1x': image_url,
@@ -116,7 +118,7 @@ class TrophyType(object):
                           'attribute instead of passing "image_width" to '
                           'the constructor.'
                           % self.__class__,
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
 
             self.image_width = image_width or 32
 
@@ -125,7 +127,7 @@ class TrophyType(object):
                           'attribute instead of passing "image_height" to '
                           'the constructor.'
                           % self.__class__,
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
 
             self.image_height = image_height or 48
 
@@ -187,7 +189,7 @@ class TrophyType(object):
                 warnings.warn(
                     'TrophyType.get_display_text has been deprecated in favor '
                     'of TrophyType.format_display_text.',
-                    DeprecationWarning)
+                    RemovedInReviewBoard40Warning)
                 return text
 
         return self.display_format_str % dict(kwargs, **{
@@ -404,7 +406,7 @@ def register_trophy(trophy):
     warnings.warn('register_trophy() is deprecated. Please use '
                   'reviewboard.accounts.trophies:trophies_registry.register() '
                   'instead.',
-                  DeprecationWarning)
+                  RemovedInReviewBoard40Warning)
 
     if not issubclass(trophy, TrophyType):
         raise TypeError('Only TrophyType subclasses can be registered')
@@ -442,7 +444,7 @@ def unregister_trophy(trophy):
     warnings.warn('unregister_trophy() is deprecated. Please use '
                   'reviewboard.accounts.trophies:trophies_registry'
                   '.unregister() instead.',
-                  DeprecationWarning)
+                  RemovedInReviewBoard40Warning)
 
     if not issubclass(trophy, TrophyType):
         raise TypeError('Only TrophyType subclasses can be unregistered')
@@ -468,7 +470,7 @@ def get_registered_trophy_types():
     warnings.warn('get_registered_trophy_types() is deprecated. Please '
                   'iterate through '
                   'reviewboard.accounts.trophies:trophies_registry instead.',
-                  DeprecationWarning)
+                  RemovedInReviewBoard40Warning)
 
     return {
         trophy.category: trophy
diff --git a/reviewboard/deprecation.py b/reviewboard/deprecation.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a678645b948c20675582ba56a5f0e823d3635bd
--- /dev/null
+++ b/reviewboard/deprecation.py
@@ -0,0 +1,32 @@
+"""Internal support for handling deprecations in Review Board.
+
+The version-specific objects in this module are not considered stable between
+releases, and may be removed at any point. The base objects are considered
+stable.
+"""
+
+from __future__ import unicode_literals
+
+
+class BaseRemovedInReviewBoardVersionWarning(DeprecationWarning):
+    """Base class for a Review Board deprecation warning.
+
+    All version-specific deprecation warnings inherit from this, allowing
+    callers to check for Review Board deprecations without being tied to a
+    specific version.
+    """
+
+
+class RemovedInReviewBoard40Warning(BaseRemovedInReviewBoardVersionWarning):
+    """Deprecations for features removed in Review Board 4.0.
+
+    Note that this class will itself be removed in Review Board 4.0. If you
+    need to check against Review Board deprecation warnings, please see
+    :py:class:`BaseRemovedInReviewBoardVersionWarning`. Alternatively, you
+    can use the alias for this class,
+    :py:data:`RemovedInNextReviewBoardVersionWarning`.
+    """
+
+
+#: An alias for the next release of Djblets where features would be removed.
+RemovedInNextReviewBoardVersionWarning = RemovedInReviewBoard40Warning
diff --git a/reviewboard/diffviewer/managers.py b/reviewboard/diffviewer/managers.py
index f1a1d89273a9edbeac10034c791f19bbdc55b023..b66d7606b46c50391b2cbd8f6b877dc302ac2d08 100644
--- a/reviewboard/diffviewer/managers.py
+++ b/reviewboard/diffviewer/managers.py
@@ -15,6 +15,7 @@ from django.utils.six.moves import range
 from django.utils.translation import ugettext as _
 from djblets.siteconfig.models import SiteConfiguration
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.diffviewer.differ import DiffCompatVersion
 from reviewboard.diffviewer.errors import DiffTooBigError, EmptyDiffError
 from reviewboard.scmtools.core import PRE_CREATION, UNKNOWN, FileNotFoundError
@@ -471,7 +472,7 @@ class DiffSetManager(models.Manager):
             warnings.warn('The save parameter to '
                           'DiffSet.objects.create_from_upload is deprecated. '
                           'Please set validate_only instead.',
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
             validate_only = not kwargs['save']
 
         siteconfig = SiteConfiguration.objects.get_current()
@@ -598,7 +599,7 @@ class DiffSetManager(models.Manager):
             warnings.warn('The save parameter to '
                           'DiffSet.objects.create_from_data is deprecated. '
                           'Please set validate_only instead.',
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
             validate_only = not kwargs['save']
 
         tool = repository.get_scmtool()
diff --git a/reviewboard/extensions/hooks.py b/reviewboard/extensions/hooks.py
index 6bc8a3025773518c21212d326d88c2ac3a0e244b..ddd118bb7659330a9706a501a8be199ab7cc53c3 100644
--- a/reviewboard/extensions/hooks.py
+++ b/reviewboard/extensions/hooks.py
@@ -29,6 +29,7 @@ from reviewboard.attachments.mimetypes import (register_mimetype_handler,
 from reviewboard.avatars import avatar_services
 from reviewboard.datagrids.grids import (DashboardDataGrid,
                                          UserPageReviewRequestDataGrid)
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.hostingsvcs.service import (register_hosting_service,
                                              unregister_hosting_service)
 from reviewboard.integrations.base import GetIntegrationManagerMixin
@@ -444,7 +445,7 @@ class NavigationBarHook(ExtensionHook):
                     'a function without keyword arguments by %r. This '
                     'is deprecated.'
                     % self.extension,
-                    DeprecationWarning)
+                    RemovedInReviewBoard40Warning)
 
                 self.is_enabled_for = \
                     lambda user, **kwargs: is_enabled_for(user)
diff --git a/reviewboard/hostingsvcs/tests/testcases.py b/reviewboard/hostingsvcs/tests/testcases.py
index d7c5690cd2345019978add0ad128fc157905683a..623bb1bce679ba2e5312320175a1410d98294359 100644
--- a/reviewboard/hostingsvcs/tests/testcases.py
+++ b/reviewboard/hostingsvcs/tests/testcases.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
 
 import warnings
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.hostingsvcs.testing import HostingServiceTestCase
 
 
@@ -19,7 +20,7 @@ class ServiceTests(HostingServiceTestCase):
     def setUpClass(cls):
         warnings.warn('ServiceTests is deprecated. Subclass '
                       'HostingServiceTestCase instead.',
-                      DeprecationWarning)
+                      RemovedInReviewBoard40Warning)
 
         super(ServiceTests, cls).setUpClass()
 
@@ -31,7 +32,7 @@ class ServiceTests(HostingServiceTestCase):
         warnings.warn(
             'ServiceTests._get_form() is deprecated. Use '
             'HostingServiceTestCase.get_form() instead.',
-            DeprecationWarning)
+            RemovedInReviewBoard40Warning)
 
         return self.get_form(*args, **kwargs)
 
@@ -39,7 +40,7 @@ class ServiceTests(HostingServiceTestCase):
         warnings.warn(
             'ServiceTests._get_hosting_account() is deprecated. Use '
             'HostingServiceTestCase.create_hosting_account() instead.',
-            DeprecationWarning)
+            RemovedInReviewBoard40Warning)
 
         kwargs['data'] = {}
         return self.create_hosting_account(*args, **kwargs)
@@ -48,6 +49,6 @@ class ServiceTests(HostingServiceTestCase):
         warnings.warn(
             'ServiceTests._get_repository_fields() is deprecated. Use '
             'HostingServiceTestCase.get_repository_fields() instead.',
-            DeprecationWarning)
+            RemovedInReviewBoard40Warning)
 
         return self.get_repository_fields(*args, **kwargs)
diff --git a/reviewboard/notifications/email/hooks.py b/reviewboard/notifications/email/hooks.py
index ca959a5e9d78e4fa5ee506dc0724369e776aee09..4d699442205a6ee9edbb5cda95aff4a87a2c6a1e 100644
--- a/reviewboard/notifications/email/hooks.py
+++ b/reviewboard/notifications/email/hooks.py
@@ -6,6 +6,7 @@ import warnings
 from collections import defaultdict
 from inspect import getargspec
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.reviews.signals import (review_request_published,
                                          review_published, reply_published,
                                          review_request_closed)
@@ -156,7 +157,7 @@ def _call_hook_compat(hook, method, optional_args, value, **kwargs):
             '%s.%s should accept **kwargs. Some arguments may not be passed '
             'to the hook.'
             % (hook.__name__, method.__name__),
-            DeprecationWarning
+            RemovedInReviewBoard40Warning
         )
 
     return method(value, **kwargs)
diff --git a/reviewboard/reviews/markdown_utils.py b/reviewboard/reviews/markdown_utils.py
index dc96335dd7620c2cdc981680e0e283e12596a434..52d19e019f6a429940c1f8112b728dd2006218bb 100644
--- a/reviewboard/reviews/markdown_utils.py
+++ b/reviewboard/reviews/markdown_utils.py
@@ -10,6 +10,8 @@ from djblets import markdown as djblets_markdown
 from djblets.siteconfig.models import SiteConfiguration
 from markdown import markdown
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
+
 
 # Keyword arguments used when calling a Markdown renderer function.
 #
@@ -55,7 +57,7 @@ def markdown_escape(text):
     """
     warnings.warn('reviewboard.reviews.markdown_utils.markdown_escape is '
                   'deprecated. Please use djblets.markdown.markdown_escape.',
-                  DeprecationWarning)
+                  RemovedInReviewBoard40Warning)
 
     return djblets_markdown.markdown_escape(text)
 
@@ -70,7 +72,7 @@ def markdown_unescape(escaped_text):
     """
     warnings.warn('reviewboard.reviews.markdown_utils.markdown_unescape is '
                   'deprecated. Please use djblets.markdown.markdown_unescape.',
-                  DeprecationWarning)
+                  RemovedInReviewBoard40Warning)
 
     return djblets_markdown.markdown_unescape(escaped_text)
 
@@ -176,7 +178,7 @@ def iter_markdown_lines(markdown_html):
     warnings.warn(
         'reviewboard.reviews.markdown_utils.iter_markdown_lines is '
         'deprecated. Please use djblets.markdown.iter_markdown_lines.',
-        DeprecationWarning)
+        RemovedInReviewBoard40Warning)
 
     return djblets_markdown.iter_markdown_lines(markdown_html)
 
@@ -193,7 +195,7 @@ def get_markdown_element_tree(markdown_html):
     warnings.warn(
         'reviewboard.reviews.markdown_utils.get_markdown_element_tree is '
         'deprecated. Please use djblets.markdown.get_markdown_element_tree.',
-        DeprecationWarning)
+        RemovedInReviewBoard40Warning)
 
     return djblets_markdown.get_markdown_element_tree(markdown_html)
 
@@ -214,7 +216,7 @@ def sanitize_illegal_chars_for_xml(s):
         'reviewboard.reviews.markdown_utils.sanitize_illegal_chars_for_xml '
         'is deprecated. Please use '
         'djblets.markdown.sanitize_illegal_chars_for_xml.',
-        DeprecationWarning)
+        RemovedInReviewBoard40Warning)
 
     return djblets_markdown.sanitize_illegal_chars_for_xml(s)
 
diff --git a/reviewboard/reviews/models/review_request.py b/reviewboard/reviews/models/review_request.py
index 35e6254c3e650b7f5b2beb1eae7576f849781507..130c34cdf881fd3a91592237b4f4b6dd03091d9a 100644
--- a/reviewboard/reviews/models/review_request.py
+++ b/reviewboard/reviews/models/review_request.py
@@ -14,10 +14,12 @@ from djblets.cache.backend import make_cache_key
 from djblets.db.fields import (CounterField, ModificationTimestampField,
                                RelationCounterField)
 from djblets.db.query import get_object_or_none
+from djblets.deprecation import deprecated_arg_value
 
 from reviewboard.attachments.models import (FileAttachment,
                                             FileAttachmentHistory)
 from reviewboard.changedescs.models import ChangeDescription
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.diffviewer.models import DiffSet, DiffSetHistory
 from reviewboard.reviews.errors import (PermissionError,
                                         PublishError)
@@ -35,7 +37,6 @@ from reviewboard.reviews.signals import (review_request_closed,
                                          review_request_reopened,
                                          review_request_reopening)
 from reviewboard.scmtools.models import Repository
-from reviewboard.signals import deprecated_signal_argument
 from reviewboard.site.models import LocalSite
 from reviewboard.site.urlresolvers import local_site_reverse
 
@@ -590,7 +591,7 @@ class ReviewRequest(BaseReviewRequestDetails):
         review request has been made public more recently.
 
         Deprecated:
-            4.0:
+            3.0:
             See :py:meth:`get_last_activity_info` instead.
 
         Args:
@@ -616,9 +617,9 @@ class ReviewRequest(BaseReviewRequestDetails):
         """
         warnings.warn(
             'ReviewRequest.get_last_activity is deprecated in Review Board '
-            '4.0 and will be removed in a future release. Please use '
+            '3.0 and will be removed in a future release. Please use '
             'ReviewRequest.get_last_activity_info instead.',
-            DeprecationWarning)
+            RemovedInReviewBoard40Warning)
 
         info = self.get_last_activity_info(diffsets=diffsets, reviews=reviews)
         return info['timestamp'], info['updated_object']
@@ -856,7 +857,7 @@ class ReviewRequest(BaseReviewRequestDetails):
         """
         warnings.warn('ReviewRequest.get_close_description() is deprecated. '
                       'Use ReviewRequest.get_close_info().',
-                      DeprecationWarning)
+                      RemovedInReviewBoard40Warning)
 
         close_info = self.get_close_info()
         return (close_info['close_description'], close_info['is_rich_text'])
@@ -974,11 +975,12 @@ class ReviewRequest(BaseReviewRequestDetails):
             user=user,
             review_request=self,
             close_type=close_type,
-            type=deprecated_signal_argument(
-                signal_name='review_request_closing',
-                old_name='type',
-                new_name='close_type',
-                value=close_type),
+            type=deprecated_arg_value(
+                owner_name='review_request_closing',
+                old_arg_name='type',
+                new_arg_name='close_type',
+                value=close_type,
+                warning_cls=RemovedInReviewBoard40Warning),
             description=description,
             rich_text=rich_text)
 
@@ -1017,11 +1019,12 @@ class ReviewRequest(BaseReviewRequestDetails):
                 user=user,
                 review_request=self,
                 close_type=close_type,
-                type=deprecated_signal_argument(
-                    signal_name='review_request_closed',
-                    old_name='type',
-                    new_name='close_type',
-                    value=close_type),
+                type=deprecated_arg_value(
+                    owner_name='review_request_closed',
+                    old_arg_name='type',
+                    new_arg_name='close_type',
+                    value=close_type,
+                    warning_cls=RemovedInReviewBoard40Warning),
                 description=description,
                 rich_text=rich_text)
         else:
diff --git a/reviewboard/reviews/templatetags/reviewtags.py b/reviewboard/reviews/templatetags/reviewtags.py
index 213a9c8f54956ead50cf59439a09f35b810654c1..4a1332a687807d31ce2964b2dc686e8149e7e8ec 100644
--- a/reviewboard/reviews/templatetags/reviewtags.py
+++ b/reviewboard/reviews/templatetags/reviewtags.py
@@ -18,6 +18,7 @@ from djblets.util.templatetags.djblets_js import json_dumps_items
 
 from reviewboard.accounts.models import Profile, Trophy
 from reviewboard.accounts.trophies import UnknownTrophy
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.diffviewer.diffutils import get_displayed_diff_line_ranges
 from reviewboard.reviews.actions import get_top_level_actions
 from reviewboard.reviews.fields import (get_review_request_field,
@@ -380,7 +381,7 @@ def for_review_request_field(context, nodelist, review_request_details,
                           'which is deprecated and will be removed in the '
                           'future. This should be converted to a property.'
                           % field_cls,
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
             try:
                 should_render = field.should_render(field.value)
             except Exception as e:
diff --git a/reviewboard/reviews/tests/test_review_request.py b/reviewboard/reviews/tests/test_review_request.py
index ffe3eae06d50ad68015476302cf0814397797744..c6385c0ac39ac904afc2e7cb38c6613b6fbda087 100644
--- a/reviewboard/reviews/tests/test_review_request.py
+++ b/reviewboard/reviews/tests/test_review_request.py
@@ -8,6 +8,7 @@ from djblets.testing.decorators import add_fixtures
 from kgb import SpyAgency
 
 from reviewboard.changedescs.models import ChangeDescription
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.diffviewer.models import DiffSet
 from reviewboard.reviews.errors import PublishError
 from reviewboard.reviews.models import (Comment, ReviewRequest,
@@ -32,7 +33,8 @@ class ReviewRequestTests(SpyAgency, TestCase):
         with catch_warnings(record=True) as w:
             review_request.get_close_description()
             self.assertEqual(len(w), 1)
-            self.assertTrue(issubclass(w[0].category, DeprecationWarning))
+            self.assertTrue(issubclass(w[0].category,
+                                       RemovedInReviewBoard40Warning))
             self.assertIn("deprecated", six.text_type(w[0].message))
 
     def test_get_close_info_returns_correct_information(self):
diff --git a/reviewboard/scmtools/core.py b/reviewboard/scmtools/core.py
index 7d4e20cac3363d3ed2da568249a73587697314bc..51b1a089afea4b37f0a6fc3e48c735d5979c3900 100644
--- a/reviewboard/scmtools/core.py
+++ b/reviewboard/scmtools/core.py
@@ -18,6 +18,7 @@ from django.utils.six.moves.urllib.request import (Request as URLRequest,
 from django.utils.translation import ugettext_lazy as _
 
 import reviewboard.diffviewer.parser as diffparser
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.scmtools.errors import (AuthenticationError,
                                          FileNotFoundError,
                                          SCMError)
@@ -512,7 +513,7 @@ class SCMTool(object):
                           % {
                               'class_name': self.__class__.__name__,
                           },
-                          DeprecationWarning)
+                          RemovedInReviewBoard40Warning)
 
             return self.get_diffs_use_absolute_paths()
         else:
@@ -598,7 +599,8 @@ class SCMTool(object):
             if argspec.keywords is None:
                 warnings.warn('SCMTool.get_file() must take keyword '
                               'arguments, signature for %s is deprecated.'
-                              % self.name, DeprecationWarning)
+                              % self.name,
+                              RemovedInReviewBoard40Warning)
                 self.get_file(path, revision)
             else:
                 self.get_file(path, revision, base_commit_id=base_commit_id)
diff --git a/reviewboard/scmtools/crypto_utils.py b/reviewboard/scmtools/crypto_utils.py
index 753e9b9279f14bd2f8ca8a54405a2c33d896e70e..924ce84e4d10d65b9eb242c92f84a97aedd6ffaf 100644
--- a/reviewboard/scmtools/crypto_utils.py
+++ b/reviewboard/scmtools/crypto_utils.py
@@ -9,6 +9,8 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from django.conf import settings
 from django.utils import six
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
+
 
 AES_BLOCK_SIZE = algorithms.AES.block_size / 8
 
@@ -195,7 +197,7 @@ def decrypt(data):
         The decrypted value.
     """
     warnings.warn('decrypt() is deprecated. Use aes_decrypt() instead.',
-                  DeprecationWarning)
+                  RemovedInReviewBoard40Warning)
 
     return aes_decrypt(data)
 
@@ -217,6 +219,6 @@ def encrypt(data):
         The resulting encrypted value, with the random IV prepended.
     """
     warnings.warn('encrypt() is deprecated. Use aes_encrypt() instead.',
-                  DeprecationWarning)
+                  RemovedInReviewBoard40Warning)
 
     return aes_encrypt(data)
diff --git a/reviewboard/scmtools/models.py b/reviewboard/scmtools/models.py
index 1f9bb3b58a93ffecf0cdc860dab4b8ec44a6f197..1c31a95ee0b57060d8641adc8d6a0d10e9a89a9b 100644
--- a/reviewboard/scmtools/models.py
+++ b/reviewboard/scmtools/models.py
@@ -21,6 +21,7 @@ from djblets.cache.backend import cache_memoize, make_cache_key
 from djblets.db.fields import JSONField
 from djblets.log import log_timed
 
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
 from reviewboard.hostingsvcs.models import HostingServiceAccount
 from reviewboard.hostingsvcs.service import get_hosting_service
 from reviewboard.scmtools.crypto_utils import (decrypt_password,
@@ -590,7 +591,8 @@ class Repository(models.Model):
             if argspec.keywords is None:
                 warnings.warn('SCMTool.get_file() must take keyword '
                               'arguments, signature for %s is deprecated.'
-                              % tool.name, DeprecationWarning)
+                              % tool.name,
+                              RemovedInReviewBoard40Warning)
                 data = tool.get_file(path, revision)
             else:
                 data = tool.get_file(path, revision,
@@ -647,7 +649,8 @@ class Repository(models.Model):
                 if argspec.keywords is None:
                     warnings.warn('SCMTool.file_exists() must take keyword '
                                   'arguments, signature for %s is deprecated.'
-                                  % tool.name, DeprecationWarning)
+                                  % tool.name,
+                                  RemovedInReviewBoard40Warning)
                     exists = tool.file_exists(path, revision)
                 else:
                     exists = tool.file_exists(path, revision,
diff --git a/reviewboard/signals.py b/reviewboard/signals.py
index 02ffbbe02dace47aab033a82f4bceac2ac005df9..19176f254249a27a200cb3b305eb67067cd540c3 100644
--- a/reviewboard/signals.py
+++ b/reviewboard/signals.py
@@ -5,7 +5,10 @@ from __future__ import unicode_literals
 import warnings
 
 from django.dispatch import Signal
-from django.utils.functional import SimpleLazyObject
+from djblets.deprecation import deprecated_arg_value
+
+from reviewboard.deprecation import RemovedInReviewBoard40Warning
+
 
 #: Emitted when the initialization of Review Board is complete.
 #:
@@ -42,12 +45,13 @@ def deprecated_signal_argument(signal_name, old_name, new_name, value):
         The value wrapped in a lazy object. The first time it is casted as
         :py:class:`unicode` a warning will be emitted.
     """
-    def warn_on_use():
-        warnings.warn('The "%s" signal argument for "%s" has been deprecated '
-                      'and will be removed in a future version; use "%s" '
-                      'instead.'
-                      % (old_name, signal_name, new_name),
-                      DeprecationWarning)
-        return value
-
-    return SimpleLazyObject(warn_on_use)
+    warnings.warn('deprecated_signal_argument has been deprecated and will '
+                  'be removed in Review Board 4.0. Use '
+                  'djblets.deprecation.deprecated_arg_value instead.',
+                  RemovedInReviewBoard40Warning)
+
+    return deprecated_arg_value(owner_name=signal_name,
+                                old_arg_name=old_name,
+                                new_arg_name=new_name,
+                                value=value,
+                                warning_cls=RemovedInReviewBoard40Warning)
