diff --git a/djblets/extensions/errors.py b/djblets/extensions/errors.py
index 2692df884fed90ffe24c91787ead573298eb6bc4..136173e8ddd9c6178902f32add96837ff4c20251 100644
--- a/djblets/extensions/errors.py
+++ b/djblets/extensions/errors.py
@@ -30,7 +30,10 @@ from django.utils.translation import ugettext as _
 
 class EnablingExtensionError(Exception):
     """An extension could not be enabled."""
-    pass
+    def __init__(self, message, load_error=None, needs_reload=False):
+        self.message = message
+        self.load_error = load_error
+        self.needs_reload = needs_reload
 
 
 class DisablingExtensionError(Exception):
@@ -40,7 +43,9 @@ class DisablingExtensionError(Exception):
 
 class InstallExtensionError(Exception):
     """An extension could not be installed."""
-    pass
+    def __init__(self, message, load_error=None):
+        self.message = message
+        self.load_error = load_error
 
 
 class InvalidExtensionError(Exception):
diff --git a/djblets/extensions/manager.py b/djblets/extensions/manager.py
index f16045bee8757aaae638307a65a20a262e1be79c..65527d1f2c9fb6c37870bea0aa3fbc9b09d394e3 100644
--- a/djblets/extensions/manager.py
+++ b/djblets/extensions/manager.py
@@ -32,6 +32,7 @@ import pkg_resources
 import shutil
 import sys
 import time
+import traceback
 
 from django.conf import settings
 from django.conf.urls import patterns, include
@@ -165,6 +166,7 @@ class ExtensionManager(object):
 
         self._extension_classes = {}
         self._extension_instances = {}
+        self._load_errors = {}
 
         # State synchronization
         self._sync_key = make_cache_key('extensionmgr:%s:gen' % key)
@@ -213,6 +215,18 @@ class ExtensionManager(object):
     def get_absolute_url(self):
         return reverse("djblets.extensions.views.extension_list")
 
+    def get_can_disable_extension(self, registered_extension):
+        extension_id = registered_extension.class_name
+
+        return (registered_extension.extension_class is not None and
+                (self.get_enabled_extension(extension_id) is not None or
+                 extension_id in self._load_errors))
+
+    def get_can_enable_extension(self, registered_extension):
+        return (registered_extension.extension_class is not None and
+                self.get_enabled_extension(
+                    registered_extension.class_name) is None)
+
     def get_enabled_extension(self, extension_id):
         """Returns an enabled extension with the given ID."""
         if extension_id in self._extension_instances:
@@ -265,6 +279,12 @@ class ExtensionManager(object):
             return
 
         if extension_id not in self._extension_classes:
+            if extension_id in self._load_errors:
+                raise EnablingExtensionError(
+                    _('There was an error loading this extension'),
+                    self._load_errors[extension_id],
+                    needs_reload=True)
+
             raise InvalidExtensionError(extension_id)
 
         ext_class = self._extension_classes[extension_id]
@@ -276,11 +296,12 @@ class ExtensionManager(object):
         try:
             self._install_extension(ext_class)
         except InstallExtensionError as e:
-            raise EnablingExtensionError(e.message)
+            raise EnablingExtensionError(e.message, e.load_error)
+
+        extension = self._init_extension(ext_class)
 
         ext_class.registration.enabled = True
         ext_class.registration.save()
-        extension = self._init_extension(ext_class)
 
         self._clear_template_cache()
         self._bump_sync_gen()
