diff --git a/django/website/dashboard/templatetags/render_widget.py b/django/website/dashboard/templatetags/render_widget.py
index e9de818e4e26ce5f3f65760b9103bdc03bf3ac60..175ea4a9dbe41215b0aa2f749d70d0a4917c79e2 100644
--- a/django/website/dashboard/templatetags/render_widget.py
+++ b/django/website/dashboard/templatetags/render_widget.py
@@ -4,7 +4,7 @@ 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, MissingWidgetType
+from dashboard.widget_pool import get_widget, MissingWidgetType, WidgetError
 
 
 logger = logging.getLogger(__name__)
@@ -53,6 +53,10 @@ def render_widget(widget_instance):
             context = widget.get_context_data(**settings)
         except AttributeError:
             context = {}
+        except WidgetError as e:
+            template_name = 'dashboard/widget-error.html'
+            context = {}
+            context['error'] = str(e)
         except Exception as e:
             logger.exception('Error while fetching widget context data: %s', e)
             template_name = 'dashboard/widget-error.html'
diff --git a/django/website/dashboard/tests/widget_tests.py b/django/website/dashboard/tests/widget_tests.py
index 2b69bc3326e97cee9a4961087bde2f175c569fcf..0719de5c97acb8e27e8f37436db2bd25208a86a0 100644
--- a/django/website/dashboard/tests/widget_tests.py
+++ b/django/website/dashboard/tests/widget_tests.py
@@ -3,7 +3,7 @@ from mock import patch
 from django.test import TestCase
 
 from dashboard.templatetags.render_widget import render_widget
-from dashboard.widget_pool import register_widget, get_widget
+from dashboard.widget_pool import register_widget, get_widget, WidgetError
 
 
 class TestWidget(object):
@@ -38,6 +38,26 @@ class TestWidgetNoTemplateName(object):
         return {}
 
 
+class TestWidgetRaisesException(object):
+    """ A test widget which raises a generic exception in
+        get_context_data
+    """
+    template_name = 'something.html'
+
+    def get_context_data(self, **kwargs):
+        raise Exception('message raised from get_context_data')
+
+
+class TestWidgetRaisesWidgetError(object):
+    """ A test widget which raises a WidgetError in
+        get_context_data
+    """
+    template_name = 'something.html'
+
+    def get_context_data(self, **kwargs):
+        raise WidgetError('message raised from get_context_data')
+
+
 class MockWidgetInstance(object):
     """ A Mock class to represent a widget instance
 
@@ -152,3 +172,48 @@ class WidgetPoolTestCase(TestCase):
         self.assertEqual(
             template_name, 'dashboard/widget-error.html'
         )
+
+    def test_render_widget_exception_includes_generic_message(self):
+        """ Test that a widget which raises a generic exception will
+            display a generic error message, not the content of the
+            exception
+        """
+        test_widget = TestWidgetRaisesException()
+        register_widget('test-widget', test_widget)
+        widget_instance = MockWidgetInstance('test-widget')
+        with patch(self.render_to_string_method) as mock:
+            render_widget(widget_instance)
+            template_name = self.get_mock_render_to_string_parameter(
+                mock, 'template_name'
+            )
+            context = self.get_mock_render_to_string_parameter(
+                mock, 'context'
+            )
+        self.assertEqual(
+            template_name, 'dashboard/widget-error.html'
+        )
+        self.assertEqual(
+            context['error'], 'Widget error. See error logs.'
+        )
+
+    def test_render_widget_widgeterror_exception_includes_error_message(self):
+        """ Test that a widget which raises a WidgetError exception will
+            display the error message provided in the exception.
+        """
+        test_widget = TestWidgetRaisesWidgetError()
+        register_widget('test-widget', test_widget)
+        widget_instance = MockWidgetInstance('test-widget')
+        with patch(self.render_to_string_method) as mock:
+            render_widget(widget_instance)
+            template_name = self.get_mock_render_to_string_parameter(
+                mock, 'template_name'
+            )
+            context = self.get_mock_render_to_string_parameter(
+                mock, 'context'
+            )
+        self.assertEqual(
+            template_name, 'dashboard/widget-error.html'
+        )
+        self.assertEqual(
+            str(context['error']), 'message raised from get_context_data'
+        )
diff --git a/django/website/dashboard/widget_pool.py b/django/website/dashboard/widget_pool.py
index 98cc08bc6c92c660e979490e8a1074b516db8f9e..83bc41577f503bddd040d69e6ac87429a80bfa75 100644
--- a/django/website/dashboard/widget_pool.py
+++ b/django/website/dashboard/widget_pool.py
@@ -6,6 +6,17 @@ class MissingWidgetType(Exception):
     pass
 
 
+class WidgetError(Exception):
+    """ Exception that can be raised from widget types
+        in get_context_data.
+
+        The error message will be displayed to the end
+        user, so should not contain debug or sensisitve
+        information
+    """
+    pass
+
+
 def register_widget(name, widget):
     """ Register a new widget type
 
