Split off Geckoboard support into django-geckoboard

This commit is contained in:
Joost Cassee 2011-02-15 16:07:32 +01:00
parent f6cbd4b744
commit 246c72cc03
8 changed files with 7 additions and 864 deletions

View file

@ -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.

View file

@ -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/

View file

@ -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"

View file

@ -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.
"""

View file

@ -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 *

View file

@ -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('<?xml version="1.0" ?><root>test</root>',
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('<?xml version="1.0" ?><root>test</root>',
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('<?xml version="1.0" ?><root>test</root>',
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('<?xml version="1.0" ?><root>test</root>',
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('<?xml version="1.0" ?><root><a>1</a><b>2</b></root>',
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('<?xml version="1.0" ?><root><list>1</list>'
'<list>2</list><list>3</list></root>', 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('<?xml version="1.0" ?><root>'
'<item><text>test1</text><value>1</value></item>'
'<item><text>test2</text><value>2</value></item></root>',
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)

View file

@ -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 <services/geckoboard>` for installation
instruction if you are not interested in the other services.
.. _installing-the-package:

View file

@ -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.