@@ -296,23 +317,40 @@ class ExtensionManager(object):
 
         It will not delete any data from the database.
         """
-        if extension_id not in self._extension_instances:
-            # It's not enabled.
-            return
+        has_load_error = extension_id in self._load_errors
 
-        if extension_id not in self._extension_classes:
-            raise InvalidExtensionError(extension_id)
+        if not has_load_error:
+            if extension_id not in self._extension_instances:
+                # It's not enabled.
+                return
+
+            if extension_id not in self._extension_classes:
+                raise InvalidExtensionError(extension_id)
 
-        extension = self._extension_instances[extension_id]
+            extension = self._extension_instances[extension_id]
 
-        for dependent_id in self.get_dependent_extensions(extension_id):
-            self.disable_extension(dependent_id)
+            for dependent_id in self.get_dependent_extensions(extension_id):
+                self.disable_extension(dependent_id)
 
-        self._uninstall_extension(extension)
-        self._uninit_extension(extension)
-        self._unregister_static_bundles(extension)
-        extension.registration.enabled = False
-        extension.registration.save()
+            self._uninstall_extension(extension)
+            self._uninit_extension(extension)
+            self._unregister_static_bundles(extension)
+
+            registration = extension.registration
+        else:
+            del self._load_errors[extension_id]
+
+            if extension_id in self._extension_classes:
+                # The class was loadable, so it just couldn't be instantiated.
+                # Update the registration on the class.
+                ext_class = self._extension_classes[extension_id]
+                registration = ext_class.registration
+            else:
+                registration = RegisteredExtension.objects.get(
+                    class_name=extension_id)
+
+        registration.enabled = False
+        registration.save(update_fields=['enabled'])
 
         self._clear_template_cache()
         self._bump_sync_gen()
@@ -359,6 +397,7 @@ class ExtensionManager(object):
             # We're reloading everything, so nuke all the cached copies.
             self._clear_extensions()
             self._clear_template_cache()
+            self._load_errors = {}
 
         # Preload all the RegisteredExtension objects
         registered_extensions = {}
@@ -376,6 +415,9 @@ class ExtensionManager(object):
             except Exception as e:
                 logging.error("Error loading extension %s: %s" %
                               (entrypoint.name, e))
+                extension_id = '%s.%s' % (entrypoint.module_name,
+                                          '.'.join(entrypoint.attrs))
+                self._store_load_error(extension_id, e)
                 continue
 
             # A class's extension ID is its class name. We want to
@@ -383,7 +425,7 @@ class ExtensionManager(object):
             # variable, which will be accessible both on the class and on
             # instances.
             class_name = ext_class.id = "%s.%s" % (ext_class.__module__,
-                                                    ext_class.__name__)
+                                                   ext_class.__name__)
             self._extension_classes[class_name] = ext_class
             found_extensions[class_name] = ext_class
 
@@ -482,17 +524,24 @@ class ExtensionManager(object):
         and make it available in Django's list of apps. It will then notify
         that the extension has been initialized.
         """
-        assert ext_class.id not in self._extension_instances
+        extension_id = ext_class.id
+
+        assert extension_id not in self._extension_instances
 
         try:
             extension = ext_class(extension_manager=self)
         except Exception as e:
             logging.error('Unable to initialize extension %s: %s'
                           % (ext_class, e), exc_info=1)
-            raise EnablingExtensionError(_('Error initializing extension: %s')
-                                         % e)
+            error_details = self._store_load_error(extension_id, e)
+            raise EnablingExtensionError(
+                _('Error initializing extension: %s') % e,
+                error_details)
+
+        if extension_id in self._load_errors:
+            del self._load_errors[extension_id]
 
-        self._extension_instances[extension.id] = extension
+        self._extension_instances[extension_id] = extension
 
         if extension.has_admin_site:
             self._init_admin_site(extension)
@@ -541,6 +590,13 @@ class ExtensionManager(object):
 
         del self._extension_instances[extension.id]
 
+    def _store_load_error(self, extension_id, e):
+        """Stores and returns a load error for the extension ID."""
+        error_details = '%s\n\n%s' % (e, traceback.format_exc())
+        self._load_errors[extension_id] = error_details
+
+        return error_details
+
     def _reset_templatetags_cache(self):
         """Clears the Django templatetags_modules cache."""
         # We'll import templatetags_modules here because
@@ -622,8 +678,11 @@ class ExtensionManager(object):
             # Something went wrong while running django-evolution, so
             # grab the output.  We can't raise right away because we
             # still need to put stdout back the way it was
-            logging.error(e.message)
-            raise InstallExtensionError(e.message)
+            logging.error('Error evolving extension models: %s',
+                          e, exc_info=1)
+
+            load_error = self._store_load_error(extension_id, e)
+            raise InstallExtensionError(six.text_type(e), load_error)
 
         # Remove this again, since we only needed it for syncdb and
         # evolve.  _init_extension will add it again later in
diff --git a/djblets/extensions/resources.py b/djblets/extensions/resources.py
index b59bd1bb8a27bb06de7d9f47749d494674a44c1d..a966b0f0901661841f1fd51d468ea56cdcad383f 100644
--- a/djblets/extensions/resources.py
+++ b/djblets/extensions/resources.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
 from django.conf.urls import patterns, include
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.urlresolvers import reverse
+from django.utils import six
 
 from djblets.extensions.errors import (DisablingExtensionError,
                                        EnablingExtensionError,
@@ -30,6 +31,14 @@ class ExtensionResource(WebAPIResource):
             'type': str,
             'description': "The author's website.",
         },