diff --git a/django/website/data_layer/migrations/0005_message_last_modified.py b/django/website/data_layer/migrations/0005_message_last_modified.py
new file mode 100644
index 0000000000000000000000000000000000000000..4570f9d33c61a323c23e5707fb3bb98d32de42c6
--- /dev/null
+++ b/django/website/data_layer/migrations/0005_message_last_modified.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import datetime
+from django.utils.timezone import utc
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('data_layer', '0004_auto_20150720_1723'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='message',
+            name='last_modified',
+            field=models.DateTimeField(default=datetime.datetime(2015, 7, 29, 11, 25, 40, 915975, tzinfo=utc), auto_now=True),
+            preserve_default=False,
+        ),
+    ]
diff --git a/django/website/data_layer/models.py b/django/website/data_layer/models.py
index fa04cfe55ba847dac221853f1f24ea3953ad2e7d..97fbebfad810f236561d0ad8cdb82da7288227ea 100644
--- a/django/website/data_layer/models.py
+++ b/django/website/data_layer/models.py
@@ -1,13 +1,21 @@
 from django.db import models
+from django.dispatch.dispatcher import receiver
+from django.utils import timezone
+
 from taxonomies.models import Term
 
 
 class DataLayerModel(models.Model):
     created = models.DateTimeField(auto_now_add=True)
+    last_modified = models.DateTimeField(auto_now=True)
 
     class Meta:
         abstract = True
 
+    def note_external_modification(self):
+        # This will set the last_modified field
+        self.save()
+
 
 class Message(DataLayerModel):
     body = models.TextField()
@@ -42,3 +50,18 @@ class Message(DataLayerModel):
 
 # TODO: rename this class
 Item = Message
