From ea6e0917e319983fc77d5af3efce13b84dc48c0a Mon Sep 17 00:00:00 2001
From: Alice Heaton <aliceh@aptivate.org>
Date: Wed, 5 Aug 2015 16:34:20 +0100
Subject: [PATCH] Refactor the ViewsItems view, which covered the view and edit
 table, into a separate (non-view) class under tabs/view_and_edit_table.py.
 When ready, this will plug straight away into the new tabbed page
 functionality - in the mean time there is a place holder view to display the
 view where it used to be found.

---
 django/website/hid/tabs/__init__.py           |   0
 .../website/hid/tabs/view_and_edit_table.py   | 311 ++++++++++++++++++
 .../website/hid/templates/hid/outer_page.html |   4 +
 .../hid/{ => tabs}/view_and_edit_buttons.html |   0
 .../view_and_edit_table.html}                 |   8 +-
 .../hid/tests/categorize_items_tests.py       |   6 +-
 django/website/hid/tests/views_tests.py       |  53 +--
 django/website/hid/urls.py                    |   7 +-
 django/website/hid/views.py                   | 258 ++-------------
 9 files changed, 367 insertions(+), 280 deletions(-)
 create mode 100644 django/website/hid/tabs/__init__.py
 create mode 100644 django/website/hid/tabs/view_and_edit_table.py
 create mode 100644 django/website/hid/templates/hid/outer_page.html
 rename django/website/hid/templates/hid/{ => tabs}/view_and_edit_buttons.html (100%)
 rename django/website/hid/templates/hid/{view.html => tabs/view_and_edit_table.html} (88%)

