diff --git a/ckanext/example_idatasetform/__init__.py b/ckanext/example_idatasetform/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ckanext/example_idatasetform/plugin.py b/ckanext/example_idatasetform/plugin.py new file mode 100644 index 00000000000..6426658f592 --- /dev/null +++ b/ckanext/example_idatasetform/plugin.py @@ -0,0 +1,145 @@ +import logging + +import ckan.plugins as plugins +import ckan.plugins.toolkit as toolkit +import ckan.lib.plugins as lib_plugins +import ckan.lib.navl.validators as validators +import ckan.logic as logic +import ckan.logic.converters as converters + + +class ExampleIDatasetFormPlugin(plugins.SingletonPlugin, + lib_plugins.DefaultDatasetForm): + '''An example IDatasetForm CKAN plugin. + + Uses a tag vocabulary to add a custom metadata field to datasets. + + ''' + plugins.implements(plugins.IConfigurer, inherit=False) + plugins.implements(plugins.IDatasetForm, inherit=False) + + # These record how many times methods that this plugin's methods are + # called, for testing purposes. + num_times_check_data_dict_called = 0 + num_times_new_template_called = 0 + num_times_comments_template_called = 0 + num_times_search_template_called = 0 + num_times_read_template_called = 0 + num_times_history_template_called = 0 + num_times_package_form_called = 0 + + def create_country_codes(self): + '''Create country_codes vocab and tags, if they don't exist already. + + Note that you could also create the vocab and tags using CKAN's API, + and once they are created you can edit them (e.g. to add and remove + possible dataset country code values) using the API. + + ''' + user = logic.get_action('get_site_user')({'ignore_auth': True}, {}) + context = {'user': user['name']} + try: + data = {'id': 'country_codes'} + logic.get_action('vocabulary_show')(context, data) + logging.info("Example genre vocabulary already exists, skipping.") + except logic.NotFound: + logging.info("Creating vocab 'country_codes'") + data = {'name': 'country_codes'} + vocab = logic.get_action('vocabulary_create')(context, data) + for tag in (u'uk', u'ie', u'de', u'fr', u'es'): + logging.info( + "Adding tag {0} to vocab 'country_codes'".format(tag)) + data = {'name': tag, 'vocabulary_id': vocab['id']} + logic.get_action('tag_create')(context, data) + + def update_config(self, config): + # Add this plugin's templates dir to CKAN's extra_template_paths, so + # that CKAN will use this plugin's custom templates. + toolkit.add_template_directory(config, 'templates') + + def is_fallback(self): + # Return True to register this plugin as the default handler for + # package types not handled by any other IDatasetForm plugin. + return True + + def package_types(self): + # This plugin doesn't handle any special package types, it just + # registers itself as the default (above). + return [] + + def form_to_db_schema(self): + schema = super(ExampleIDatasetFormPlugin, self).form_to_db_schema() + + # Add our custom country_code metadata field to the schema. + schema.update({ + 'country_code': [validators.ignore_missing, + converters.convert_to_tags('country_codes')] + }) + + return schema + + def db_to_form_schema(self): + schema = super(ExampleIDatasetFormPlugin, self).db_to_form_schema() + + # Don't show vocab tags mixed in with normal 'free' tags + # (e.g. on dataset pages, or on the search page) + schema['tags']['__extras'].append(logic.converters.free_tags_only) + + # Add our custom country_code metadata field to the schema. + schema.update({ + 'country_code': [ + converters.convert_from_tags('country_codes'), + validators.ignore_missing] + }) + + return schema + + def setup_template_variables(self, context, data_dict=None): + super(ExampleIDatasetFormPlugin, self).setup_template_variables( + context, data_dict) + + # Create the country_codes vocab and tags, if they don't already exist. + self.create_country_codes() + + # Add the list of available country codes, from the country_codes + # vocab, to the template context. + try: + toolkit.c.country_codes = logic.get_action('tag_list')( + context, {'vocabulary_id': 'country_codes'}) + except logic.NotFound: + toolkit.c.country_codes = None + + # These methods just record how many times they're called, for testing + # purposes. + # TODO: It might be better to test that custom templates returned by + # these methods are actually used, not just that the methods get + # called. + + def new_template(self): + ExampleIDatasetFormPlugin.num_times_new_template_called += 1 + return lib_plugins.DefaultDatasetForm.new_template(self) + + def comments_template(self): + ExampleIDatasetFormPlugin.num_times_comments_template_called += 1 + return lib_plugins.DefaultDatasetForm.comments_template(self) + + def search_template(self): + ExampleIDatasetFormPlugin.num_times_search_template_called += 1 + return lib_plugins.DefaultDatasetForm.search_template(self) + + def read_template(self): + ExampleIDatasetFormPlugin.num_times_read_template_called += 1 + return lib_plugins.DefaultDatasetForm.read_template(self) + + def history_template(self): + ExampleIDatasetFormPlugin.num_times_history_template_called += 1 + return lib_plugins.DefaultDatasetForm.history_template(self) + + def package_form(self): + ExampleIDatasetFormPlugin.num_times_package_form_called += 1 + return lib_plugins.DefaultDatasetForm.package_form(self) + + def check_data_dict(self, data_dict, schema=None): + ExampleIDatasetFormPlugin.num_times_check_data_dict_called += 1 + return lib_plugins.DefaultDatasetForm.check_data_dict(self, data_dict, + schema) diff --git a/ckanext/example_idatasetform/templates/package/new_package_metadata.html b/ckanext/example_idatasetform/templates/package/new_package_metadata.html new file mode 100644 index 00000000000..dbe6af6871b --- /dev/null +++ b/ckanext/example_idatasetform/templates/package/new_package_metadata.html @@ -0,0 +1,6 @@ +{% ckan_extends %} + + +{% block form %}{{ h.snippet('package/snippets/package_metadata_form.html', data=data, errors=errors, include_metadata=false, pkg_name=pkg_name, country_codes=c.country_codes) }}{% endblock %} diff --git a/ckanext/example_idatasetform/templates/package/read.html b/ckanext/example_idatasetform/templates/package/read.html new file mode 100644 index 00000000000..19db768c789 --- /dev/null +++ b/ckanext/example_idatasetform/templates/package/read.html @@ -0,0 +1,13 @@ +{% ckan_extends %} + +{% block package_description %} + {{ super() }} + + + {% if pkg.get('country_code') %} +
+

