Permalink
Browse files

[#847] Add docs for extensions custom config settings

And example with tests
  • Loading branch information...
1 parent dcf11d9 commit 86d90ee175f254ed8b374c627fd9e8778c5aa911 Sean Hammond committed Dec 13, 2013
@@ -0,0 +1,30 @@
+import pylons.config as config
+
+import ckan.plugins as plugins
+import ckan.plugins.toolkit as toolkit
+
+
+def group_create(context, data_dict=None):
+
+ # Get the value of the ckan.iauthfunctions.users_can_create_groups
+ # setting from the CKAN config file as a string, or False if the setting
+ # isn't in the config file.
+ users_can_create_groups = config.get(
+ 'ckan.iauthfunctions.users_can_create_groups', False)
+
+ # Convert the value from a string to a boolean.
+ users_can_create_groups = toolkit.asbool(users_can_create_groups)
+
+ if users_can_create_groups:
+ return {'success': True}
+ else:
+ return {'success': False,
+ 'msg': 'Only sysadmins can create groups'}
+
+
+class ExampleIAuthFunctionsPlugin(plugins.SingletonPlugin):
+ plugins.implements(plugins.IAuthFunctions)
+
+ def get_auth_functions(self):
+ return {'group_create': group_create}
+
@@ -3,14 +3,95 @@
'''
import paste.fixture
import pylons.test
+import pylons.config as config
+import webtest
import ckan.model as model
import ckan.tests as tests
-import ckan.plugins as plugins
-import ckan.plugins.toolkit as toolkit
+import ckan.plugins
+import ckan.new_tests.factories as factories
-class TestExampleIAuthFunctionsPlugin(object):
+class TestExampleIAuthFunctionsCustomConfigSetting(object):
+ '''Tests for the plugin_v5_custom_config_setting module.
+
+ '''
+ def setup(self):
+
+ # Access CKAN's model directly (bad) to create a sysadmin user and save
+ # it against self for all test methods to access.
+ self.sysadmin = model.User(name='test_sysadmin', sysadmin=True)
+ model.Session.add(self.sysadmin)
+ model.Session.commit()
+ model.Session.remove()
+
+ def _get_app(self, users_can_create_groups):
+
+ # Set the custom config option in pylons.config.
+ config['ckan.iauthfunctions.users_can_create_groups'] = (
+ users_can_create_groups)
+
+ # Return a test app with the custom config.
+ app = ckan.config.middleware.make_app(config['global_conf'], **config)
+ app = webtest.TestApp(app)
+
+ ckan.plugins.load('example_iauthfunctions_v5_custom_config_setting')
+
+ return app
+
+ def teardown(self):
+
+ # Remove the custom config option from pylons.config.
+ del config['ckan.iauthfunctions.users_can_create_groups']
+
+ # Delete any stuff that's been created in the db, so it doesn't
+ # interfere with the next test.
+ model.repo.rebuild_db()
+
+ @classmethod
+ def teardown_class(cls):
+ ckan.plugins.unload('example_iauthfunctions_v5_custom_config_setting')
+
+ def test_sysadmin_can_create_group_when_config_is_False(self):
+ app = self._get_app(users_can_create_groups=False)
+
+ tests.call_action_api(app, 'group_create', name='test-group',
+ apikey=self.sysadmin.apikey)
+
+ def test_user_cannot_create_group_when_config_is_False(self):
+ app = self._get_app(users_can_create_groups=False)
+ user = factories.User()
+
+ tests.call_action_api(app, 'group_create', name='test-group',
+ apikey=user['apikey'], status=403)
+
+ def test_visitor_cannot_create_group_when_config_is_False(self):
+ app = self._get_app(users_can_create_groups=False)
+
+ tests.call_action_api(app, 'group_create', name='test-group',
+ status=403)
+
+ def test_sysadmin_can_create_group_when_config_is_True(self):
+ app = self._get_app(users_can_create_groups=True)
+
+ tests.call_action_api(app, 'group_create', name='test-group',
+ apikey=self.sysadmin.apikey)
+
+ def test_user_can_create_group_when_config_is_True(self):
+ app = self._get_app(users_can_create_groups=True)
+ user = factories.User()
+
+ tests.call_action_api(app, 'group_create', name='test-group',
+ apikey=user['apikey'])
+
+ def test_visitor_cannot_create_group_when_config_is_True(self):
+ app = self._get_app(users_can_create_groups=True)
+
+ tests.call_action_api(app, 'group_create', name='test-group',
+ status=403)
+
+
+class TestExampleIAuthFunctionsPluginV4(object):
'''Tests for the ckanext.example_iauthfunctions.plugin module.
'''
@@ -24,7 +105,7 @@ def setup_class(cls):
# Test code should use CKAN's plugins.load() function to load plugins
# to be tested.
- plugins.load('example_iauthfunctions')
+ ckan.plugins.load('example_iauthfunctions_v4')
def setup(self):
'''Nose runs this method before each test method in our test class.'''
@@ -51,7 +132,7 @@ def teardown_class(cls):
'''
# We have to unload the plugin we loaded, so it doesn't affect any
# tests that run after ours.
- plugins.unload('example_iauthfunctions')
+ ckan.plugins.unload('example_iauthfunctions_v4')
def _make_curators_group(self):
'''This is a helper method for test methods to call when they want
@@ -129,18 +210,18 @@ def test_group_create_with_curator(self):
assert result['name'] == name
-class TestExampleIAuthFunctionsPluginV3(TestExampleIAuthFunctionsPlugin):
+class TestExampleIAuthFunctionsPluginV3(TestExampleIAuthFunctionsPluginV4):
'''Tests for the ckanext.example_iauthfunctions.plugin_v3 module.
'''
@classmethod
def setup_class(cls):
cls.app = paste.fixture.TestApp(pylons.test.pylonsapp)
- plugins.load('example_iauthfunctions_v3')
+ ckan.plugins.load('example_iauthfunctions_v3')
@classmethod
def teardown_class(cls):
- plugins.unload('example_iauthfunctions_v3')
+ ckan.plugins.unload('example_iauthfunctions_v3')
def test_group_create_with_no_curators_group(self):
'''Test that group_create returns a 404 when there's no curators group.
@@ -175,18 +256,18 @@ def test_group_create_with_visitor(self):
assert response['__type'] == 'Authorization Error'
-class TestExampleIAuthFunctionsPluginV2(TestExampleIAuthFunctionsPlugin):
+class TestExampleIAuthFunctionsPluginV2(TestExampleIAuthFunctionsPluginV4):
'''Tests for the ckanext.example_iauthfunctions.plugin_v2 module.
'''
@classmethod
def setup_class(cls):
cls.app = paste.fixture.TestApp(pylons.test.pylonsapp)
- plugins.load('example_iauthfunctions_v2')
+ ckan.plugins.load('example_iauthfunctions_v2')
@classmethod
def teardown_class(cls):
- plugins.unload('example_iauthfunctions_v2')
+ ckan.plugins.unload('example_iauthfunctions_v2')
def test_group_create_with_curator(self):
'''Test that a curator can*not* create a group.
@@ -0,0 +1,42 @@
+==========================================
+Using custom config settings in extensions
+==========================================
+
+Extensions can define their own custom config settings that users can add to
+their CKAN config files to configure the behavior of the extension.
+
+Continuing with the :py:class:`~ckan.plugins.interfaces.IAuthFunctions` example
+from :doc:`tutorial`, let's make an alternative version of the extension that
+allows users to create new groups if a new config setting
+``ckan.iauthfunctions.users_can_create_groups`` is ``True``:
+
+.. literalinclude:: ../../ckanext/example_iauthfunctions/plugin_v5_custom_config_setting.py
+
+The ``group_create`` authorization function in this plugin uses
+:py:obj:`pylons.config` to read the setting from the config file, then calls
+:py:func:`ckan.plugins.toolkit.asbool` to convert the value from a string
+(all config settings values are strings, when read from the file) to a boolean.
+
+.. note::
+
+ There are also :py:func:`~ckan.plugins.toolkit.asint` and
+ :py:func:`~ckan.plugins.toolkit.aslist` functions in the plugins toolkit.
+
+With this plugin enabled, you should find that users can create new groups if
+you have ``ckan.iauthfunctions.users_can_create_groups = True`` in the
+``[app:main]`` section of your CKAN config file. Otherwise, only sysadmin users
+will be allowed to create groups.
+
+.. note::
+
+ Names of config settings provided by extensions should include the name
+ of the extension, to avoid conflicting with core config settings or with
+ config settings from other extensions.
+ See :ref:`extension config setting names best practice`.
+
+.. note::
+
+ The users still need to be logged-in to create groups.
+ In general creating, updating or deleting content in CKAN requires the user
+ to be logged-in to a registered user account, no matter what the relevant
+ authorization function says.
View
@@ -26,6 +26,7 @@ extensions.
:maxdepth: 2
tutorial
+ custom-config-settings
testing-extensions
best-practices
plugin-interfaces
@@ -439,7 +439,7 @@ crashing, we'll have to handle the exception that CKAN's
list the members of a group that doesn't exist. Replace the ``member_list``
line in your ``plugin.py`` file with these lines:
-.. literalinclude:: ../../ckanext/example_iauthfunctions/plugin.py
+.. literalinclude:: ../../ckanext/example_iauthfunctions/plugin_v4.py
:start-after: # Get a list of the members of the 'curators' group.
:end-before: # 'members' is a list of (user_id, object_type, capacity) tuples, we're
@@ -466,7 +466,7 @@ We need to handle that exception as well, replace the
``convert_user_name_or_id_to_id`` line in your ``plugin.py`` file with these
lines:
-.. literalinclude:: ../../ckanext/example_iauthfunctions/plugin.py
+.. literalinclude:: ../../ckanext/example_iauthfunctions/plugin_v4.py
:start-after: # We have the logged-in user's user name, get their user id.
:end-before: # Finally, we can test whether the user is a member of the curators group.
@@ -476,7 +476,7 @@ We're done!
Here's our final, working ``plugin.py`` module in full:
-.. literalinclude:: ../../ckanext/example_iauthfunctions/plugin.py
+.. literalinclude:: ../../ckanext/example_iauthfunctions/plugin_v4.py
In working through this tutorial, you've covered all the key concepts needed
for writing CKAN extensions, including:
@@ -521,8 +521,3 @@ If you get a ``TypeError`` like this one::
it means that one of your plugin methods has the wrong number of parameters.
A plugin has to implement each method in a plugin interface with the same
parameters as in the interface.
-
-----
-
-.. todo:: Add a section about how to use custom config settings.
- See :ref:`accessing custom config settings from templates`.
View
@@ -706,6 +706,11 @@ custom config setting, this setting will not be available. If you need to
access a custom config setting from a template, you can do so by wrapping the
config setting in a helper function.
+.. seealso::
+
+ For more on custom config settings, see
+ :doc:`/extensions/custom-config-settings`.
+
.. todo::
I'm not sure if making config settings available to templates like this is
@@ -718,18 +723,10 @@ the most popular groups on the front page. First, add a new helper function to
.. literalinclude:: /../ckanext/example_theme/custom_config_setting/plugin.py
:language: python
-The helper function uses :py:obj:`pylons.config` (imported at the top of the
-file) to access the value from the CKAN config file, and calls
-:py:func:`ckan.plugins.toolkit.asbool` to convert the value from a string to
-``True`` or ``False``:
-
.. literalinclude:: /../ckanext/example_theme/custom_config_setting/plugin.py
:language: python
:pyobject: show_most_popular_groups
-There are also :py:func:`ckan.plugins.toolkit.asint` and
-:py:func:`ckan.plugins.toolkit.aslist` functions in the plugins toolkit.
-
.. note::
Names of config settings provided by extensions should include the name
View
@@ -78,7 +78,8 @@
'example_iauthfunctions_v1 = ckanext.example_iauthfunctions.plugin_v1:ExampleIAuthFunctionsPlugin',
'example_iauthfunctions_v2 = ckanext.example_iauthfunctions.plugin_v2:ExampleIAuthFunctionsPlugin',
'example_iauthfunctions_v3 = ckanext.example_iauthfunctions.plugin_v3:ExampleIAuthFunctionsPlugin',
- 'example_iauthfunctions = ckanext.example_iauthfunctions.plugin:ExampleIAuthFunctionsPlugin',
+ 'example_iauthfunctions_v4 = ckanext.example_iauthfunctions.plugin_v4:ExampleIAuthFunctionsPlugin',
+ 'example_iauthfunctions_v5_custom_config_setting = ckanext.example_iauthfunctions.plugin_v5_custom_config_setting:ExampleIAuthFunctionsPlugin',
'example_theme_v01_empty_extension = ckanext.example_theme.v01_empty_extension.plugin:ExampleThemePlugin',
'example_theme_v02_empty_template = ckanext.example_theme.v02_empty_template.plugin:ExampleThemePlugin',
'example_theme_v03_jinja = ckanext.example_theme.v03_jinja.plugin:ExampleThemePlugin',

0 comments on commit 86d90ee

Please sign in to comment.