diff --git a/django/website/hid/tabs/__init__.py b/django/website/hid/tabs/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/django/website/hid/tabs/view_and_edit_table.py b/django/website/hid/tabs/view_and_edit_table.py
new file mode 100644
index 00000000..98c77a26
--- /dev/null
+++ b/django/website/hid/tabs/view_and_edit_table.py
@@ -0,0 +1,311 @@
+from collections import OrderedDict
+import re
+
+from django.contrib import messages
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect, QueryDict
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+
+from hid.assets import require_assets
+from hid.forms import UploadForm
+from hid.tables import ItemTable
+import transport
+from transport.exceptions import TransportException
+
+
+QUESTION_TYPE_TAXONOMY = 'ebola-questions'
+ADD_CATEGORY_PREFIX = 'add-category-'
+DELETE_COMMAND = 'delete'
+NONE_COMMAND = 'none'
+
+
+class ViewAndEditTableTab(object):
+    """ A table view that can be used to view messages,
+        categorize them (individually and in batches)
+        and delete them.
+
+        Settings:
+            filters (dict): Filters to pass to the term
+                list API
+            categories (list of str): List of taxonomy slugs
+                which indiciate the taxonomies the items
+                in this view can be categorized by.
+            columns (list of str): List of columns to display,
+                from the columns available to ItemTable. If
+                missing, all columns are displayed.
+            per_page (int): Number of items to display per
+                page. Defaults to 25.
+
+    """
+    template_name = 'hid/tabs/view_and_edit_table.html'
+
+    def _build_action_dropdown_group(self, label='', items=[], prefix=''):
+        """ Helper method to build a group of actions used in the
+            action dropdown.
+
+            Args:
+                - label: Label of the group of action;
+                - items: List of items in the group. Each item is a tupple
+                         consisting of the command suffix and the display
+                         name;
+                - prefix: A string used to prefix the command string.
+
+            Returns:
+                A dictionary representing the action group.
+        """
+        return {
+            'label': label,
+            'items': OrderedDict(
+                [(prefix + entry_cmd, entry_label)
+                    for entry_cmd, entry_label in items]
+            )
+        }
+
+    def _get_items(self, **kwargs):
+        """ Given the tab settings, return the list of items
+            to include in the page
+
+            Args:
+                **kwargs (dict): Tab settings. If present
+                    kwargs['filters'] is expected to be
+                    a dictionary of filters that is passed
+                    on to the transport API.
+            Reruns:
+                QuerySet: The items to list on the page
+        """
+        filters = kwargs.get('filters', {})
+        return transport.items.list(**filters)
+
+    def _get_columns_to_exclude(self, **kwargs):
+        """ Given the tab settings, return the columns to exclude
+            from the page
+
+            Args:
+                **kwargs (dict): Tab settings. If present
+                    kwargs['columns'] is execpted to be a list
+                    of strings listing the columns to include
+            Returns:
+                list of str: List of columns to exclude
+        """
+        all_columns = []
+        for attr, value in vars(ItemTable).items():
+            if not attr.startswith('_') and not callable(value):
+                all_columns.append(attr)
+        included_columns = kwargs.get('fields', None)
+        if included_columns is None:
+            excluded_columns = ()
+        else:
+            excluded_columns = set(all_columns) - set(included_columns)
+
+        return excluded_columns
+
+    def _get_category_options(self, **kwargs):
+        """ Given the tab settings, return the options to fill
+            the categorisation drop down on the page.
+
+            Args:
+                **kwargs (dict): Tab settings. If present,
+                   kwargs['categories'] is a list of taxonomy slugs
+                   representing the taxonomies that can be used
+                   to categorized the items in the table. At the
+                   moment only one such taxonomy is supported.
+            Returns:
+                set of (value, label) pairs: The options of the
+                    first categorie in categories. If no categories
+                    were present, this is empty.
+        """
+        taxonomy_slugs = kwargs.get('categories', [])
+        if len(taxonomy_slugs) > 1:
+            raise Exception('ViewAndEditTableTab supports up to one category')
+        if len(taxonomy_slugs) == 0:
+            return ()
+
+        terms = transport.terms.list(taxonomy=taxonomy_slugs[0])
+        terms.sort(key=lambda e: e['name'].lower())
+        return tuple((t['name'], t['name']) for t in terms)
+
+    def get_context_data(self, request, **kwargs):
+        # Build the table
+        table = ItemTable(
+            self._get_items(**kwargs),
+            categories=self._get_category_options(**kwargs),
+            exclude=self._get_columns_to_exclude(**kwargs),
+            orderable=True,
+            order_by=request.GET.get('sort', None),
+        )
+        table.paginate(
+            per_page=kwargs.get('per_page', 25),
+            page=request.GET.get('page', 1)
+        )
+
+        # Build the upload form
+        upload_form = UploadForm(initial={'source': 'geopoll'})
+
+        # Build the actions drop down
+        actions = [
+            self._build_action_dropdown_group(
+                label=_('Actions'),
+                items=[
+                    (NONE_COMMAND, '---------'),
+                    (DELETE_COMMAND, _('Delete Selected'))
+                ]
+            ),
+            self._build_action_dropdown_group(
+                label=_('Set question type'),
+                items=self._get_category_options(**kwargs),
+                prefix=ADD_CATEGORY_PREFIX
+            )
+        ]
+
+        # Ensure we have the assets we want
+        require_assets('hid/js/automatic_file_upload.js')
+        require_assets('hid/js/select_all_checkbox.js')
+
+        # And return the context
+        return {
+            'type_label': kwargs.get('label', '?'),
+            'table': table,
+            'upload_form': upload_form,
+            'actions': actions
+        }
+
+
+def _get_view_and_edit_form_request_parameters(params):
+    """ Return the parameters of the given request.
+
+    The form has mirrored inputs as the top and the
+    bottom of the form. This detects which one was used
+    to submit the form, and returns the parameters
+    associated with that one.
+
+    It is expected that:
+        - All mirrored form elements are named as
+          <name>-<placement>
+        - The submit button is called 'action',
+          and it's value is <action>-<placement>
+
+    Args:
+        params (QueryDict): GET or POST request parameters
+
+    Returns:
+        QueryDict: The list of invoked parameters renamed such
+            that the active parameters match the submit
+            button that was invoked. If no 'action' exists
+            it is defaulted to 'none' and placement to 'top'.
+    """
+    new_params = QueryDict('', mutable=True)
+    action = params.get('action', 'none-top')
+    if '-' in action:
+        placement = re.sub('^[^-]+-', '', action)
+        action = action[0:len(action) - len(placement) - 1]
+    else:
+        placement = 'top'
+    for name, value in params.iterlists():
+        if name == 'action':
+            value = [action]
+        elif name.endswith(placement):
+            name = name[0:len(name)-len(placement)-1]
+        new_params.setlist(name, value)
+    if 'action' not in new_params:
+        new_params['action'] = 'none'
+    return new_params
+
+
+def view_and_edit_table_form_process_items(request):
+    """ Request to process a selection of items from the
+        view & edit table page.
+
+        Args:
+            request (Request): This should contain
+                a POST request defining:
+                    - action: The action to apply
+                    - select_action: List of items to apply
+                      the action too.
+    """
+    redirect_url = reverse("data-view")
+    # Just redirect back to items view on GET
+    if request.method == "POST":
+        params = _get_view_and_edit_form_request_parameters(request.POST)
+        if params['action'] == 'batchupdate':
+            selected = ItemTable.get_selected(params)
+            batch_action = params['batchaction']
+            if batch_action == DELETE_COMMAND:
+                _delete_items(request, selected)
+            elif batch_action and batch_action.startswith(ADD_CATEGORY_PREFIX):
+                category = batch_action[len(ADD_CATEGORY_PREFIX):]
+                # TODO: add the taxonomy to the form.
+                _add_items_categories(
+                    request,
+                    [(item, QUESTION_TYPE_TAXONOMY, category)
+                     for item in selected]
+                )
+            elif batch_action == NONE_COMMAND:
+                pass
+            else:
+                messages.error(request, _('Unknown batch action'))
+        elif params['action'] == 'save':
+            changes = ItemTable.get_row_select_values(params, 'category')
+            # TODO: Add the taxonomy to the form
+            _add_items_categories(
+                request,
+                [(item, QUESTION_TYPE_TAXONOMY, category)
+                 for item, category in changes]
+            )
+        elif params['action'] != 'none':
+            messages.error(request, _('Unknown action'))
+
+    return HttpResponseRedirect(redirect_url)
+
+
+def _delete_items(request, deleted):
+    """ Delete the given items, and set a success/failure
+        on the request
+
+        Args:
+            request (Request): Current request object
+            items (list of int): List of items to delete
+    """
+    try:
+        transport.items.bulk_delete(deleted)
+        num_deleted = len(deleted)
+        msg = ungettext("%d item deleted.",
+                        "%d items deleted.",
+                        num_deleted) % num_deleted
+        messages.success(request, msg)
+    except:
+        msg = _("There was an error while deleting.")
+        messages.error(request, msg)
+
+
+def _add_items_categories(request, items):
+    """ Add the given category to the given items,
+        and set a success/failure on the request
+
+        Args:
+            request (Request): Current request object
+            items (list of (item id, taxonomy_slug, term_name)):
+                tupples to update.
+    """
+    success = 0
+    failed = 0
+    for item_id, taxonomy_slug, term_name in items:
+        try:
+            transport.items.add_term(
+                item_id,
+                taxonomy_slug,
+                term_name
+            )
+            success += 1
+        except TransportException:
+            failed += 1
+    if success > 0:
+        msg = ungettext("Updated %d item.",
+                        "Updated %d items.",
+                        len(items)) % len(items)
+        messages.success(request, msg)
+    if failed > 0:
+        msg = ungettext("Failed to update %d item.",
+                        "Failed to update %d items.",
+                        len(items)) % len(items)
+        messages.success(request, msg)
diff --git a/django/website/hid/templates/hid/outer_page.html b/django/website/hid/templates/hid/outer_page.html
new file mode 100644
index 00000000..a9a8928a
--- /dev/null
+++ b/django/website/hid/templates/hid/outer_page.html
@@ -0,0 +1,4 @@
+{% extends "base_side.html" %}
+{% block maincontent %}
+    {{ body }}
+{% endblock %}
diff --git a/django/website/hid/templates/hid/view_and_edit_buttons.html b/django/website/hid/templates/hid/tabs/view_and_edit_buttons.html
similarity index 100%
rename from django/website/hid/templates/hid/view_and_edit_buttons.html
rename to django/website/hid/templates/hid/tabs/view_and_edit_buttons.html
diff --git a/django/website/hid/templates/hid/view.html b/django/website/hid/templates/hid/tabs/view_and_edit_table.html
similarity index 88%
rename from django/website/hid/templates/hid/view.html
rename to django/website/hid/templates/hid/tabs/view_and_edit_table.html
index ce47656a..b46676fb 100644
--- a/django/website/hid/templates/hid/view.html
+++ b/django/website/hid/templates/hid/tabs/view_and_edit_table.html
@@ -1,9 +1,7 @@
-{% extends "base_side.html" %}
 {% load i18n %}
 {% load bootstrap3 %}
 {% load render_table from django_tables2 %}
 
