diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e0a083c..5cb42ef 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,9 @@
+Version 0.5.0
+-------------
+* Split off Geckoboard support into django-geckoboard_.
+
+.. _django-geckoboard: http://pypi.python.org/pypi/django-geckoboard
+
Version 0.4.0
-------------
* Added support for the Geckoboard service.
diff --git a/README.rst b/README.rst
index b095434..8e4bf8c 100644
--- a/README.rst
+++ b/README.rst
@@ -22,7 +22,6 @@ Currently supported services:
* `Chartbeat`_ traffic analysis
* `Clicky`_ traffic analysis
* `Crazy Egg`_ visual click tracking
-* `Geckoboard`_ status board
* `Google Analytics`_ traffic analysis
* `HubSpot`_ inbound marketing
* `KISSinsights`_ feedback surveys
@@ -46,7 +45,6 @@ an issue to discuss your plans.
.. _Chartbeat: http://www.chartbeat.com/
.. _Clicky: http://getclicky.com/
.. _`Crazy Egg`: http://www.crazyegg.com/
-.. _Geckoboard: http://www.geckoboard.com/
.. _`Google Analytics`: http://www.google.com/analytics/
.. _HubSpot: http://www.hubspot.com/
.. _KISSinsights: http://www.kissinsights.com/
diff --git a/analytical/__init__.py b/analytical/__init__.py
index dda325d..1c27423 100644
--- a/analytical/__init__.py
+++ b/analytical/__init__.py
@@ -10,6 +10,6 @@ Django_ project. See the ``docs`` directory for more information.
__author__ = "Joost Cassee"
__email__ = "joost@cassee.net"
-__version__ = "0.4.0"
+__version__ = "0.5.0"
__copyright__ = "Copyright (C) 2011 Joost Cassee"
__license__ = "MIT License"
diff --git a/analytical/geckoboard.py b/analytical/geckoboard.py
deleted file mode 100644
index fede327..0000000
--- a/analytical/geckoboard.py
+++ /dev/null
@@ -1,296 +0,0 @@
-"""
-Geckoboard decorators.
-"""
-
-import base64
-from xml.dom.minidom import Document
-
-try:
- from functools import wraps
-except ImportError:
- from django.utils.functional import wraps # Python 2.4 fallback
-
-from django.conf import settings
-from django.http import HttpResponse, HttpResponseForbidden
-from django.views.decorators.csrf import csrf_exempt
-from django.utils.datastructures import SortedDict
-from django.utils.decorators import available_attrs
-from django.utils import simplejson
-
-
-TEXT_NONE = 0
-TEXT_INFO = 2
-TEXT_WARN = 1
-
-
-class WidgetDecorator(object):
- """
- Geckoboard widget decorator.
-
- The decorated view must return a data structure suitable for
- serialization to XML or JSON for Geckoboard. See the Geckoboard
- API docs or the source of extending classes for details.
-
- If the GECKOBOARD_API_KEY setting is used, the request must contain
- the correct API key, or a 403 Forbidden response is returned.
- """
- def __call__(self, view_func):
- def _wrapped_view(request, *args, **kwargs):
- if not _is_api_key_correct(request):
- return HttpResponseForbidden("Geckoboard API key incorrect")
- view_result = view_func(request, *args, **kwargs)
- data = self._convert_view_result(view_result)
- content = _render(request, data)
- return HttpResponse(content)
- wrapper = wraps(view_func, assigned=available_attrs(view_func))
- return csrf_exempt(wrapper(_wrapped_view))
-
- def _convert_view_result(self, data):
- # Extending classes do view result mangling here.
- return data
-
-widget = WidgetDecorator()
-
-
-class NumberWidgetDecorator(WidgetDecorator):
- """
- Geckoboard number widget decorator.
-
- The decorated view must return either a single value, or a list or
- tuple with one or two values. The first (or only) value represents
- the current value, the second value represents the previous value.
- """
-
- def _convert_view_result(self, result):
- if not isinstance(result, (tuple, list)):
- result = [result]
- return {'item': [{'value': v} for v in result]}
-
-number = NumberWidgetDecorator()
-
-
-class RAGWidgetDecorator(WidgetDecorator):
- """
- Geckoboard red-amber-green widget decorator.
-
- The decorated view must return a tuple or list with three values,
- or three tuples (value, text). The values represent numbers shown
- in red, amber and green respectively. The text parameter is
- optional and will be displayed next to the value in the dashboard.
- """
-
- def _convert_view_result(self, result):
- items = []
- for elem in result:
- if not isinstance(elem, (tuple, list)):
- elem = [elem]
- item = SortedDict()
- if elem[0] is None:
- item['value'] = ''
- else:
- item['value'] = elem[0]
- if len(elem) > 1:
- item['text'] = elem[1]
- items.append(item)
- return {'item': items}
-
-rag = RAGWidgetDecorator()
-
-
-class TextWidgetDecorator(WidgetDecorator):
- """
- Geckoboard text widget decorator.
-
- The decorated view must return a list or tuple of strings, or tuples
- (string, type). The type parameter tells Geckoboard how to display
- the text. Use TEXT_INFO for informational messages, TEXT_WARN for
- warnings and TEXT_NONE for plain text (the default).
- """
-
- def _convert_view_result(self, result):
- items = []
- if not isinstance(result, (tuple, list)):
- result = [result]
- for elem in result:
- if not isinstance(elem, (tuple, list)):
- elem = [elem]
- item = SortedDict()
- item['text'] = elem[0]
- if len(elem) > 1:
- item['type'] = elem[1]
- else:
- item['type'] = TEXT_NONE
- items.append(item)
- return {'item': items}
-
-text = TextWidgetDecorator()
-
-
-class PieChartWidgetDecorator(WidgetDecorator):
- """
- Geckoboard pie chart widget decorator.
-
- The decorated view must return a list or tuple of tuples
- (value, label, color). The color parameter is a string 'RRGGBB[TT]'
- representing red, green, blue and optionally transparency.
- """
-
- def _convert_view_result(self, result):
- items = []
- for elem in result:
- if not isinstance(elem, (tuple, list)):
- elem = [elem]
- item = SortedDict()
- item['value'] = elem[0]
- if len(elem) > 1:
- item['label'] = elem[1]
- if len(elem) > 2:
- item['colour'] = elem[2]
- items.append(item)
- return {'item': items}
-
-pie_chart = PieChartWidgetDecorator()
-
-
-class LineChartWidgetDecorator(WidgetDecorator):
- """
- Geckoboard line chart widget decorator.
-
- The decorated view must return a tuple (values, x_axis, y_axis,
- color). The value parameter is a tuple or list of data points. The
- x-axis parameter is a label string, or a tuple or list of strings,
- that will be placed on the X-axis. The y-axis parameter works
- similarly for the Y-axis. If there are more axis labels, they are
- placed evenly along the axis. The color parameter is a string
- 'RRGGBB[TT]' representing red, green, blue and optionally
- transparency.
- """
-
- def _convert_view_result(self, result):
- data = SortedDict()
- data['item'] = result[0]
- data['settings'] = SortedDict()
-
- if len(result) > 1:
- x_axis = result[1]
- if x_axis is None:
- x_axis = ''
- if not isinstance(x_axis, (tuple, list)):
- x_axis = [x_axis]
- data['settings']['axisx'] = x_axis
-
- if len(result) > 2:
- y_axis = result[2]
- if y_axis is None:
- y_axis = ''
- if not isinstance(y_axis, (tuple, list)):
- y_axis = [y_axis]
- data['settings']['axisy'] = y_axis
-
- if len(result) > 3:
- data['settings']['colour'] = result[3]
-
- return data
-
-line_chart = LineChartWidgetDecorator()
-
-
-class GeckOMeterWidgetDecorator(WidgetDecorator):
- """
- Geckoboard Geck-O-Meter widget decorator.
-
- The decorated view must return a tuple (value, min, max). The value
- parameter represents the current value. The min and max parameters
- represent the minimum and maximum value respectively. They are
- either a value, or a tuple (value, text). If used, the text
- parameter will be displayed next to the minimum or maximum value.
- """
-
- def _convert_view_result(self, result):
- value, min, max = result
- data = SortedDict()
- data['item'] = value
- data['max'] = SortedDict()
- data['min'] = SortedDict()
-
- if not isinstance(max, (tuple, list)):
- max = [max]
- data['max']['value'] = max[0]
- if len(max) > 1:
- data['max']['text'] = max[1]
-
- if not isinstance(min, (tuple, list)):
- min = [min]
- data['min']['value'] = min[0]
- if len(min) > 1:
- data['min']['text'] = min[1]
-
- return data
-
-geck_o_meter = GeckOMeterWidgetDecorator()
-
-
-def _is_api_key_correct(request):
- """Return whether the Geckoboard API key on the request is correct."""
- api_key = getattr(settings, 'GECKOBOARD_API_KEY', None)
- if api_key is None:
- return True
- auth = request.META.get('HTTP_AUTHORIZATION', '').split()
- if len(auth) == 2:
- if auth[0].lower() == 'basic':
- request_key = base64.b64decode(auth[1])
- return request_key == api_key
- return False
-
-
-def _render(request, data):
- format = request.POST.get('format', '')
- if not format:
- format = request.GET.get('format', '')
- if format == '2':
- return _render_json(data)
- else:
- return _render_xml(data)
-
-def _render_json(data):
- return simplejson.dumps(data)
-
-def _render_xml(data):
- doc = Document()
- root = doc.createElement('root')
- doc.appendChild(root)
- _build_xml(doc, root, data)
- return doc.toxml()
-
-def _build_xml(doc, parent, data):
- if isinstance(data, (tuple, list)):
- _build_list_xml(doc, parent, data)
- elif isinstance(data, dict):
- _build_dict_xml(doc, parent, data)
- else:
- _build_str_xml(doc, parent, data)
-
-def _build_str_xml(doc, parent, data):
- parent.appendChild(doc.createTextNode(str(data)))
-
-def _build_list_xml(doc, parent, data):
- for item in data:
- _build_xml(doc, parent, item)
-
-def _build_dict_xml(doc, parent, data):
- for tag, item in data.items():
- if isinstance(item, (list, tuple)):
- for subitem in item:
- elem = doc.createElement(tag)
- _build_xml(doc, elem, subitem)
- parent.appendChild(elem)
- else:
- elem = doc.createElement(tag)
- _build_xml(doc, elem, item)
- parent.appendChild(elem)
-
-
-class GeckoboardException(Exception):
- """
- Represents an error with the Geckoboard decorators.
- """
diff --git a/analytical/tests/__init__.py b/analytical/tests/__init__.py
index 95bad00..4ac739e 100644
--- a/analytical/tests/__init__.py
+++ b/analytical/tests/__init__.py
@@ -2,7 +2,6 @@
Tests for django-analytical.
"""
-from analytical.tests.test_geckoboard import *
from analytical.tests.test_tag_analytical import *
from analytical.tests.test_tag_chartbeat import *
from analytical.tests.test_tag_clicky import *
diff --git a/analytical/tests/test_geckoboard.py b/analytical/tests/test_geckoboard.py
deleted file mode 100644
index 461637a..0000000
--- a/analytical/tests/test_geckoboard.py
+++ /dev/null
@@ -1,317 +0,0 @@
-"""
-Tests for the Geckoboard decorators.
-"""
-
-from django.http import HttpRequest, HttpResponseForbidden
-from django.utils.datastructures import SortedDict
-
-from analytical import geckoboard
-from analytical.tests.utils import TestCase
-import base64
-
-
-class WidgetDecoratorTestCase(TestCase):
- """
- Tests for the ``widget`` decorator.
- """
-
- def setUp(self):
- super(WidgetDecoratorTestCase, self).setUp()
- self.settings_manager.delete('GECKOBOARD_API_KEY')
- self.xml_request = HttpRequest()
- self.xml_request.POST['format'] = '1'
- self.json_request = HttpRequest()
- self.json_request.POST['format'] = '2'
-
- def test_api_key(self):
- self.settings_manager.set(GECKOBOARD_API_KEY='abc')
- req = HttpRequest()
- req.META['HTTP_AUTHORIZATION'] = "basic %s" % base64.b64encode('abc')
- resp = geckoboard.widget(lambda r: "test")(req)
- self.assertEqual('test',
- resp.content)
-
- def test_missing_api_key(self):
- self.settings_manager.set(GECKOBOARD_API_KEY='abc')
- req = HttpRequest()
- resp = geckoboard.widget(lambda r: "test")(req)
- self.assertTrue(isinstance(resp, HttpResponseForbidden), resp)
- self.assertEqual('Geckoboard API key incorrect', resp.content)
-
- def test_wrong_api_key(self):
- self.settings_manager.set(GECKOBOARD_API_KEY='abc')
- req = HttpRequest()
- req.META['HTTP_AUTHORIZATION'] = "basic %s" % base64.b64encode('def')
- resp = geckoboard.widget(lambda r: "test")(req)
- self.assertTrue(isinstance(resp, HttpResponseForbidden), resp)
- self.assertEqual('Geckoboard API key incorrect', resp.content)
-
- def test_xml_get(self):
- req = HttpRequest()
- req.GET['format'] = '1'
- resp = geckoboard.widget(lambda r: "test")(req)
- self.assertEqual('test',
- resp.content)
-
- def test_json_get(self):
- req = HttpRequest()
- req.GET['format'] = '2'
- resp = geckoboard.widget(lambda r: "test")(req)
- self.assertEqual('"test"', resp.content)
-
- def test_xml_post(self):
- req = HttpRequest()
- req.POST['format'] = '1'
- resp = geckoboard.widget(lambda r: "test")(req)
- self.assertEqual('test',
- resp.content)
-
- def test_json_post(self):
- req = HttpRequest()
- req.POST['format'] = '2'
- resp = geckoboard.widget(lambda r: "test")(req)
- self.assertEqual('"test"', resp.content)
-
- def test_scalar_xml(self):
- resp = geckoboard.widget(lambda r: "test")(self.xml_request)
- self.assertEqual('test',
- resp.content)
-
- def test_scalar_json(self):
- resp = geckoboard.widget(lambda r: "test")(self.json_request)
- self.assertEqual('"test"', resp.content)
-
- def test_dict_xml(self):
- widget = geckoboard.widget(lambda r: SortedDict([('a', 1), ('b', 2)]))
- resp = widget(self.xml_request)
- self.assertEqual('12',
- resp.content)
-
- def test_dict_json(self):
- widget = geckoboard.widget(lambda r: SortedDict([('a', 1), ('b', 2)]))
- resp = widget(self.json_request)
- self.assertEqual('{"a": 1, "b": 2}', resp.content)
-
- def test_list_xml(self):
- widget = geckoboard.widget(lambda r: {'list': [1, 2, 3]})
- resp = widget(self.xml_request)
- self.assertEqual('1
'
- '2
3
', resp.content)
-
- def test_list_json(self):
- widget = geckoboard.widget(lambda r: {'list': [1, 2, 3]})
- resp = widget(self.json_request)
- self.assertEqual('{"list": [1, 2, 3]}', resp.content)
-
- def test_dict_list_xml(self):
- widget = geckoboard.widget(lambda r: {'item': [
- {'value': 1, 'text': "test1"}, {'value': 2, 'text': "test2"}]})
- resp = widget(self.xml_request)
- self.assertEqual(''
- '- test11
'
- '- test22
',
- resp.content)
-
- def test_dict_list_json(self):
- widget = geckoboard.widget(lambda r: {'item': [
- SortedDict([('value', 1), ('text', "test1")]),
- SortedDict([('value', 2), ('text', "test2")])]})
- resp = widget(self.json_request)
- self.assertEqual('{"item": [{"value": 1, "text": "test1"}, '
- '{"value": 2, "text": "test2"}]}', resp.content)
-
-
-class NumberDecoratorTestCase(TestCase):
- """
- Tests for the ``number`` decorator.
- """
-
- def setUp(self):
- super(NumberDecoratorTestCase, self).setUp()
- self.settings_manager.delete('GECKOBOARD_API_KEY')
- self.request = HttpRequest()
- self.request.POST['format'] = '2'
-
- def test_scalar(self):
- widget = geckoboard.number(lambda r: 10)
- resp = widget(self.request)
- self.assertEqual('{"item": [{"value": 10}]}', resp.content)
-
- def test_singe_value(self):
- widget = geckoboard.number(lambda r: [10])
- resp = widget(self.request)
- self.assertEqual('{"item": [{"value": 10}]}', resp.content)
-
- def test_two_values(self):
- widget = geckoboard.number(lambda r: [10, 9])
- resp = widget(self.request)
- self.assertEqual('{"item": [{"value": 10}, {"value": 9}]}',
- resp.content)
-
-
-class RAGDecoratorTestCase(TestCase):
- """
- Tests for the ``rag`` decorator.
- """
-
- def setUp(self):
- super(RAGDecoratorTestCase, self).setUp()
- self.settings_manager.delete('GECKOBOARD_API_KEY')
- self.request = HttpRequest()
- self.request.POST['format'] = '2'
-
- def test_scalars(self):
- widget = geckoboard.rag(lambda r: (10, 5, 1))
- resp = widget(self.request)
- self.assertEqual(
- '{"item": [{"value": 10}, {"value": 5}, {"value": 1}]}',
- resp.content)
-
- def test_tuples(self):
- widget = geckoboard.rag(lambda r: ((10, "ten"), (5, "five"),
- (1, "one")))
- resp = widget(self.request)
- self.assertEqual('{"item": [{"value": 10, "text": "ten"}, '
- '{"value": 5, "text": "five"}, {"value": 1, "text": "one"}]}',
- resp.content)
-
-
-class TextDecoratorTestCase(TestCase):
- """
- Tests for the ``text`` decorator.
- """
-
- def setUp(self):
- super(TextDecoratorTestCase, self).setUp()
- self.settings_manager.delete('GECKOBOARD_API_KEY')
- self.request = HttpRequest()
- self.request.POST['format'] = '2'
-
- def test_string(self):
- widget = geckoboard.text(lambda r: "test message")
- resp = widget(self.request)
- self.assertEqual('{"item": [{"text": "test message", "type": 0}]}',
- resp.content)
-
- def test_list(self):
- widget = geckoboard.text(lambda r: ["test1", "test2"])
- resp = widget(self.request)
- self.assertEqual('{"item": [{"text": "test1", "type": 0}, '
- '{"text": "test2", "type": 0}]}', resp.content)
-
- def test_list_tuples(self):
- widget = geckoboard.text(lambda r: [("test1", geckoboard.TEXT_NONE),
- ("test2", geckoboard.TEXT_INFO),
- ("test3", geckoboard.TEXT_WARN)])
- resp = widget(self.request)
- self.assertEqual('{"item": [{"text": "test1", "type": 0}, '
- '{"text": "test2", "type": 2}, '
- '{"text": "test3", "type": 1}]}', resp.content)
-
-
-class PieChartDecoratorTestCase(TestCase):
- """
- Tests for the ``pie_chart`` decorator.
- """
-
- def setUp(self):
- super(PieChartDecoratorTestCase, self).setUp()
- self.settings_manager.delete('GECKOBOARD_API_KEY')
- self.request = HttpRequest()
- self.request.POST['format'] = '2'
-
- def test_scalars(self):
- widget = geckoboard.pie_chart(lambda r: [1, 2, 3])
- resp = widget(self.request)
- self.assertEqual(
- '{"item": [{"value": 1}, {"value": 2}, {"value": 3}]}',
- resp.content)
-
- def test_tuples(self):
- widget = geckoboard.pie_chart(lambda r: [(1, ), (2, ), (3, )])
- resp = widget(self.request)
- self.assertEqual(
- '{"item": [{"value": 1}, {"value": 2}, {"value": 3}]}',
- resp.content)
-
- def test_2tuples(self):
- widget = geckoboard.pie_chart(lambda r: [(1, "one"), (2, "two"),
- (3, "three")])
- resp = widget(self.request)
- self.assertEqual('{"item": [{"value": 1, "label": "one"}, '
- '{"value": 2, "label": "two"}, '
- '{"value": 3, "label": "three"}]}', resp.content)
-
- def test_3tuples(self):
- widget = geckoboard.pie_chart(lambda r: [(1, "one", "00112233"),
- (2, "two", "44556677"), (3, "three", "8899aabb")])
- resp = widget(self.request)
- self.assertEqual('{"item": ['
- '{"value": 1, "label": "one", "colour": "00112233"}, '
- '{"value": 2, "label": "two", "colour": "44556677"}, '
- '{"value": 3, "label": "three", "colour": "8899aabb"}]}',
- resp.content)
-
-
-class LineChartDecoratorTestCase(TestCase):
- """
- Tests for the ``line_chart`` decorator.
- """
-
- def setUp(self):
- super(LineChartDecoratorTestCase, self).setUp()
- self.settings_manager.delete('GECKOBOARD_API_KEY')
- self.request = HttpRequest()
- self.request.POST['format'] = '2'
-
- def test_values(self):
- widget = geckoboard.line_chart(lambda r: ([1, 2, 3],))
- resp = widget(self.request)
- self.assertEqual('{"item": [1, 2, 3], "settings": {}}', resp.content)
-
- def test_x_axis(self):
- widget = geckoboard.line_chart(lambda r: ([1, 2, 3],
- ["first", "last"]))
- resp = widget(self.request)
- self.assertEqual('{"item": [1, 2, 3], '
- '"settings": {"axisx": ["first", "last"]}}', resp.content)
-
- def test_axes(self):
- widget = geckoboard.line_chart(lambda r: ([1, 2, 3],
- ["first", "last"], ["low", "high"]))
- resp = widget(self.request)
- self.assertEqual('{"item": [1, 2, 3], "settings": '
- '{"axisx": ["first", "last"], "axisy": ["low", "high"]}}',
- resp.content)
-
- def test_color(self):
- widget = geckoboard.line_chart(lambda r: ([1, 2, 3],
- ["first", "last"], ["low", "high"], "00112233"))
- resp = widget(self.request)
- self.assertEqual('{"item": [1, 2, 3], "settings": '
- '{"axisx": ["first", "last"], "axisy": ["low", "high"], '
- '"colour": "00112233"}}', resp.content)
-
-
-class GeckOMeterDecoratorTestCase(TestCase):
- """
- Tests for the ``line_chart`` decorator.
- """
-
- def setUp(self):
- super(GeckOMeterDecoratorTestCase, self).setUp()
- self.settings_manager.delete('GECKOBOARD_API_KEY')
- self.request = HttpRequest()
- self.request.POST['format'] = '2'
-
- def test_scalars(self):
- widget = geckoboard.geck_o_meter(lambda r: (2, 1, 3))
- resp = widget(self.request)
- self.assertEqual('{"item": 2, "max": {"value": 3}, '
- '"min": {"value": 1}}', resp.content)
-
- def test_tuples(self):
- widget = geckoboard.geck_o_meter(lambda r: (2, (1, "min"), (3, "max")))
- resp = widget(self.request)
- self.assertEqual('{"item": 2, "max": {"value": 3, "text": "max"}, '
- '"min": {"value": 1, "text": "min"}}', resp.content)
diff --git a/docs/install.rst b/docs/install.rst
index 048c4fb..39c5ba1 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -12,11 +12,6 @@ configuring the services you use in the project settings.
#. `Adding the template tags to the base template`_
#. `Enabling the services`_
-.. note::
-
- The Geckoboard integration differs from that of the other services.
- See :doc:`Geckoboard ` for installation
- instruction if you are not interested in the other services.
.. _installing-the-package:
diff --git a/docs/services/geckoboard.rst b/docs/services/geckoboard.rst
deleted file mode 100644
index 526c729..0000000
--- a/docs/services/geckoboard.rst
+++ /dev/null
@@ -1,242 +0,0 @@
-==========================
-Geckoboard -- status board
-==========================
-
-Geckoboard_ is a hosted, real-time status board serving up indicators
-from web analytics, CRM, support, infrastructure, project management,
-sales, etc. It can be connected to virtually any source of quantitative
-data.
-
-.. _Geckoboard: https://www.geckoboard.com/
-
-
-.. _geckoboard-installation:
-
-Installation
-============
-
-Geckoboard works differently from most other analytics services in that
-it pulls measurement data from your website periodically. You will not
-have to change anything in your existing templates, and there is no need
-to install the ``analytical`` application to use the integration.
-Instead you will use custom views to serve the data to Geckoboard custom
-widgets.
-
-
-.. _geckoboard-configuration:
-
-Configuration
-=============
-
-If you want to protect the data you send to Geckoboard from access by
-others, you can use an API key shared by Geckoboard and your widget
-views. Set :const:`GECKOBOARD_API_KEY` in the project
-:file:`settings.py` file::
-
- GECKOBOARD_API_KEY = 'XXXXXXXXX'
-
-Provide the API key to the custom widget configuration in Geckoboard.
-If you do not set an API key, anyone will be able to view the data by
-visiting the widget URL.
-
-
-Creating custom widgets and charts
-==================================
-
-The available custom widgets are described in the Geckoboard support
-section, under `Geckoboard API`_. From the perspective of a Django
-project, a custom widget is just a view. The django-analytical
-application provides view decorators that render the correct response
-for the different widgets. When you create a custom widget, enter the
-following information:
-
-URL data feed
- The view URL.
-
-API key
- The content of the :const:`GECKOBOARD_API_KEY` setting, if you have
- set it.
-
-Widget type
- *Custom*
-
-Feed format
- Either *XML* or *JSON*. The view decorators will automatically
- detect and output the correct format.
-
-Request type
- Either *GET* or *POST*. The view decorators accept both.
-
-Then create a view using one of the decorators from the
-:mod:`analytical.geckoboard` module.
-
-.. decorator:: number
-
- Render a *Number & Secondary Stat* widget.
-
- The decorated view must return either a single value, or a list or
- tuple with one or two values. The first (or only) value represents
- the current value, the second value represents the previous value.
- For example, to render a widget that shows the number of active
- users and the difference from last week::
-
- from analytical import geckoboard
- from datetime import datetime, timedelta
- from django.contrib.auth.models import User
-
- @geckoboard.number
- def user_count(request):
- last_week = datetime.now() - timedelta(weeks=1)
- users = User.objects.filter(is_active=True)
- last_week_users = users.filter(date_joined__lt=last_week)
- return (users.count(), last_week_users.count())
-
-
-.. decorator:: rag
-
- Render a *RAG Column & Numbers* or *RAG Numbers* widget.
-
- The decorated view must return a tuple or list with three values, or
- three tuples (value, text). The values represent numbers shown in
- red, amber and green respectively. The text parameter is optional
- and will be displayed next to the value in the dashboard. For
- example, to render a widget that shows the number of comments that
- were approved or deleted by moderators in the last 24 hours::
-
- from analytical import geckoboard
- from datetime import datetime, timedelta
- from django.contrib.comments.models import Comment, CommentFlag
-
- @geckoboard.rag
- def comments(request):
- start_time = datetime.now() - timedelta(hours=24)
- comments = Comment.objects.filter(submit_date__gt=start_time)
- total_count = comments.count()
- approved_count = comments.filter(
- flags__flag=CommentFlag.MODERATOR_APPROVAL).count()
- deleted_count = Comment.objects.filter(
- flags__flag=CommentFlag.MODERATOR_DELETION).count()
- pending_count = total_count - approved_count - deleted_count
- return (
- (deleted_count, "Deleted comments"),
- (pending_count, "Pending comments"),
- (approved_count, "Approved comments"),
- )
-
-
-.. decorator:: text
-
- Render a *Text* widget.
-
- The decorated view must return either a string, a list or tuple of
- strings, or a list or tuple of tuples (string, type). The type
- parameter tells Geckoboard how to display the text. Use
- :const:`TEXT_INFO` for informational messages, :const:`TEXT_WARN`
- for warnings and :const:`TEXT_NONE` for plain text (the default).
- For example, to render a widget showing the latest Geckoboard
- twitter updates::
-
- from analytical import geckoboard
- import twitter
-
- @geckoboard.text
- def twitter_status(request):
- twitter = twitter.Api()
- updates = twitter.GetUserTimeline('geckoboard')
- return [(u.text, geckoboard.TEXT_NONE) for u in updates]
-
-
-
-.. decorator:: pie_chart
-
- Render a *Pie chart* widget.
-
- The decorated view must return a list or tuple of tuples
- (value, label, color). The color parameter is a string
- ``'RRGGBB[TT]'`` representing red, green, blue and optionally
- transparency. For example, to render a widget showing the number
- of normal, staff and superusers::
-
- from analytical import geckoboard
- from django.contrib.auth.models import User
-
- @geckoboard.pie_chart
- def user_types(request):
- users = User.objects.filter(is_active=True)
- total_count = users.count()
- superuser_count = users.filter(is_superuser=True).count()
- staff_count = users.filter(is_staff=True,
- is_superuser=False).count()
- normal_count = total_count = superuser_count - staff_count
- return [
- (normal_count, "Normal users", "ff8800"),
- (staff_count, "Staff", "00ff88"),
- (superuser_count, "Superusers", "8800ff"),
- ]
-
-
-.. decorator:: line_chart
-
- Render a *Line chart* widget.
-
- The decorated view must return a tuple (values, x_axis, y_axis,
- color). The value parameter is a tuple or list of data points. The
- x-axis parameter is a label string, or a tuple or list of strings,
- that will be placed on the X-axis. The y-axis parameter works
- similarly for the Y-axis. If there are more axis labels, they are
- placed evenly along the axis. The optional color parameter is a
- string ``'RRGGBB[TT]'`` representing red, green, blue and optionally
- transparency. For example, to render a widget showing the number
- of comments per day over the last four weeks (including today)::
-
- from analytical import geckoboard
- from datetime import date, timedelta
- from django.contrib.comments.models import Comment
-
- @geckoboard.line_chart
- def comment_trend(request):
- since = date.today() - timedelta(days=29)
- days = dict((since + timedelta(days=d), 0)
- for d in range(0, 29))
- comments = Comment.objects.filter(submit_date=since)
- for comment in comments:
- days[comment.submit_date.date()] += 1
- return (
- days.values(),
- [days[i] for i in range(0, 29, 7)],
- "Comments",
- )
-
-
-.. decorator:: geck_o_meter
-
- Render a *Geck-O-Meter* widget.
-
- The decorated view must return a tuple (value, min, max). The value
- parameter represents the current value. The min and max parameters
- represent the minimum and maximum value respectively. They are
- either a value, or a tuple (value, text). If used, the text
- parameter will be displayed next to the minimum or maximum value.
- For example, to render a widget showing the number of users that
- have logged in in the last 24 hours::
-
- from analytical import geckoboard
- from datetime import datetime, timedelta
- from django.contrib.auth.models import User
-
- @geckoboard.geck_o_meter
- def login_count(request):
- since = datetime.now() - timedelta(hours=24)
- users = User.objects.filter(is_active=True)
- total_count = users.count()
- logged_in_count = users.filter(last_login__gt=since).count()
- return (logged_in_count, 0, total_count)
-
-
-.. _`Geckoboard API`: http://geckoboard.zendesk.com/forums/207979-geckoboard-api
-
-
-----
-
-Thanks go to Geckoboard for their support with the development of this
-application.