+
+
+@receiver(models.signals.m2m_changed, sender=Item.terms.through,
+          dispatch_uid="data_layer.models.terms_signal_handler")
+def terms_signal_handler(sender, **kwargs):
+    if kwargs.get('action') not in ('post_add', 'post_remove'):
+        return
+
+    if kwargs.get('reverse'):
+        items = Item.objects.filter(pk__in=kwargs.get('pk_set'))
+    else:
+        items = [kwargs.get('instance')]
+
+    for item in items:
+        item.note_external_modification()
diff --git a/django/website/data_layer/tests/item_tests.py b/django/website/data_layer/tests/item_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..404aa938106715f743f32da04a70393d9cac4797
--- /dev/null
+++ b/django/website/data_layer/tests/item_tests.py
@@ -0,0 +1,147 @@
+import pytest
+import datetime
+from mock import patch, MagicMock
+
+from django.utils import timezone
+
+from factories import ItemFactory
+from ..models import Item
+from taxonomies.tests.factories import TermFactory, TaxonomyFactory
+
+
+def last_modified(item):
+    return Item.objects.get(id=item.id).last_modified
+
+
+# Ensure value of "now" always increases by amount sufficient
+# to show up as a change, even if db resolution for datetime
+# is one second.
+def time_granularity():
+    return datetime.timedelta(hours=1)
+
+
+def now_iter(start):
+    t = start
+    while True:
+        t += time_granularity()
+        yield t
+
+
+def num_updates(old_time, new_time):
+    elapsed_time = new_time - old_time
+
+    return elapsed_time.seconds / time_granularity().seconds
+
+
+@pytest.fixture
+def item():
+    return ItemFactory()
+
+
+@pytest.fixture
+def mock_time_now():
+    return MagicMock(wraps=timezone.now,
+                     side_effect=now_iter(timezone.now()))
+
+
+@pytest.mark.django_db
+def test_last_modified_date_updates_on_body_change(item, mock_time_now):
+    with patch('django.utils.timezone.now', new=mock_time_now):
+        orig_last_modified = last_modified(item)
+        item.body = 'replacement text'
+        item.save()
+
+        assert num_updates(orig_last_modified, last_modified(item)) == 1
+
+
+@pytest.mark.django_db
+def test_last_modified_date_updates_on_item_category_add(
+        item, mock_time_now):
+    with patch('django.utils.timezone.now', new=mock_time_now):
+        orig_last_modified = last_modified(item)
+        term = TermFactory()
+        item.terms.add(term)
+
+        assert num_updates(orig_last_modified, last_modified(item)) == 1
+
+
+@pytest.mark.django_db
+def test_last_modified_date_updates_on_item_category_delete(
+        item, mock_time_now):
+    with patch('django.utils.timezone.now', new=mock_time_now):
+        term = TermFactory()
+        item.terms.add(term)
+        orig_last_modified = last_modified(item)
+        item.terms.remove(term)
+
+        assert num_updates(orig_last_modified, last_modified(item)) == 1
+
+
+@pytest.mark.django_db
+def test_last_modified_date_updates_on_category_item_add(
+        item, mock_time_now):
+    with patch('django.utils.timezone.now', new=mock_time_now):
+        orig_last_modified = last_modified(item)
+        term = TermFactory()
+        term.items.add(item)
+
+        assert num_updates(orig_last_modified, last_modified(item)) == 1
+
+
+@pytest.mark.django_db
+def test_last_modified_date_updates_on_category_item_delete(
+        item, mock_time_now):
+    with patch('django.utils.timezone.now', new=mock_time_now):
+        term = TermFactory()
+        term.items.add(item)
+        orig_last_modified = last_modified(item)
+        term.items.remove(item)
+
+        assert num_updates(orig_last_modified, last_modified(item)) == 1
+
+
+@pytest.mark.django_db
+def test_last_modified_date_on_other_item_not_updated(
+        item, mock_time_now):
+    with patch('django.utils.timezone.now', new=mock_time_now):
+        term = TermFactory()
+        term.items.add(item)
+
+        other_item = ItemFactory()
+        term.items.add(other_item)
+
+        orig_last_modified = last_modified(other_item)
+        term.items.remove(item)
+
+        assert num_updates(orig_last_modified, last_modified(other_item)) == 0
+
+
+@pytest.mark.django_db
+def test_apply_term_replaces_term_for_categories():
+    item = ItemFactory()
+    taxonomy = TaxonomyFactory()  # Ensure multiplicity = optional
+    term1 = TermFactory(taxonomy=taxonomy)
+    term2 = TermFactory(taxonomy=taxonomy)
+    assert taxonomy.is_optional
+
+    item.apply_term(term1)
+    assert list(item.terms.all()) == [term1]
+
+    item.apply_term(term2)
+    assert list(item.terms.all()) == [term2]
+
+@pytest.mark.xfail
+# I'm putting this here to explain some of my thinking.
+@pytest.mark.django_db
+def test_apply_term_adds_term_for_tags():
+    item = ItemFactory()
+    taxonomy = TaxonomyFactory()  # Ensure multiplicity == multiple
+    term1 = TermFactory(taxonomy=taxonomy)
+    term2 = TermFactory(taxonomy=taxonomy)
+    assert taxonomy.is_multiple
+
+    item.apply_term(term1)
+    assert list(item.terms.all()) == [term1]
+
+    item.apply_term(term2)
+    assert set(item.terms.all()) == set([term1, term2])
diff --git a/django/website/hid/static/hid/widgets/chart.js b/django/website/hid/static/hid/widgets/chart.js
index 54b79baf93dab1b670af46f59ff50b0849856a0c..34bd445937a3cbbbe46058f8517b35c25ba45a37 100644
--- a/django/website/hid/static/hid/widgets/chart.js
+++ b/django/website/hid/static/hid/widgets/chart.js
@@ -39,11 +39,16 @@ FlotChart = {
 
         /* Render the chart */
         render: function() {
+            var options = this.chart.get('options');
+            var $legend_container = this.$el.siblings('.flot-legend');
+            if ($legend_container.length > 0 && options['legend']) {
+                options['legend']['container'] = $legend_container;
+            }
             this.resize();
             $.plot(
                 this.$el,
                 this.chart.get('data'), 
-                this.chart.get('options')
+                options
             );
 
             return this;
@@ -51,8 +56,14 @@ FlotChart = {
 
         /* Resize the chart */
         resize: function() {
-            this.$el.width(this.$container.width());
-            this.$el.height(this.$container.height());
+            if (this.$container.length == 0) {
+                return;
+            }
+            var new_width = this.$container.width();
+            var chart_parent_offset = this.$el.offset().top - this.$container.offset().top;
+            var new_height = this.$container.height() - chart_parent_offset;
+            this.$el.width(new_width);
+            this.$el.height(new_height);
         },
 
         /* Display tooltip */
diff --git a/django/website/hid/templates/hid/tests/chart.html b/django/website/hid/templates/hid/tests/chart.html
index b4e4ca77c639110551fb4fc59570b1abdc5a1879..8176e48febcd8eb8b94c7e0f9c8055ae7c99c642 100644
--- a/django/website/hid/templates/hid/tests/chart.html
+++ b/django/website/hid/templates/hid/tests/chart.html
@@ -51,9 +51,11 @@
     </script>
 {% endblock %}
 {% block body_content %}
-    <div class='flot-chart' 
-         data-data='[[[23, 0], [10, 1], [150, 2], [50, 3]]]'
-         data-options='{"series": {"bars": {"show": true}}, "bars": {"horizontal": true}, "yaxis": {"ticks": [[0, "q1"], [1, "q2"], [2, "q3"], [3, "q4"]]}}'
-         style='width:300px; height:300px'
-    ></div>
+    <div class='panel-body'>
+        <div class='flot-chart'
+             data-data='[[[23, 0], [10, 1], [150, 2], [50, 3]]]'
+             data-options='{"series": {"bars": {"show": true}}, "bars": {"horizontal": true}, "yaxis": {"ticks": [[0, "q1"], [1, "q2"], [2, "q3"], [3, "q4"]]}}'
+             style='width:300px; height:300px'
+        ></div>
+    </div>
 {% endblock %}
diff --git a/django/website/hid/templates/hid/widgets/chart.html b/django/website/hid/templates/hid/widgets/chart.html
index e8a742cf81a81bf92e2297bee375bae66882a5bd..f514ef1d675c4ded785559a98bc501442c83d692 100644
--- a/django/website/hid/templates/hid/widgets/chart.html
+++ b/django/website/hid/templates/hid/widgets/chart.html
@@ -5,7 +5,9 @@
         <h2>{{title}}</h2>
     </div>
     <div class='panel-body'>
-        <div class='flot-chart' 
+        <div class='flot-legend'>
+        </div>
+        <div class='flot-chart'
              data-options='{{ options|json_data }}'
              data-data='{{ data|json_data }}'
         >
diff --git a/django/website/hid/tests/categorize_items_tests.py b/django/website/hid/tests/categorize_items_tests.py
index 1a787e49c6322eb62fcca8905694836b9f0bc4f0..d85a6023facf973b7f486ba6b29438b450162a7a 100644
--- a/django/website/hid/tests/categorize_items_tests.py
+++ b/django/website/hid/tests/categorize_items_tests.py
@@ -13,6 +13,13 @@ from ..views import add_items_categories
 ReqFactory = RequestFactory()
 
 
+@pytest.fixture
+def term():
+    # TODO rewrite using transport.terms, etc.
+    taxonomy = TaxonomyFactory(name="Test Ebola Questions")
+    return TermFactory(taxonomy=taxonomy, name="Vaccine")
+
+
 @pytest.fixture
 def terms():
     # TODO rewrite using transport.terms, etc.
@@ -31,6 +38,26 @@ def items():
     ]
 
 
+@pytest.fixture
+def item():
+    return transport.items.create({'body': 'test message one'})
+
+
+@pytest.mark.django_db
+def test_add_categories_adds_term_to_item(term, item):
+    category_list = [(item['id'], term.taxonomy.slug, term.name), ]
+
+    url = reverse('data-view-process')
+    request = ReqFactory.post(url, {'a': 'b'})
+    request = fix_messages(request)
+    add_items_categories(request, category_list)
+
+    [item_data] = transport.items.list()
+    [term_data] = item_data['terms']
+    assert term_data['name'] == term.name
+    assert term_data['taxonomy'] == term.taxonomy.slug
+
+
 @pytest.mark.django_db
 def test_add_items_categories_adds_term_to_items(terms, items):
     url = reverse('data-view-process')
diff --git a/django/website/hid/tests/term_count_widget_tests.py b/django/website/hid/tests/term_count_widget_tests.py
index 6d8e2ddec5a1fea598f9ba653a4866d6da735393..632435fa67505752cc6f5359516023d408fcbb54 100644
--- a/django/website/hid/tests/term_count_widget_tests.py
+++ b/django/website/hid/tests/term_count_widget_tests.py
@@ -1,4 +1,7 @@
+from datetime import datetime, timedelta
+from dateutil import parser
 from mock import patch
+from dashboard.widget_pool import WidgetError
 from django.test import TestCase
 from hid.widgets.term_count_chart import TermCountChartWidget
 
@@ -50,10 +53,80 @@ class TestTermCountChartWidget(TestCase):
                 title='test-name', taxonomy='tax'
             )
         self.assertEqual(
-            context_data['data'],
-            [[[345, 2], [782, 1]]]
+            context_data['data'][0]['data'],
+            [[345, 2], [782, 1]]
         )
 
+    def test_context_data_hides_legend_when_there_is_no_time_period(self):
+        widget = TermCountChartWidget()
+        with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
+            itemcount.return_value = []
+            context_data = widget.get_context_data(
+                title='test-name', taxonomy='tax'
+            )
+        self.assertEqual(context_data['options']['legend']['show'], False)
+
+    def test_context_data_includes_legend_when_there_is_a_time_period(self):
+        widget = TermCountChartWidget()
+        with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
+            itemcount.return_value = []
+            periods = [{
+                'start_time': '2015-01-01',
+                'end_time': '2015-02-02'
+            }]
+            context_data = widget.get_context_data(
+                title='test-name', taxonomy='tax', periods=periods
+            )
+        self.assertEqual(context_data['options']['legend']['show'], True)
+
+    def test_context_data_raises_widgeterror_when_more_than_one_period(self):
+        widget = TermCountChartWidget()
+        with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
+            itemcount.return_value = []
+            periods = [{
+                'start_time': '2015-01-01',
+                'end_time': '2015-02-02'
+            }, {
+                'start-time': '2015-07-08',
+                'end-time': '2015-07-09'
+            }]
+            with self.assertRaises(WidgetError):
+                widget.get_context_data(
+                    title='test-name', taxonomy='tax', periods=periods
+                )
+
+    def test_context_data_raises_widgeterror_when_date_is_not_parseable(self):
+        widget = TermCountChartWidget()
+        with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
+            itemcount.return_value = []
+            periods = [{
+                'start_time': '!!!',
+                'end_time': '2015-02-02'
+            }, {
+                'start-time': '2015-07-08',
+                'end-time': '2015-07-09'
+            }]
+            with self.assertRaises(WidgetError):
+                widget.get_context_data(
+                    title='test-name', taxonomy='tax', periods=periods
+                )
+
+    def test_get_context_data_parses_dates(self):
+        widget = TermCountChartWidget()
+        with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
+            itemcount.return_value = []
+            periods = [{
+                'start_time': '2015-01-01',
+                'end_time': '2015-02-02'
+            }]
+            widget.get_context_data(
+                title='test-name', taxonomy='tax', periods=periods
+            )
+            kwargs = itemcount.call_args[1]
+
+        self.assertEqual(kwargs['start_time'], parser.parse('2015-01-01'))
+        self.assertEqual(kwargs['end_time'], parser.parse('2015-02-02'))
+
     def test_chart_questions_are_set_as_yaxis_value_labels(self):
         widget = TermCountChartWidget()
         with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
@@ -93,7 +166,7 @@ class TestTermCountChartWidget(TestCase):
                     'count': 1000
                 },
             ]