-{% block maincontent %}
 <h1 class="page-header"><span class="fa fa-pencil fa-fw"></span>{% trans "View &amp; Edit" %}</h1>
  <div class='row'>
     <div class="col-lg-12">
@@ -27,18 +25,16 @@
                       class="view-items-form">
                     {% csrf_token %}
                     {% with button_placement="top" %}
-                        {% include "hid/view_and_edit_buttons.html" %}
+                        {% include "hid/tabs/view_and_edit_buttons.html" %}
                     {% endwith %}
                     {% with pagination_class="pagination-circle-nav" %}
                         {% render_table table %}
                     {% endwith %}
                     {% with button_placement="bottom" %}
-                        {% include "hid/view_and_edit_buttons.html" %}
+                        {% include "hid/tabs/view_and_edit_buttons.html" %}
                     {% endwith %}
                 </form>
             </div>
         </div>
     </div>
 </div>
-{% endblock maincontent %}
-
diff --git a/django/website/hid/tests/categorize_items_tests.py b/django/website/hid/tests/categorize_items_tests.py
index d85a6023..b431121d 100644
--- a/django/website/hid/tests/categorize_items_tests.py
+++ b/django/website/hid/tests/categorize_items_tests.py
@@ -7,7 +7,7 @@ from taxonomies.tests.factories import TermFactory, TaxonomyFactory
 
 import transport
 from .views_tests import fix_messages
