Merge pull request #10253 from edx/renzo/studio-entity-plugins
Groundwork for Studio extensibility
This commit is contained in:
64
cms/lib/studio_tabs.py
Normal file
64
cms/lib/studio_tabs.py
Normal file
@@ -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
|
||||
0
cms/lib/tests/__init__.py
Normal file
0
cms/lib/tests/__init__.py
Normal file
35
cms/lib/tests/test_studio_tabs.py
Normal file
35
cms/lib/tests/test_studio_tabs.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user