+        'can_disable': {
+            'type': bool,
+            'description': 'Whether or not the extension can be disabled.',
+        },
+        'can_enable': {
+            'type': bool,
+            'description': 'Whether or not the extension can be enabled.',
+        },
         'class_name': {
             'type': str,
             'description': 'The class name for the extension.',
@@ -42,6 +51,17 @@ class ExtensionResource(WebAPIResource):
             'type': bool,
             'description': 'Whether or not the extension is installed.',
         },
+        'loadable': {
+            'type': bool,
+            'description': 'Whether or not the extension is currently '
+                           'loadable. An extension may be installed but '
+                           'missing or may be broken due to a bug.',
+        },
+        'load_error': {
+            'type': str,
+            'description': 'If the extension could not be loaded, this will '
+                           'contain any errors captured while trying to load.',
+        },
         'name': {
             'type': str,
             'description': 'The name of the extension.',
@@ -78,18 +98,46 @@ class ExtensionResource(WebAPIResource):
         extension_uninitialized.connect(self._on_extension_uninitialized)
 
     def serialize_author_field(self, extension, *args, **kwargs):
+        if extension.extension_class is None:
+            return None
+
         return extension.extension_class.info.author
 
     def serialize_author_url_field(self, extension, *args, **kwargs):
+        if extension.extension_class is None:
+            return None
+
         return extension.extension_class.info.author_url
 
+    def serialize_can_disable_field(self, extension, *args, **kwargs):
+        return self._extension_manager.get_can_disable_extension(extension)
+
+    def serialize_can_enable_field(self, extension, *args, **kwargs):
+        return self._extension_manager.get_can_enable_extension(extension)
+
+    def serialize_loadable_field(self, extension, *args, **kwargs):
+        return (extension.extension_class is not None and
+                extension.class_name not in self._extension_manager._load_errors)
+
+    def serialize_load_error_field(self, extension, *args, **kwargs):
+        return self._extension_manager._load_errors.get(extension.class_name)
+
     def serialize_name_field(self, extension, *args, **kwargs):
-        return extension.extension_class.info.name
+        if extension.extension_class is None:
+            return extension.name
+        else:
+            return extension.extension_class.info.name
 
     def serialize_summary_field(self, extension, *args, **kwargs):
+        if extension.extension_class is None:
+            return None
+
         return extension.extension_class.info.summary
 
     def serialize_version_field(self, extension, *args, **kwargs):
+        if extension.extension_class is None:
+            return None
+
         return extension.extension_class.info.version
 
     @webapi_login_required
@@ -151,22 +199,26 @@ class ExtensionResource(WebAPIResource):
         except ObjectDoesNotExist:
             return DOES_NOT_EXIST
 
-        try:
-            ext_class = self._extension_manager.get_installed_extension(
-                registered_extension.class_name)
-        except InvalidExtensionError:
-            return DOES_NOT_EXIST
+        extension_id = registered_extension.class_name
 
         if kwargs.get('enabled'):
             try:
-                self._extension_manager.enable_extension(ext_class.id)
-            except (EnablingExtensionError, InvalidExtensionError) as e:
-                return ENABLE_EXTENSION_FAILED.with_message(e.message)
+                self._extension_manager.enable_extension(extension_id)
+            except EnablingExtensionError as e:
+                err = ENABLE_EXTENSION_FAILED.with_message(six.text_type(e))
+
+                return err, {
+                    'load_error': e.load_error,
+                    'needs_reload': e.needs_reload,
+                }
+            except InvalidExtensionError as e:
+                raise
+                return ENABLE_EXTENSION_FAILED.with_message(six.text_type(e))
         else:
             try:
-                self._extension_manager.disable_extension(ext_class.id)
+                self._extension_manager.disable_extension(extension_id)
             except (DisablingExtensionError, InvalidExtensionError) as e:
-                return DISABLE_EXTENSION_FAILED.with_message(e.message)
+                return DISABLE_EXTENSION_FAILED.with_message(six.text_type(e))
 
         # Refetch extension, since the ExtensionManager may have changed
         # the model.
diff --git a/djblets/extensions/templates/extensions/extension_list.html b/djblets/extensions/templates/extensions/extension_list.html
index ce3c9ceea2b075a503742b55d0f6f63582baf8c0..323ea6c7970a8fad85430795dbde57723464ca99 100644
--- a/djblets/extensions/templates/extensions/extension_list.html
+++ b/djblets/extensions/templates/extensions/extension_list.html
@@ -24,11 +24,16 @@
 <h1 class="title">{% trans "Manage Extensions" %}</h1>
 
 <div id="content-main">
- <div id="extension-manager">
+ <form id="extension-manager" method="POST" action=".">
+  <input type="hidden" name="full-reload" value="1" />
+
+  <ul class="actions">
+   <li><a href="#" id="reload-extensions">{% trans "Scan for new extensions" %}</a></li>
+  </ul>
   <ul class="extensions">
    <li class="loading">{% trans "Loading..." %}</li>
   </ul>
- </div>
+ </form>
 </div>
 
 <script>
diff --git a/djblets/extensions/views.py b/djblets/extensions/views.py
index be1f22d0f0303846e1805f6854e29094ef20289b..d913fe8eae9212fac106bf769a86e520ef96fd31 100644
--- a/djblets/extensions/views.py
+++ b/djblets/extensions/views.py
@@ -34,10 +34,16 @@ from django.template.context import RequestContext
 @staff_member_required
 def extension_list(request, extension_manager,
                    template_name='extensions/extension_list.html'):
-    # Refresh the extension list.
-    extension_manager.load()
+    if request.method == 'POST':
+        if 'full-reload' in request.POST:
+            extension_manager.load(full_reload=True)
+
+        return HttpResponseRedirect('.')
+    else:
+        # Refresh the extension list.
+        extension_manager.load()
 
-    return render_to_response(template_name, RequestContext(request))
+        return render_to_response(template_name, RequestContext(request))
 
 
 @staff_member_required
diff --git a/djblets/static/djblets/css/extensions.less b/djblets/static/djblets/css/extensions.less
index 994d281c0c78e47f645e8a240fc3dcc33970f82d..3c64b493e94fe5c92202c3991bceb69d7b1b2776 100644
--- a/djblets/static/djblets/css/extensions.less
+++ b/djblets/static/djblets/css/extensions.less
@@ -80,6 +80,33 @@
  * Extension manager
  ****************************************************************************/
 
+#extension-manager {
+  .actions {
+    background: #FDFDFD;
+    border: 1px #AAAAAA solid;
+    border-top: 0;
+    border-bottom: 1px #999999 solid;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+
+    li {
+      list-style: none;
+      display: inline-block;
+      margin: 0;
+      padding: 0.8em 1em;
+
+      a {
+        color: #0000FF;
+
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
+
 .extensions-list-page {
   #content-main {
     float: left;
@@ -107,9 +134,25 @@
   }
 
   .extension {
+    &.enabled {
+      border-left: 8px #AACCAA solid;
+    }
+
+    &.disabled {
+      border-left: 8px #DDDDDD solid;
+    }
+
+    &.error {
+      border-left: 8px #CCAAAA solid;
+    }
+
     &.row1 {
       border-top: 1px #E0E0E0 solid;
       border-bottom: 1px #E0E0E0 solid;
+
+      &:first-child {
+        border-top: 0;
+      }
     }
 
     h1 {
@@ -139,6 +182,19 @@
       .clearfix;
       display: block;
     }
+
+    .extension-load-error {
+      p {
+        margin: 1.5em 10em 1.5em 1em;
+        padding: 0;
+      }
+
+      pre {
+        margin-left: 3em;
+        margin-bottom: 2em;
+        overflow-x: auto;
+      }
+    }
   }
 
   .object-tools {
diff --git a/djblets/static/djblets/js/extensions/models/extensionManagerModel.js b/djblets/static/djblets/js/extensions/models/extensionManagerModel.js
index 97338b5b6929c241377c148b098142f585bbec48..93410d0cdd281763960836c4abd6207c0b72439a 100644
--- a/djblets/static/djblets/js/extensions/models/extensionManagerModel.js
+++ b/djblets/static/djblets/js/extensions/models/extensionManagerModel.js
@@ -1,4 +1,8 @@
-var InstalledExtension;
+(function() {
+
+
+var InstalledExtension,
+    InstalledExtensionCollection;
 
 
 /*
@@ -14,6 +18,8 @@ InstalledExtension = Backbone.Model.extend({
         configURL: null,
         dbURL: null,
         enabled: false,
+        loadable: true,
+        loadError: null,
         name: null,
         summary: null,
         version: null
@@ -31,10 +37,13 @@ InstalledExtension = Backbone.Model.extend({
             enabled: true
         }, {
             wait: true,
-            error: function(model, xhr) {
-                alert(gettext('Failed to enable extension. ') +
-                      xhr.errorText + '.');
-            }
+            error: _.bind(function(model, xhr) {
+                this.set({
+                    loadable: false,
+                    loadError: xhr.errorRsp.load_error,
+                    canEnable: !xhr.errorRsp.needs_reload
+                });
+            }, this)
         });
     },
 
@@ -79,9 +88,13 @@ InstalledExtension = Backbone.Model.extend({
         return {
             author: rsp.author,
             authorURL: rsp.author_url,
+            canDisable: rsp.can_disable,
+            canEnable: rsp.can_enable,
             configURL: configLink ? configLink.href : null,
             dbURL: dbLink ? dbLink.href : null,
             enabled: rsp.enabled,
+            loadable: rsp.loadable,
+            loadError: rsp.load_error,
             id: rsp.class_name,
             name: rsp.name,
             summary: rsp.summary,
@@ -99,18 +112,20 @@ InstalledExtension = Backbone.Model.extend({
             processData: true,
             error: _.bind(function(xhr) {
                 var rsp = null,
+                    loadError,
                     text;
 
                 try {
                     rsp = $.parseJSON(xhr.responseText);
                     text = rsp.err.msg;
+                    loadError = rsp.load_error;
                 } catch (e) {
                     text = 'HTTP ' + xhr.status + ' ' + xhr.statusText;
                 }
 
-                console.log(xhr.responseText);
                 if (_.isFunction(options.error)) {
                     xhr.errorText = text;
+                    xhr.errorRsp = rsp;
                     options.error(xhr, options);
                 }
             }, this)
@@ -120,6 +135,25 @@ InstalledExtension = Backbone.Model.extend({
 
 
 /*
+ * A collection of installed extensions.
+ *
+ * This stores the list of installed extensions, and allows fetching from
+ * the API.
+ */
+InstalledExtensionCollection = Backbone.Collection.extend({
+    model: InstalledExtension,
+
+    url: function() {
+        return SITE_ROOT + 'api/extensions/';
+    },
+
+    parse: function(rsp) {
+        return rsp.extensions;
+    }
+});
+
+
+/*
  * Manages installed extensions.
  *
  * This stores a collection of installed extensions, and provides
@@ -138,3 +172,6 @@ Djblets.ExtensionManager = Backbone.Model.extend({
         });
     }
 });
+
+
+})();
diff --git a/djblets/static/djblets/js/extensions/views/extensionManagerView.js b/djblets/static/djblets/js/extensions/views/extensionManagerView.js
index 9e23a0b2890c3749c3b0199e5efc16e5d09c8301..998b5c912297974394a5c47ada8a4522c32ba47d 100644
--- a/djblets/static/djblets/js/extensions/views/extensionManagerView.js
+++ b/djblets/static/djblets/js/extensions/views/extensionManagerView.js
@@ -1,3 +1,6 @@
+(function() {
+
+
 var InstalledExtensionView;
 
 
@@ -13,7 +16,8 @@ InstalledExtensionView = Backbone.View.extend({
     tagName: 'li',
 
     events: {
-        'click .enable-toggle': '_toggleEnableState'
+        'click .enable-toggle': '_toggleEnableState',
+        'click .reload-link': '_reloadExtensions'
     },
 
     template: _.template([
@@ -28,15 +32,25 @@ InstalledExtensionView = Backbone.View.extend({
         ' </p>',
         '</div>',
         '<div class="description"><%- summary %></div>',
+        '<% if (!loadable) { %>',
+        ' <div class="extension-load-error">',
+        '  <p><%- loadFailureText %></p>',
+        '  <pre><%- loadError %></pre>',
+        ' </div>',
+        '<% } %>',
         '<ul class="object-tools">',
         ' <li><a href="#" class="enable-toggle"></a></li>',
-        ' <% if (configURL) { %>',
-        '  <li><a href="<%- configURL %>" class="enabled-only changelink">',
-        '      <%- configureText %></a></li>',
-        ' <% } %>',
-        ' <% if (dbURL) { %>',
-        '  <li><a href="<%- dbURL %>" class="enabled-only changelink">',
-        '      <%- databaseText %></a></li>',
+        ' <% if (loadError) { %>',
+        '  <li><a href="#" class="reload-link"><%- reloadText %></a></li>',
+        ' <% } else { %>',
+        '  <% if (configURL) { %>',
+        '   <li><a href="<%- configURL %>" class="enabled-only changelink">',
+        '       <%- configureText %></a></li>',
+        '  <% } %>',
+        '  <% if (dbURL) { %>',
+        '   <li><a href="<%- dbURL %>" class="enabled-only changelink">',
+        '       <%- databaseText %></a></li>',
+        '  <% } %>',
         ' <% } %>',
         '</ul>',
     ].join('')),
@@ -45,18 +59,42 @@ InstalledExtensionView = Backbone.View.extend({
      * Renders the extension in the list.
      */
     render: function() {
+        this._renderTemplate();
+
+        this.listenTo(this.model, 'change:loadable change:loadError',
+                      this._renderTemplate);
+        this.listenTo(this.model,
+                      'change:enabled change:canEnable change:canDisable',
+                      this._showEnabledState);
+
+        return this;
+    },
+
+    /*
+     * Renders the template for the extension.
+     *
+     * This will render the template based on the current page conditions.
+     * It's called when first rendering the extension and whenever there's
+     * another need to do a full re-render (such as when loading an extension
+     * fails).
+     */
+    _renderTemplate: function() {
         this.$el.html(this.template(_.defaults({
             configureText: gettext('Configure'),
-            databaseText: gettext('Database')
+            databaseText: gettext('Database'),
+            loadFailureText: gettext('This extension failed to load with the following error:'),
+            reloadText: gettext('Reload')
         }, this.model.attributes)));
 
+        if (this.model.get('loadable')) {
+            this.$el.removeClass('error');
+        } else {
+            this.$el.addClass('error');
+        }
+
         this._$enableToggle = this.$('.enable-toggle');
         this._$enabledToolLinks = this.$('.enabled-only');
-
-        this.listenTo(this.model, 'change:enabled', this._showEnabledState);
         this._showEnabledState();
-
-        return this;
     },
 
     /*
@@ -68,10 +106,16 @@ InstalledExtensionView = Backbone.View.extend({
     _showEnabledState: function() {
         var enabled = this.model.get('enabled');
 
+        this.$el
+            .removeClass(enabled ? 'disabled' : 'enabled')
+            .addClass(enabled ? 'enabled' : 'disabled')
+
         this._$enableToggle
             .text(enabled ? gettext('Disable') : gettext('Enable'))
             .addClass(enabled ? 'disablelink' : 'enablelink')
-            .removeClass(enabled ? 'enablelink' : 'disablelink');
+            .removeClass(enabled ? 'enablelink' : 'disablelink')
+            .setVisible((enabled && this.model.get('canDisable')) ||
+                        (!enabled && this.model.get('canEnable')));
         this._$enabledToolLinks.setVisible(enabled);
     },
 
@@ -86,25 +130,11 @@ InstalledExtensionView = Backbone.View.extend({
         }
 
         return false;
-    }
-});
-
-
-/*
- * A collection of installed extensions.
- *
- * This stores the list of installed extensions, and allows fetching from
- * the API.
- */
-InstalledExtensionCollection = Backbone.Collection.extend({
-    model: InstalledExtension,
-
-    url: function() {
-        return SITE_ROOT + 'api/extensions/';
     },
 
-    parse: function(rsp) {
-        return rsp.extensions;
+    _reloadExtensions: function() {
+        this.trigger('reloadClicked');
+        return false;
     }
 });
 
@@ -115,6 +145,10 @@ InstalledExtensionCollection = Backbone.Collection.extend({
  * This loads the list of installed extensions and displays each in a list.
  */
 Djblets.ExtensionManagerView = Backbone.View.extend({
+    events: {
+        'click #reload-extensions': '_reloadFull'
+    },
+
     initialize: function() {
         this._$extensions = null;
     },
@@ -153,10 +187,25 @@ Djblets.ExtensionManagerView = Backbone.View.extend({
                 view.$el.addClass(evenRow ? 'row2' : 'row1');
                 view.render();
 
+                this.listenTo(view, 'reloadClicked', this._reloadFull);
+
                 evenRow = !evenRow;
             }, this);
 
             this._$extensions.appendTo(this.$el);
         }
+    },
+
+    /*
+     * Performs a full reload of the list of extensions on the server.
+     *
+     * This submits our form, which is set in the template to tell the
+     * ExtensionManager to do a full reload.
+     */
+    _reloadFull: function() {
+        this.el.submit();
     }
 });
+
+
+})();