-from ..views import add_items_categories
+from hid.tabs.view_and_edit_table import _add_items_categories
 
 
 ReqFactory = RequestFactory()
@@ -50,7 +50,7 @@ def test_add_categories_adds_term_to_item(term, item):
     url = reverse('data-view-process')
     request = ReqFactory.post(url, {'a': 'b'})
     request = fix_messages(request)
-    add_items_categories(request, category_list)
+    _add_items_categories(request, category_list)
 
     [item_data] = transport.items.list()
     [term_data] = item_data['terms']
@@ -73,7 +73,7 @@ def test_add_items_categories_adds_term_to_items(terms, items):
         (item_id, term.taxonomy.slug, term.name)
         for item_id, term in expected.items()
     ]
-    add_items_categories(request, category_map)
+    _add_items_categories(request, category_map)
 
     fetched_items = transport.items.list()
     found = 0
diff --git a/django/website/hid/tests/views_tests.py b/django/website/hid/tests/views_tests.py
index a283b1e0..8053f618 100644
--- a/django/website/hid/tests/views_tests.py
+++ b/django/website/hid/tests/views_tests.py
@@ -5,10 +5,11 @@ from django.core.urlresolvers import reverse
 from django.http import HttpResponseRedirect, QueryDict
 from django.test import RequestFactory
 
-from ..views import (
-    process_items,
-    delete_items,
-    ViewItems,
+from hid.tabs.view_and_edit_table import (
+    ViewAndEditTableTab,
+    view_and_edit_table_form_process_items,
+    _delete_items,
+    _get_view_and_edit_form_request_parameters,
     DELETE_COMMAND
 )
 
@@ -73,14 +74,14 @@ def check_item_was_deleted(request):
 @pytest.mark.django_db
 def test_delete_items_deletes_items(request_item):
     req, item = request_item
-    delete_items(req, [item['id']])
+    _delete_items(req, [item['id']])
     check_item_was_deleted(req)
 
 
 @pytest.mark.django_db
 def test_process_items_deletes_items(request_item):
     req, item = request_item
-    process_items(req)
+    view_and_edit_table_form_process_items(req)
     check_item_was_deleted(req)
 
 
@@ -90,14 +91,14 @@ def test_process_items_always_redirects_to_data_view():
 
     request = ReqFactory.get(url)
 
-    response = process_items(request)
+    response = view_and_edit_table_form_process_items(request)
     assert response.url == redirect_url
     assert isinstance(response, HttpResponseRedirect) is True
 
     request.method = 'POST'
     request = ReqFactory.post(url, {})
     request = fix_messages(request)
-    response = process_items(request)
+    response = view_and_edit_table_form_process_items(request)
     assert response.url == redirect_url
     assert isinstance(response, HttpResponseRedirect) is True
 
@@ -127,8 +128,8 @@ def test_get_category_options_uses_terms():
         taxonomy=other_taxonomy,
     )
 
-    view = ViewItems()
-    options = view.get_category_options(ebola_questions.slug)
+    table = ViewAndEditTableTab()
+    options = table._get_category_options(categories=[ebola_questions.slug])
 
     assert (type_1.name, type_1.name) in options
     assert (type_2.name, type_2.name) in options