-            counts = widget._fetch_counts('tax', 0, 'Others')
+            counts = widget._fetch_counts('tax', 0, None, None, 'Others')
 
         self.assertEqual(
             counts.items(),
@@ -127,7 +200,7 @@ class TestTermCountChartWidget(TestCase):
                 },
 
             ]
-            counts = widget._fetch_counts('tax', 3, 'Others')
+            counts = widget._fetch_counts('tax', 3, None, None, 'Others')
 
         self.assertEqual(
             counts.items(),
@@ -136,3 +209,24 @@ class TestTermCountChartWidget(TestCase):
                 ('Others', 11)
             ]
         )
+
+    def test_fetch_count_ignores_missing_start_and_end_time(self):
+        widget = TermCountChartWidget()
+
+        with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
+            widget._fetch_counts('tax', 3, None, None, 'Others')
+            itemcount_kwargs = itemcount.call_args[1]
+
+        self.assertNotIn('start_time', itemcount_kwargs)
+        self.assertNotIn('end_time', itemcount_kwargs)
+
+    def test_fetch_count_uses_start_and_end_time(self):
+        widget = TermCountChartWidget()
+        t1 = datetime.now()
+        t2 = t1 + timedelta(days=4)
+        with patch('hid.widgets.term_count_chart.term_itemcount') as itemcount:
+            widget._fetch_counts('tax', 3, t1, t2, 'Others')
+            itemcount_kwargs = itemcount.call_args[1]
+
+        self.assertEqual(t1, itemcount_kwargs['start_time'])
+        self.assertEqual(t2, itemcount_kwargs['end_time'])
diff --git a/django/website/hid/widgets/term_count_chart.py b/django/website/hid/widgets/term_count_chart.py
index 136650decd0b1317935d007359e79fa3d054e0e3..864e9972b844464d335cb15a82703cde293f0ebe 100644
--- a/django/website/hid/widgets/term_count_chart.py
+++ b/django/website/hid/widgets/term_count_chart.py
@@ -1,4 +1,9 @@
 from collections import OrderedDict
