From 4331816b09dc92cb7c0dfb6aa7b95d49ea89d3da Mon Sep 17 00:00:00 2001
From: Sean Hammond
Date: Mon, 25 Feb 2013 20:18:08 +0100
Subject: [PATCH] [#2750] Add example_idatasetform example extension
---
ckanext/example_idatasetform/__init__.py | 0
ckanext/example_idatasetform/plugin.py | 145 ++++++++++++++++++
.../package/new_package_metadata.html | 6 +
.../templates/package/read.html | 13 ++
.../package/snippets/package_form.html | 8 +
.../snippets/package_metadata_fields.html | 40 +++++
.../snippets/package_metadata_form.html | 21 +++
.../tests/test_example_idatasetform_plugin.py | 129 ++++++++++++++++
setup.py | 1 +
9 files changed, 363 insertions(+)
create mode 100644 ckanext/example_idatasetform/__init__.py
create mode 100644 ckanext/example_idatasetform/plugin.py
create mode 100644 ckanext/example_idatasetform/templates/package/new_package_metadata.html
create mode 100644 ckanext/example_idatasetform/templates/package/read.html
create mode 100644 ckanext/example_idatasetform/templates/package/snippets/package_form.html
create mode 100644 ckanext/example_idatasetform/templates/package/snippets/package_metadata_fields.html
create mode 100644 ckanext/example_idatasetform/templates/package/snippets/package_metadata_form.html
create mode 100644 ckanext/example_idatasetform/tests/test_example_idatasetform_plugin.py
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') %}
+
{{ _('Add to Groups') }}
+
+
+ {{ _('Select a group...') }}
+ {% for group in groups_available %}
+ {{ group.name }}
+ {% endfor %}
+
+
+
+{% 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']) }}
+
+{{ _("Country Code") }}
+
+
+ {% for country_code in country_codes %}
+ {{ country_code }}
+ {% endfor %}
+
+
+
+{#
+{% 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 {} %}
+
+
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 'Country Code : uk
' in response
+
+ # Get the edit dataset page for the dataset we just created.
+ offset = routes.url_for(controller='package', action='edit',
+ id='idatasetform_test_dataset')
+ response = self.app.get(offset, extra_environ=extra_environ)
+
+ # Check that the custom country_code field is on the 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