@@ -136,28 +137,6 @@ def test_get_category_options_uses_terms():
     assert (other_term.name, other_term.long_name) not in options
 
 
-@pytest.mark.django_db
-def test_get_category_options_with_no_taxonomy_returns_all():
-    # TODO: Rewrite tests to use transport layer
-    ebola_questions = TaxonomyFactory(name="Ebola Questions")
-    other_taxonomy = TaxonomyFactory(name="Should be ignored")
-    type_1 = TermFactory(
-        name="Measures",
-        taxonomy=ebola_questions,
-        long_name="What measures could end Ebola?",
-    )
-    other_term = TermFactory(
-        name="Some other thing",
-        taxonomy=other_taxonomy,
-    )
-
-    view = ViewItems()
-    options = view.get_category_options()
-
-    assert (type_1.name, type_1.name) in options
-    assert (other_term.name, other_term.name) in options
-
-
 @pytest.mark.django_db
 def test_get_category_options_orders_by_lowercase_name():
     # TODO: Rewrite tests to use transport layer
@@ -173,8 +152,8 @@ def test_get_category_options_orders_by_lowercase_name():
             taxonomy=taxonomy
         )
 
-    view = ViewItems()
-    options = view.get_category_options(taxonomy.slug)
+    table = ViewAndEditTableTab()
+    options = table._get_category_options(categories=[taxonomy.slug])
 
     # Expected is the list ordered by lowercase short name.
     expected = [(short, short) for short, long_name in test_term_values]
@@ -192,7 +171,7 @@ def test_views_item_get_request_parameters_renames_items_of_active_location():
         'item': 'bottom-value',
         'item-top': 'top-value'
     }
-    actual = ViewItems.get_request_parameters(query)
+    actual = _get_view_and_edit_form_request_parameters(query)
     assert actual.dict() == expected
 
 
@@ -205,7 +184,7 @@ def test_views_item_get_request_parameters_sets_default_location():
         'item': 'top-value',
         'item-bottom': 'bottom-value'
     }
-    actual = ViewItems.get_request_parameters(query)
+    actual = _get_view_and_edit_form_request_parameters(query)
     assert actual.dict() == expected
 
 
@@ -218,5 +197,5 @@ def test_views_item_get_request_parameters_sets_default_action_and_location():
         'item': 'top-value',
         'item-bottom': 'bottom-value'
     }
-    actual = ViewItems.get_request_parameters(query)
+    actual = _get_view_and_edit_form_request_parameters(query)
     assert actual.dict() == expected
diff --git a/django/website/hid/urls.py b/django/website/hid/urls.py
index 97c6b717..a402b47f 100644
--- a/django/website/hid/urls.py
+++ b/django/website/hid/urls.py
@@ -4,15 +4,16 @@ from django.contrib.auth.decorators import login_required
 from dashboard.views import DashboardView
 
 from .views import (
-    UploadSpreadsheetView, ListSources, ViewItems, ViewSingleItem, process_items
+    view_and_edit_table, UploadSpreadsheetView, ListSources, ViewSingleItem
 )
+from hid.tabs.view_and_edit_table import view_and_edit_table_form_process_items
 
 urlpatterns = patterns('',
     url(r'^sources/upload/$', login_required(UploadSpreadsheetView.as_view()), name='sources-upload'),
     url(r'^sources/(?P<label>\w+)/$', login_required(ListSources.as_view()), name='sources-edit'),
     url(r'^sources/$', login_required(ListSources.as_view()), name='sources'),
-    url(r'^view/process/$', login_required(process_items), name="data-view-process"),
-    url(r'^view/$', login_required(ViewItems.as_view()), name="data-view"),
+    url(r'^process-items/$', login_required(view_and_edit_table_form_process_items), name="data-view-process"),
+    url(r'^view/$', login_required(view_and_edit_table), name="data-view"),
     url(r'^view/(?P<id>\d+)$', login_required(ViewSingleItem.as_view())),
     url(r'^$', login_required(DashboardView.as_view()), name='dashboard'),
 )
diff --git a/django/website/hid/views.py b/django/website/hid/views.py
index f8821f40..87ca5547 100644
--- a/django/website/hid/views.py
+++ b/django/website/hid/views.py
@@ -1,30 +1,40 @@
-import re
-
-from collections import OrderedDict
-
 from django.contrib import messages
-from django.contrib.auth.views import login
 from django.core.urlresolvers import reverse
