From 596cf4f7c558276c1edd02b99d432ecf0c82c0a0 Mon Sep 17 00:00:00 2001
From: Alice Heaton <aliceh@aptivate.org>
Date: Thu, 23 Jul 2015 16:17:27 +0100
Subject: [PATCH] Ensure dashboard displays error in case of missing or faulty
 widget, rather than causing the whole application to fail.

---
 .../templates/dashboard/widget-error.html     | 10 +++++
 .../dashboard/widget-missing-template.html    |  1 -
 .../dashboard/templatetags/render_widget.py   | 42 +++++++++++++++----
 django/website/dashboard/views.py             |  7 +++-
 django/website/dashboard/widget_pool.py       | 12 +++++-
 .../widgets/{chart.py => term_count_chart.py} |  0
 6 files changed, 59 insertions(+), 13 deletions(-)
 create mode 100644 django/website/dashboard/templates/dashboard/widget-error.html
 delete mode 100644 django/website/dashboard/templates/dashboard/widget-missing-template.html
 rename django/website/hid/widgets/{chart.py => term_count_chart.py} (100%)

diff --git a/django/website/dashboard/templates/dashboard/widget-error.html b/django/website/dashboard/templates/dashboard/widget-error.html
new file mode 100644
index 00000000..38d75f8d
--- /dev/null
+++ b/django/website/dashboard/templates/dashboard/widget-error.html
@@ -0,0 +1,10 @@
+{% load i18n %}
+<div class='panel panel-default'>
+    <div class='panel-heading'>
+        <span class='fa fa-warning fa-fw'></span>
+        <h2>{% trans "Error" %}</h2>
+    </div>
+    <div class='panel-body'>
+        {{ error }}
+    </div>
+</div>
diff --git a/django/website/dashboard/templates/dashboard/widget-missing-template.html b/django/website/dashboard/templates/dashboard/widget-missing-template.html
deleted file mode 100644
index 3f976032..00000000
--- a/django/website/dashboard/templates/dashboard/widget-missing-template.html
+++ /dev/null
@@ -1 +0,0 @@
-Missing template_name for widget type {{empty_type}}
diff --git a/django/website/dashboard/templatetags/render_widget.py b/django/website/dashboard/templatetags/render_widget.py
index d090438b..904b0a61 100644
--- a/django/website/dashboard/templatetags/render_widget.py
+++ b/django/website/dashboard/templatetags/render_widget.py
@@ -1,9 +1,13 @@
+import logging
+
 from django import template
 from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
 
-from dashboard.widget_pool import get_widget
+from dashboard.widget_pool import get_widget, MissingWidgetType
 
 
+logger = logging.getLogger(__name__)
 register = template.Library()
 
 
@@ -16,18 +20,40 @@ def render_widget(widget_instance):
     get_context_data then that is used to generate the
     template context.
     """
-    widget = get_widget(widget_instance.widget_type)
+    widget = None
+    # Get settings, if any
     if widget_instance.settings:
         settings = widget_instance.settings
     else:
         settings = {}
+    # Get widget
     try:
-        context = widget.get_context_data(**settings)
-    except AttributeError:
+        widget = get_widget(widget_instance.widget_type)
+    except MissingWidgetType:
+        template_name = 'dashboard/widget-error.html'
         context = {}
-    try:
-        template_name = widget.template_name
-    except AttributeError:
-        template_name = 'dashboard/widget-missing-template.html'
+        context['error'] = _('Unknown widget type %(widget_type)s') % {
+            'widget_type': widget_instance.widget_type
+        }
         context['empty_type'] = widget_instance.widget_type
+    if widget:
+        # Get template
+        try:
+            template_name = widget.template_name
+        except AttributeError:
+            template_name = 'dashboard/widget-error.html'
+            context = {}
+            context['error'] = _('Missing template for %(widget_type)s') % {
+                'widget_type': widget_instance.widget_type
+            }
+            widget = None
+    if widget:
+        # Get context
+        try:
+            context = widget.get_context_data(**settings)
+        except:
+            logger.exception()
+            template_name = 'dashboard/widget-error.html'
+            context['error'] = _('Widget error. See error logs.')
+
     return render_to_string(template_name, context)
diff --git a/django/website/dashboard/views.py b/django/website/dashboard/views.py
index 73f4a3b6..b20136dc 100644
--- a/django/website/dashboard/views.py
+++ b/django/website/dashboard/views.py
@@ -3,7 +3,7 @@ from django.views.generic import TemplateView
 from hid.assets import require_assets
 
 from dashboard.models import Dashboard
-from dashboard.widget_pool import get_widget
+from dashboard.widget_pool import get_widget, MissingWidgetType
 
 
 class DashboardView(TemplateView):
@@ -43,7 +43,10 @@ class DashboardView(TemplateView):
         # Ensure we have all the javascript & css dependencies
         require_assets('dashboard/dashboard.css')
         for widget in widgets:
-            widget_type = get_widget(widget.widget_type)
+            try:
+                widget_type = get_widget(widget.widget_type)
+            except MissingWidgetType:
+                continue
             if hasattr(widget_type, 'javascript'):
                 require_assets(*widget_type.javascript)
             if hasattr(widget_type, 'css'):
diff --git a/django/website/dashboard/widget_pool.py b/django/website/dashboard/widget_pool.py
index 7615b0bb..98cc08bc 100644
--- a/django/website/dashboard/widget_pool.py
+++ b/django/website/dashboard/widget_pool.py
@@ -1,6 +1,11 @@
 _pool = {}
 
 
+class MissingWidgetType(Exception):
+    """ Exception raised when a widget type is missing """
+    pass
+
+
 def register_widget(name, widget):
     """ Register a new widget type
 
@@ -24,10 +29,13 @@ def get_widget(name):
     Returns:
         The widget object as registered with register_widget
     Raises:
-        KeyError: If the widget type does not exist
+        MissingWidgetType: If the widget type does not exist
     """
     global _pool
-    return _pool[name]
+    try:
+        return _pool[name]
+    except KeyError:
+        raise MissingWidgetType()
 
 
 class BasicTextWidget(object):
diff --git a/django/website/hid/widgets/chart.py b/django/website/hid/widgets/term_count_chart.py
similarity index 100%
rename from django/website/hid/widgets/chart.py
rename to django/website/hid/widgets/term_count_chart.py
-- 
GitLab