From 246c72cc033cd93f621eff056c12916384a9dbaf Mon Sep 17 00:00:00 2001 From: Joost Cassee Date: Tue, 15 Feb 2011 16:07:32 +0100 Subject: [PATCH] Split off Geckoboard support into django-geckoboard --- CHANGELOG.rst | 6 + README.rst | 2 - analytical/__init__.py | 2 +- analytical/geckoboard.py | 296 -------------------------- analytical/tests/__init__.py | 1 - analytical/tests/test_geckoboard.py | 317 ---------------------------- docs/install.rst | 5 - docs/services/geckoboard.rst | 242 --------------------- 8 files changed, 7 insertions(+), 864 deletions(-) delete mode 100644 analytical/geckoboard.py delete mode 100644 analytical/tests/test_geckoboard.py delete mode 100644 docs/services/geckoboard.rst 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' - '23', 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.