-from django.http import HttpResponseRedirect, QueryDict
-from django.utils.translation import ugettext as _
+from django.http import HttpResponseRedirect, HttpResponse
+from django.template import RequestContext
+from django.template.loader import render_to_string
 from django.utils.translation import ungettext
 from django.views.generic import FormView
 from django.views.generic.base import TemplateView
 
-from django_tables2 import SingleTableView
-
 from chn_spreadsheet.importer import Importer, SheetImportException
-import transport
-from transport.exceptions import TransportException
-from .assets import require_assets
+from hid.tabs.view_and_edit_table import ViewAndEditTableTab
 from .forms import UploadForm, get_spreadsheet_choices
-from .tables import ItemTable
 
 
-QUESTION_TYPE_TAXONOMY = 'ebola-questions'
-ADD_CATEGORY_PREFIX = 'add-category-'
-DELETE_COMMAND = 'delete'
-NONE_COMMAND = 'none'
+def view_and_edit_table(request):
+    """ Placeholder view that renders the view&edit tab as a single
+        page.
+
+        Args:
+            request (Request): Current request
+        Returns:
+            HttpResponse: The rendered HTML
+    """
+    table = ViewAndEditTableTab()
+    context = table.get_context_data(
+        request,
+        label='Questions',
+        per_page=25,
+        categories=['ebola-questions'],
+        columns=['select_item', 'created', 'timestamp',
+                 'body', 'category']
+    )
+    html = render_to_string(
+        table.template_name, context, RequestContext(request)
+    )
+    page_html = render_to_string('hid/outer_page.html', {'body': html})
+    return HttpResponse(page_html)
 
 
 class ListSources(TemplateView):
@@ -73,123 +83,6 @@ class UploadSpreadsheetView(FormView):
         return HttpResponseRedirect(self.get_success_url())
 
 
-#
-#  VIEW & EDIT ITEMS VIEWS
-#
-class ViewItems(SingleTableView):
-    template_name = 'hid/view.html'
-    table_class = ItemTable
-    table_pagination = {
-        'per_page': 25
-    }
-
-    def get_success_url(self):
-        return reverse("data-view")
-
-    def get_queryset(self):
-        return transport.items.list()
-
-    def get_category_options(self, taxonomy_slug=None):
-        if taxonomy_slug is not None:
-            terms = transport.terms.list(taxonomy=taxonomy_slug)
-        else:
-            terms = transport.terms.list()
-        terms.sort(key=lambda e: e['name'].lower())
-        return tuple((t['name'], t['name']) for t in terms)
-
-    def get_table(self, **kwargs):
-        kwargs['categories'] = self.get_category_options(
-            QUESTION_TYPE_TAXONOMY
-        )
-        return super(ViewItems, self).get_table(**kwargs)
-
-    def get_context_data(self, **kwargs):
-        context = super(ViewItems, self).get_context_data(**kwargs)
-        context['type_label'] = _('Questions')
-        context['upload_form'] = UploadForm(initial={'source': 'geopoll'})
-        context['actions'] = [
-            self._build_action_dropdown_group(
-                label=_('Actions'),
-                items=[
-                    (NONE_COMMAND, '---------'),
-                    (DELETE_COMMAND, _('Delete Selected'))
-                ]
-            ),
-            self._build_action_dropdown_group(
-                label=_('Set question type'),
-                items=self.get_category_options(QUESTION_TYPE_TAXONOMY),
-                prefix=ADD_CATEGORY_PREFIX
-            )
-        ]
-
-        require_assets('hid/js/automatic_file_upload.js')
-        require_assets('hid/js/select_all_checkbox.js')
-        return context
-
-    def _build_action_dropdown_group(self, label='', items=[], prefix=''):
-        """ Helper method to build a group of actions used in the
-            action dropdown.
-
-            Args:
-                - label: Label of the group of action;
-                - items: List of items in the group. Each item is a tupple
-                         consisting of the command suffix and the display
-                         name;
-                - prefix: A string used to prefix the command string.
-
-            Returns:
-                A dictionary representing the action group.
-        """
-        return {
-            'label': label,
-            'items': OrderedDict(
-                [(prefix + entry_cmd, entry_label)
-                    for entry_cmd, entry_label in items]
-            )
-        }
-
-    @staticmethod
-    def get_request_parameters(params):
-        """ Return the parameters of the given request.
-
-        The form has mirrored inputs as the top and the
-        bottom of the form. This detects which one was used
-        to submit the form, and returns the parameters
-        associated with that one.
-
-        It is expected that:
-            - All mirrored form elements are named as
-              <name>-<placement>
-            - The busmit button is called 'action',
-              and it's value is <action>-<placement>
-
-        Args:
-            - params: GET or POST request parameters
-
-        Returns:
-            The list of invoked parameters renamed such
-            that the active parameters match the submit
-            button that was invoked. If no 'action' exists
-            it is defaulted to 'none' and placement to 'top'.
-        """
-        new_params = QueryDict('', mutable=True)
-        action = params.get('action', 'none-top')
-        if '-' in action:
-            placement = re.sub('^[^-]+-', '', action)
-            action = action[0:len(action) - len(placement) - 1]
-        else:
-            placement = 'top'
-        for name, value in params.iterlists():
-            if name == 'action':
-                value = [action]
-            elif name.endswith(placement):
-                name = name[0:len(name)-len(placement)-1]
-            new_params.setlist(name, value)
-        if 'action' not in new_params:
-            new_params['action'] = 'none'
-        return new_params
-
-
 class ViewSingleItem(TemplateView):
     template_name = "hid/item.html"
 
