diff --git a/djblets/template/loaders/conditional_cached.py b/djblets/template/loaders/conditional_cached.py
new file mode 100644
index 0000000000000000000000000000000000000000..9223377e3385ca012ab77d931139d7edd5270d41
--- /dev/null
+++ b/djblets/template/loaders/conditional_cached.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.template.loaders import cached
+
+
+class Loader(cached.Loader):
+    """Caches template loading results only if not in DEBUG mode.
+
+    This extends Django's built-in 'cached' template loader to only
+    perform caching if ``settings.DEBUG`` is False. That helps to keep
+    the site nice and speedy when in production, without causing headaches
+    during development.
+    """
+    def load_template(self, *args, **kwargs):
+        if settings.DEBUG:
+            self.reset()
+
+        return super(Loader, self).load_template(*args, **kwargs)
diff --git a/djblets/template/loaders/namespaced_app_dirs.py b/djblets/template/loaders/namespaced_app_dirs.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d4f07c54756e02fdddb78da53537394cff2ecdf
--- /dev/null
+++ b/djblets/template/loaders/namespaced_app_dirs.py
@@ -0,0 +1,48 @@
+from __future__ import unicode_literals
+
+import os
+
+from django.template.base import TemplateDoesNotExist
+from django.template.loaders import app_directories
+from django.utils.importlib import import_module
+
+
+class Loader(app_directories.Loader):
+    """Looks for templates in app directories, optionally with a namespace.
+
+    This extends the standard Django 'app_directories' template loader by
+    allowing a prefix specifying the app whose template should be used.
+    It solves the problem of one app defining a template and another app
+    trying to both override and extend it, resulting in an infinite loop.
+
+    Templates can be in the standard form of 'path/to/template', or in the
+    namespaced form of 'app.path:path/to/template'.
+    """
+    def __init__(self, *args, **kwargs):
+        super(Loader, self).__init__(*args, **kwargs)
+
+        self._cache = {}
+
+    def get_template_sources(self, template_name, template_dirs=None):
+        parts = template_name.split(':')
+
+        if len(parts) == 2:
+            app = parts[0]
+
+            template_dir = self._cache.get(app)
+
+            if not template_dir:
+                try:
+                    mod = import_module(app)
+                except ImportError:
+                    raise TemplateDoesNotExist(template_name)
+
+                template_dir = os.path.join(os.path.dirname(mod.__file__),
+                                            'templates')
+                self._cache[app] = template_dir
+
+            template_name = parts[1]
+            template_dirs = [template_dir]
+
+        return super(Loader, self).get_template_sources(template_name,
+                                                        template_dirs)
