From 32460a990eaf994216ce99c0d1f8c00a809975e5 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 19 Oct 2015 11:25:55 -0400 Subject: [PATCH] Groundwork for Studio extensibility Implement a Studio tab plugin manager, define a namespace for Studio tab plugins, and introduce an abstract base class used to represent Studio tabs. This does not introduce changes required to inject such plugins into the Studio UI. ECOM-2187. --- cms/lib/studio_tabs.py | 64 +++++++++++++++++++++++++++++++ cms/lib/tests/__init__.py | 0 cms/lib/tests/test_studio_tabs.py | 35 +++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 cms/lib/studio_tabs.py create mode 100644 cms/lib/tests/__init__.py create mode 100644 cms/lib/tests/test_studio_tabs.py diff --git a/cms/lib/studio_tabs.py b/cms/lib/studio_tabs.py new file mode 100644 index 0000000000..d91f6fa9d8 --- /dev/null +++ b/cms/lib/studio_tabs.py @@ -0,0 +1,64 @@ +"""Studio tab plugin manager and API.""" +import abc + +from openedx.core.lib.api.plugins import PluginManager + + +class StudioTabPluginManager(PluginManager): + """Manager for all available Studio tabs. + + Examples of Studio tabs include Courses, Libraries, and Programs. All Studio + tabs should implement `StudioTab`. + """ + NAMESPACE = 'openedx.studio_tab' + + @classmethod + def get_enabled_tabs(cls): + """Returns a list of enabled Studio tabs.""" + tabs = cls.get_available_plugins() + enabled_tabs = [tab for tab in tabs.viewvalues() if tab.is_enabled()] + + return enabled_tabs + + +class StudioTab(object): + """Abstract class used to represent Studio tabs. + + Examples of Studio tabs include Courses, Libraries, and Programs. + """ + __metaclass__ = abc.ABCMeta + + @abc.abstractproperty + def tab_text(self): + """Text to display in a tab used to navigate to a list of instances of this tab. + + Should be internationalized using `ugettext_noop()` since the user won't be available in this context. + """ + pass + + @abc.abstractproperty + def button_text(self): + """Text to display in a button used to create a new instance of this tab. + + Should be internationalized using `ugettext_noop()` since the user won't be available in this context. + """ + pass + + @abc.abstractproperty + def view_name(self): + """Name of the view used to render this tab. + + Used within templates in conjuction with Django's `reverse()` to generate a URL for this tab. + """ + pass + + @abc.abstractmethod + def is_enabled(cls, user=None): # pylint: disable=no-self-argument,unused-argument + """Indicates whether this tab should be enabled. + + This is a class method; override with @classmethod. + + Keyword Arguments: + user (User): The user signed in to Studio. + """ + pass diff --git a/cms/lib/tests/__init__.py b/cms/lib/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cms/lib/tests/test_studio_tabs.py b/cms/lib/tests/test_studio_tabs.py new file mode 100644 index 0000000000..35140c5464 --- /dev/null +++ b/cms/lib/tests/test_studio_tabs.py @@ -0,0 +1,35 @@ +"""Tests for the Studio tab plugin API.""" +from django.test import TestCase +import mock + +from cms.lib.studio_tabs import StudioTabPluginManager +from openedx.core.lib.api.plugins import PluginError + + +class TestStudioTabPluginApi(TestCase): + """Unit tests for the Studio tab plugin API.""" + + @mock.patch('cms.lib.studio_tabs.StudioTabPluginManager.get_available_plugins') + def test_get_enabled_tabs(self, get_available_plugins): + """Verify that only enabled tabs are retrieved.""" + enabled_tab = self._mock_tab(is_enabled=True) + mock_tabs = { + 'disabled_tab': self._mock_tab(), + 'enabled_tab': enabled_tab, + } + + get_available_plugins.return_value = mock_tabs + + self.assertEqual(StudioTabPluginManager.get_enabled_tabs(), [enabled_tab]) + + def test_get_invalid_plugin(self): + """Verify that get_plugin fails when an invalid plugin is requested.""" + with self.assertRaises(PluginError): + StudioTabPluginManager.get_plugin('invalid_tab') + + def _mock_tab(self, is_enabled=False): + """Generate a mock tab.""" + tab = mock.Mock() + tab.is_enabled = mock.Mock(return_value=is_enabled) + + return tab