@@ -198,103 +91,6 @@ class ViewSingleItem(TemplateView):
         return ctx
 
 
-def delete_items(request, deleted):
-    """ Delete the given items, and set a success/failure
-        on the request
-
-        Args:
-            - request: Current request object
-            - items: List of items to delete
-    """
-    try:
-        transport.items.bulk_delete(deleted)
-        num_deleted = len(deleted)
-        msg = ungettext("%d item deleted.",
-                        "%d items deleted.",
-                        num_deleted) % num_deleted
-        messages.success(request, msg)
-    except:
-        msg = _("There was an error while deleting.")
-        messages.error(request, msg)
-
-
-def add_items_categories(request, items):
-    """ Add the given category to the given items,
-        and set a success/failure on the request
-
-        Args:
-            - request: Current request object
-            - items: List of (item id, taxonomy_slug, term_name) tupples to
-                     update.
-    """
-    success = 0
-    failed = 0
-    for item_id, taxonomy_slug, term_name in items:
-        try:
-            transport.items.add_term(
-                item_id,
-                taxonomy_slug,
-                term_name
-            )
-            success += 1
-        except TransportException:
-            failed += 1
-    if success > 0:
-        msg = ungettext("Updated %d item.",
-                        "Updated %d items.",
-                        len(items)) % len(items)
-        messages.success(request, msg)
-    if failed > 0:
-        msg = ungettext("Failed to update %d item.",
-                        "Failed to update %d items.",
-                        len(items)) % len(items)
-        messages.success(request, msg)
-
-
-def process_items(request):
-    """ Request to process a selection of items from the
-        view & edit page.
-
-        Args:
-            - request: Request object. This should contain
-              a POST request defining:
-                  - action: The action to apply
-                  - select_action: List of items to apply
-                    the action too.
-    """
-    redirect_url = reverse("data-view")
-    # Just redirect back to items view on GET
-    if request.method == "POST":
-        params = ViewItems.get_request_parameters(request.POST)
-        if params['action'] == 'batchupdate':
-            selected = ItemTable.get_selected(params)
-            batch_action = params['batchaction']
-            if batch_action == DELETE_COMMAND:
-                delete_items(request, selected)
-            elif batch_action and batch_action.startswith(ADD_CATEGORY_PREFIX):
-                category = batch_action[len(ADD_CATEGORY_PREFIX):]
-                add_items_categories(
-                    request,
-                    [(item, QUESTION_TYPE_TAXONOMY, category)
-                     for item in selected]
-                )
-            elif batch_action == NONE_COMMAND:
-                pass
-            else:
-                messages.error(request, _('Unknown batch action'))
-        elif params['action'] == 'save':
-            changes = ItemTable.get_row_select_values(params, 'category')
-            add_items_categories(
-                request,
-                [(item, QUESTION_TYPE_TAXONOMY, category)
-                 for item, category in changes]
-            )
-        elif params['action'] != 'none':
-            messages.error(request, _('Unknown action'))
-
-    return HttpResponseRedirect(redirect_url)
-
-
 def csrf_failure(request, reason=''):
     # If the user presses the back button in the browser to go back to the
     # login page and logs in again, they will get a CSRF error page because
-- 
GitLab