+from dashboard.widget_pool import WidgetError
+from datetime import timedelta
+from dateutil import parser
+from django.conf import settings
+from django.utils import dateformat
 from django.utils.translation import ugettext_lazy as _
 from transport.taxonomies import term_itemcount
 
@@ -23,17 +28,27 @@ class TermCountChartWidget(object):
         'hid/widgets/chart.js'
     ]
 
-    def _fetch_counts(self, taxonomy, count, other_label):
+    def _fetch_counts(self, taxonomy, count, start, end, other_label):
         """ Given a taxonomy, fetch the count per term.
 
         Args:
-            - taxonomy: Taxonomy slug
-            - count: If >0, maximum number of rows to returns. If the data
-                     has more terms, all other terms are aggregated under
-                     an 'others' section
-            - other_label: Label for the 'Others' section
+            taxonomy (str): Taxonomy slug
+            count (int): If >0, maximum number of rows to returns. If the data
+                has more terms, all other terms are aggregated under
+                an 'others' section
+            start (datetime or None): If not None, the start of the time period
+                to get the count for
+            end (datetime or None): If not None, the start of the time period
+                to get the count for
+            other_label (str): Label for the 'Others' section
         """
-        itemcount = term_itemcount(taxonomy)
+        itemcount = None
+        if start is not None and end is not None:
+            itemcount = term_itemcount(
+                taxonomy, start_time=start, end_time=end
+            )
+        else:
+            itemcount = term_itemcount(taxonomy)
         itemcount.sort(key=lambda k: int(k['count']), reverse=True)
         if count > 0:
             head = itemcount[0:count-1]
