Skip to content

Commit

Permalink
[#847] Add docs for extensions custom config settings
Browse files Browse the repository at this point in the history
And example with tests
  • Loading branch information
Sean Hammond committed Dec 13, 2013
1 parent dcf11d9 commit 86d90ee
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 28 deletions.
File renamed without changes.
30 changes: 30 additions & 0 deletions ckanext/example_iauthfunctions/plugin_v5_custom_config_setting.py
@@ -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}

103 changes: 92 additions & 11 deletions ckanext/example_iauthfunctions/tests/test_example_iauthfunctions.py
Expand Up @@ -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.
'''
Expand All @@ -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.'''
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
42 changes: 42 additions & 0 deletions doc/extensions/custom-config-settings.rst
@@ -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.
1 change: 1 addition & 0 deletions doc/extensions/index.rst
Expand Up @@ -26,6 +26,7 @@ extensions.
:maxdepth: 2

tutorial
custom-config-settings
testing-extensions
best-practices
plugin-interfaces
Expand Down
11 changes: 3 additions & 8 deletions doc/extensions/tutorial.rst
Expand Up @@ -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

Expand All @@ -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.

Expand All @@ -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:
Expand Down Expand Up @@ -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`.
13 changes: 5 additions & 8 deletions doc/theming/templates.rst
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -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',
Expand Down

0 comments on commit 86d90ee

Please sign in to comment.