Country Code: {{ pkg.country_code[0] }}

+
+ {% endif %} + +{% endblock %} diff --git a/ckanext/example_idatasetform/templates/package/snippets/package_form.html b/ckanext/example_idatasetform/templates/package/snippets/package_form.html new file mode 100644 index 00000000000..573f1333cbc --- /dev/null +++ b/ckanext/example_idatasetform/templates/package/snippets/package_form.html @@ -0,0 +1,8 @@ +{% ckan_extends %} + + +{% block metadata_fields %} + {% snippet 'package/snippets/package_metadata_fields.html', data=data, errors=errors, country_codes=c.country_codes %} +{% endblock %} diff --git a/ckanext/example_idatasetform/templates/package/snippets/package_metadata_fields.html b/ckanext/example_idatasetform/templates/package/snippets/package_metadata_fields.html new file mode 100644 index 00000000000..7d90b2489d9 --- /dev/null +++ b/ckanext/example_idatasetform/templates/package/snippets/package_metadata_fields.html @@ -0,0 +1,40 @@ +{% import 'macros/form.html' as form %} + +{% set groups_available = h.groups_available() %} +{% if groups_available %} +
+ {% set groups = h.dict_list_reduce(data.groups, 'id') %} + +
+ +
+
+{% endif %} + +{{ form.input('author', label=_('Author'), id='field-author', placeholder=_('Joe Bloggs'), value=data.author, error=errors.author, classes=['control-medium']) }} + +{{ form.input('author_email', label=_('Author Email'), id='field-author-email', placeholder=_('joe@example.com'), value=data.author_email, error=errors.author_email, classes=['control-medium']) }} + +{{ form.input('maintainer', label=_('Maintainer'), id='field-maintainer', placeholder=_('Joe Bloggs'), value=data.maintainer, error=errors.maintainer, classes=['control-medium']) }} + +{{ form.input('maintainer_email', label=_('Maintainer Email'), id='field-maintainer-email', placeholder=_('joe@example.com'), value=data.maintainer_email, error=errors.maintainer_email, classes=['control-medium']) }} + + +
+ +
+ +{# +{% block custom_fields %} + {% snippet 'snippets/custom_form_fields.html', extras=data.extras, errors=errors, limit=3 %} +{% endblock %} +#} diff --git a/ckanext/example_idatasetform/templates/package/snippets/package_metadata_form.html b/ckanext/example_idatasetform/templates/package/snippets/package_metadata_form.html new file mode 100644 index 00000000000..da95a2347bb --- /dev/null +++ b/ckanext/example_idatasetform/templates/package/snippets/package_metadata_form.html @@ -0,0 +1,21 @@ +{% import "macros/form.html" as form %} + +{% set data = data or {} %} +{% set errors = errors or {} %} + +
+ {{ h.snippet('package/snippets/stages.html', stages=['complete', 'complete', 'active'], pkg_name=pkg_name) }} + {{ form.errors(error_summary) }} + + + {% snippet 'package/snippets/package_metadata_fields.html', data=data, errors=errors, groups_available=groups_available, country_codes=country_codes %} + +
+ {# TODO: Go back to previous resource form #} + + +
+ +
diff --git a/ckanext/example_idatasetform/tests/test_example_idatasetform_plugin.py b/ckanext/example_idatasetform/tests/test_example_idatasetform_plugin.py new file mode 100644 index 00000000000..2d158a3490b --- /dev/null +++ b/ckanext/example_idatasetform/tests/test_example_idatasetform_plugin.py @@ -0,0 +1,129 @@ +import ckan +import ckan.lib.create_test_data +import paste.fixture +import pylons.test +import routes + + +class TestExampleIDatasetFormPlugin: + + @classmethod + def setup(cls): + cls.app = paste.fixture.TestApp(pylons.test.pylonsapp) + ckan.plugins.load('example_idatasetform') + ckan.lib.create_test_data.CreateTestData.create() + + @classmethod + def teardown(cls): + ckan.model.repo.rebuild_db() + + def test_example_idatasetform_plugin(self): + + # Get the new dataset stage 1 page. + offset = routes.url_for(controller='package', action='new') + extra_environ = {'REMOTE_USER': 'tester'} + response = self.app.get(offset, extra_environ=extra_environ) + + # Fill out the new dataset stage 1 form and submit it. + form = response.forms[1] + form['name'] = 'idatasetform_test_dataset' + form['title'] = 'IDatasetForm Test Dataset' + # Submit the form and get a redirected to the stage 2 form. + response = form.submit('save', extra_environ=extra_environ) + assert response.status == 302 + response = response.follow(extra_environ=extra_environ) + assert response.status == 200 + + # Fill out the new dataset stage 2 form and submit it. + form = response.forms[1] + form['name'] = 'idatasetform_test_resource' + form['resource_type'] = 'api' + form['url'] = 'www.example.com' + response = form.submit('save', 3, extra_environ=extra_environ) + assert response.status == 302 + response = response.follow(extra_environ=extra_environ) + assert response.status == 200 + + # Check that the custom Country Code field and its possible values + # are on the new dataset stage 3 page. + assert 'uk' in ( + response) + + # Fill out the form and submit it, changing the country_code value. + form = response.forms[1] + #form['tag_string'] = 'testing, idatasetform, test_update_tag' + form['country_code'] = 'fr' + response = form.submit('save', extra_environ=extra_environ) + assert response.status == 302 + response = response.follow(extra_environ=extra_environ) + assert response.status == 200 + assert response.request.url.endswith( + '/dataset/idatasetform_test_dataset') + + # Test the contents of the updated dataset read page. + assert '

Country Code: fr

' in response + + # FIXME: Tags aren't shown on the dataset read page, so check them + # another way. + # TODO: Also edit some other fields and check their values. + assert 'idatasetform' in response + assert 'testing' in response + assert 'test_update_tag' in response + + # Fetch the dataset search page, just to test that the plugin's + # search_template() method gets called. + offset = routes.url_for(controller='package', action='search') + response = self.app.get(offset) + assert response.status == 200 + + # Fetch the dataset history page, just to test that the plugin's + # history_template() method gets called. + offset = routes.url_for(controller='package', action='history', + id='idatasetform_test_dataset') + response = self.app.get(offset) + assert response.status == 200 + + # TODO: It might be better to test that custom templates returned by + # these methods are actually used, not just that the methods get + # called. + import ckanext.example_idatasetform.plugin as plugin + assert plugin.ExampleIDatasetFormPlugin.num_times_package_form_called == 2 + assert plugin.ExampleIDatasetFormPlugin.num_times_read_template_called == 2 + #assert plugin.ExampleIDatasetFormPlugin.num_times_edit_template_called == 2 + assert plugin.ExampleIDatasetFormPlugin.num_times_new_template_called == 1 + #assert plugin.ExampleIDatasetFormPlugin.num_times_index_template_called == 1 + assert plugin.ExampleIDatasetFormPlugin.num_times_history_template_called == 1 + + # TODO: Test IDatasetForm's comments_template() method. + # (I think this requires the disqus plugin?) diff --git a/setup.py b/setup.py index fc9fda35371..f5cd700d095 100644 --- a/setup.py +++ b/setup.py @@ -127,6 +127,7 @@ pdf_preview=ckanext.pdfpreview.plugin:PdfPreview recline_preview=ckanext.reclinepreview.plugin:ReclinePreview example_itemplatehelpers=ckanext.example_itemplatehelpers.plugin:ExampleITemplateHelpersPlugin + example_idatasetform=ckanext.example_idatasetform.plugin:ExampleIDatasetFormPlugin [ckan.system_plugins] domain_object_mods = ckan.model.modification:DomainObjectModificationExtension