@@ -72,13 +87,74 @@ class TermCountChartWidget(object):
 
         return values, yticks
 
+    def _create_date_range_label(self, start, end):
+        """ Create a label to display a date range.
+
+        The dates are formatter such that:
+        - If either start or end include hours/minutes/seconds
+          that are not 00:00:00 then the full date time is
+          displayed;
+        - If both start and end have zero hours/minutes/seconds
+          then only the day is displayed, and the end day
+          is set to the previous day (to show an inclusive
+          range);
+
+        Args:
+            start (datetime): Start date time
+            end (datetime): End date time
+        Returns:
+            str: Label to use for the date range.
+        """
+        if not start.time() and not end.time():
+            start_str = dateformat.format(start,
+                                          settings.SHORT_DATE_FORMAT)
+            end_str = dateformat.format(end - timedelta(days=1),
+                                        settings.SHORT_DATE_FORMAT)
+        else:
+            start_str = dateformat.format(start,
+                                          settings.SHORT_DATETIME_FORMAT),
+            end_str = dateformat.format(end,
+                                        settings.SHORT_DATETIME_FORMAT)
+        if start_str == end_str:
+            return _('%(date)s') % {'date': start_str}
+        else:
+            return _('%(start)s - %(end)s') % {
+                'start': start_str,
+                'end': end_str
+            }
+
     def get_context_data(self, **kwargs):
         title = kwargs.get('title', _('(missing title)'))
         taxonomy = kwargs.get('taxonomy')
         count = kwargs.get('count', 0)
         other_label = kwargs.get('other_label', 'Others')
+        periods = kwargs.get('periods', [])
 
-        counts = self._fetch_counts(taxonomy, count, other_label)
+        if len(periods) > 1:
+            raise WidgetError('Only one time period is currently supported')
+        if len(periods) == 1:
+            try:
+                start_time = parser.parse(periods[0]['start_time'])
+                end_time = parser.parse(periods[0]['end_time'])
+            except ValueError:
+                raise WidgetError('Error parsing start/end time')
+            legend = {
+                'show': True,
+                'noColumns': 1,
+                'position': 'ne',
+                'labelBoxBorderColor': 'white',
+                'backgroundColor': 'white'
+            }
+            label = self._create_date_range_label(start_time, end_time)
+        else:
+            start_time = None
+            end_time = None
+            legend = {'show': False}
+            label = ''
+
+        counts = self._fetch_counts(
+            taxonomy, count, start_time, end_time, other_label
+        )
         (values, yticks) = self._create_axis_values(counts)
         return {
             'title': title,
@@ -117,7 +193,12 @@ class TermCountChartWidget(object):
                     'margin': 10,
                     'labelMargin': 20,
                     'backgroundColor': '#fafafa'
-                }
+                },
+                'legend': legend
             },
-            'data': [values]
+            'data': [{
+                'label': label,
+                'color': '#f29e30',
+                'data': values
+            }]
         }
diff --git a/django/website/media/less/internews.less b/django/website/media/less/internews.less
index 4e34fa209b2a0c85f875595169ca2fa89da47e97..9bc352f153b0377deb70dd4c037db4011ae6e5b0 100644
--- a/django/website/media/less/internews.less
+++ b/django/website/media/less/internews.less
@@ -403,6 +403,11 @@ body {
     height: 100%;
 }
 
+.flot-legend {
+    padding-bottom: @padding-small-vertical;
+    border-bottom: solid 1px @gray-lighter;
+}
+
 // Circle Buttons 
 
 .btn-circle {
diff --git a/django/website/rest_api/tests/categorize_items_tests.py b/django/website/rest_api/tests/categorize_items_tests.py
index 91d467b43464fc204cfaedf249eb23263b33b119..d06392f0d3dc23123d0f90ba2290b2e6c3900bcb 100644
--- a/django/website/rest_api/tests/categorize_items_tests.py
+++ b/django/website/rest_api/tests/categorize_items_tests.py
@@ -70,6 +70,15 @@ def test_item_can_haz_category(term, item):
 
 # TODO test for terms with the same name in different taxonomies
 
+@pytest.mark.django_db
+def test_categorize_item_returns_the_categorized_item(term, item):
+    result = categorize_item(item, term).data
+
+    assert result['id'] == item['id']
+    terms = result['terms']
+    assert term in terms
+
+
 @pytest.mark.django_db
 def test_categorize_item_fails_gracefully_if_term_not_found(item):
     response = categorize_item(
@@ -81,6 +90,15 @@ def test_categorize_item_fails_gracefully_if_term_not_found(item):
     assert response.data['detail'] == "Term matching query does not exist."
 
 
+@pytest.mark.django_db
+def test_categorize_item_fails_gracefully_if_item_not_found(term, item):
+    unknown_item_id = 6  # I am not a prisoner
+    response = categorize_item({'id': unknown_item_id}, term)
+
+    assert response.status_code == status.HTTP_404_NOT_FOUND
+    assert response.data['detail'] == "Message matching query does not exist."
+
+
 @pytest.mark.django_db
 def test_only_one_category_per_item_per_taxonomy(item, term, second_term):
     """
diff --git a/django/website/rest_api/views.py b/django/website/rest_api/views.py
index 2ea8627f98554e13976f1145a0e8cd31d087a12f..f58b2910d48f15bf149d92168e8fe471ee50fa9c 100644
--- a/django/website/rest_api/views.py
+++ b/django/website/rest_api/views.py
@@ -37,7 +37,12 @@ class ItemViewSet(viewsets.ModelViewSet, BulkDestroyModelMixin):
 
     @detail_route(methods=['post'])
     def add_term(self, request, item_pk):
-        item = Item.objects.get(pk=item_pk)
+        try:
+            item = Item.objects.get(pk=item_pk)
+        except Item.DoesNotExist as e:
+            data = {'detail': e.message}
+            return Response(data, status=status.HTTP_404_NOT_FOUND)
+
         term_data = request.data
         try:
             term = Term.objects.by_taxonomy(
@@ -49,8 +54,9 @@ class ItemViewSet(viewsets.ModelViewSet, BulkDestroyModelMixin):
             return Response(data, status=status.HTTP_400_BAD_REQUEST)
 
         item.apply_term(term)
-        data = {}  # TODO should be the item containing the new term
-        return Response(data, status=status.HTTP_200_OK)
+
+        serializer = ItemSerializer(item)
+        return Response(serializer.data, status=status.HTTP_200_OK)
 
 
 class TaxonomyViewSet(viewsets.ModelViewSet):
diff --git a/django/website/settings.py b/django/website/settings.py
index 96f2f289b9e81a2e8dc5e925aec4b31d983ca6fc..286d53cb61cb22b856c28cc2de7bf6b85f16d26b 100644
--- a/django/website/settings.py
+++ b/django/website/settings.py
@@ -64,6 +64,7 @@ USE_L10N = True
 USE_TZ = True
 
 SHORT_DATETIME_FORMAT = 'd M Y H:i'
+SHORT_DATE_FORMAT = 'd M Y'
 # TODO this is used in hid/tables.py
 # and should probably use FORMAT_MODULE_PATH instead.?
 
diff --git a/django/website/taxonomies/models.py b/django/website/taxonomies/models.py
index 5d50b06936790e18db0bff24cc2e71a2f7e33ff6..da8295e2aca041c95359430ab217f4bf2a191a81 100644
--- a/django/website/taxonomies/models.py
+++ b/django/website/taxonomies/models.py
@@ -82,7 +82,10 @@ class TermManager(models.Manager):
             raise ValueError(
                 "taxonomy must be a Taxonomy instance "
                 "or a valid taxonomy slug")
-        return self.get(taxonomy__slug=taxonomy_slug, name=name)
+        return self.select_related('taxonomy').get(
+            taxonomy__slug=taxonomy_slug,
+            name=name
+        )
 
 
 class Term(models.Model):
diff --git a/django/website/transport/items.py b/django/website/transport/items.py
index 1bf54b00bc2adc0e53be8e573eade7dc6625f6a7..e7691947c442835c2efe4f4689fd77c13d38f8c5 100644
--- a/django/website/transport/items.py
+++ b/django/website/transport/items.py
@@ -113,4 +113,6 @@ def add_term(item_id, taxonomy_slug, name):
         return response.data
     else:
         response.data['status_code'] = response.status_code
+        response.data['term'] = term
+        response.data['item_id'] = item_id
         raise TransportException(response.data)
diff --git a/django/website/transport/tests/item_add_term_tests.py b/django/website/transport/tests/item_add_term_tests.py
index 1c189de16477b5ec06be65442bd5040c4f8904b6..0e877ecdd72457e0ad4c9c43a65b34ad51c67035 100644
--- a/django/website/transport/tests/item_add_term_tests.py
+++ b/django/website/transport/tests/item_add_term_tests.py
@@ -3,9 +3,7 @@ import pytest
 
 from data_layer.models import Item
 
-from taxonomies.tests.factories import (
-    TaxonomyFactory,
-    TermFactory)
+from taxonomies.tests.factories import TermFactory
 from transport import items
 from ..exceptions import TransportException
 
@@ -42,3 +40,19 @@ def test_add_term_fails_if_term_does_not_exist(item_data):
 
     assert error['status_code'] == 400
     assert error['detail'] == "Term matching query does not exist."
+    assert error['term']['name'] == "unknown term name"
+
+
+@pytest.mark.django_db
+def test_add_term_fails_if_item_does_not_exist():
+    with pytest.raises(TransportException) as excinfo:
+        term = TermFactory()
+        unknown_item_id = 6  # I am a Free Man
+        items.add_term(unknown_item_id, term.taxonomy.slug, term.name)
+
+    error = excinfo.value.message
+
+    assert error['status_code'] == 404
+    assert error['detail'] == "Message matching query does not exist."
+    # TODO: assert error['detail'] == "Item matching query does not exist."
+    assert error['item_id'] == unknown_item_id