Skip to content

Commit

Permalink
[#2750] Add example_idatasetform example extension
Browse files Browse the repository at this point in the history
  • Loading branch information
Sean Hammond committed Feb 25, 2013
1 parent deea895 commit 4331816
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 0 deletions.
Empty file.
145 changes: 145 additions & 0 deletions 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)
@@ -0,0 +1,6 @@
{% ckan_extends %}

<!-- Pass c.country_codes to package_metadata_form.html.
FIXME: If ckan passed c to package_metadata_form.html anyway, we wouldn't need
to override this block here. -->
{% 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 %}
13 changes: 13 additions & 0 deletions ckanext/example_idatasetform/templates/package/read.html
@@ -0,0 +1,13 @@
{% ckan_extends %}

{% block package_description %}
{{ super() }}

<!-- Add our custom country_code field to the dataset read page. -->
{% if pkg.get('country_code') %}
<section id="dataset-country_code" class="resources module-content">
<p><strong>Country Code</strong>: {{ pkg.country_code[0] }}</p>
</section>
{% endif %}

{% endblock %}
@@ -0,0 +1,8 @@
{% ckan_extends %}

<!-- Pass c.country_codes to package_metadata_fields.html.
FIXME: If ckan passed c to package_metadata_fields.html anyway, we wouldn't
need to override this block here. -->
{% block metadata_fields %}
{% snippet 'package/snippets/package_metadata_fields.html', data=data, errors=errors, country_codes=c.country_codes %}
{% endblock %}
@@ -0,0 +1,40 @@
{% import 'macros/form.html' as form %}

{% set groups_available = h.groups_available() %}
{% if groups_available %}
<div class="control-group">
{% set groups = h.dict_list_reduce(data.groups, 'id') %}
<label for="field-groups" class="control-label">{{ _('Add to Groups') }}</label>
<div class="controls">
<select id="field-groups" name="groups__{{ groups | count }}__id" data-module="autocomplete">
<option value="">{{ _('Select a group...') }}</option>
{% for group in groups_available %}
<option value="{{ group.id }}" {% if group.id in groups %}selected="selected"{% endif %}>{{ group.name }}</option>
{% endfor %}
</select>
</div>
</div>
{% 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']) }}

<label class="control-label" for="field-country_code">{{ _("Country Code") }}</label>
<div class="controls">
<select id="field-country_code" name="country_code" data-module="autocomplete">
{% for country_code in country_codes %}
<option value="{{ country_code }}" {% if country_code in data.get('country_code', []) %}selected="selected"{% endif %}>{{ country_code }}</option>
{% endfor %}
</select>
</div>

{#
{% block custom_fields %}
{% snippet 'snippets/custom_form_fields.html', extras=data.extras, errors=errors, limit=3 %}
{% endblock %}
#}
@@ -0,0 +1,21 @@
{% import "macros/form.html" as form %}

{% set data = data or {} %}
{% set errors = errors or {} %}

<form class="dataset-form dataset-resource-form form-horizontal" method="post" data-module="basic-form">
{{ h.snippet('package/snippets/stages.html', stages=['complete', 'complete', 'active'], pkg_name=pkg_name) }}
{{ form.errors(error_summary) }}

<!-- Pass country_codes to package_metadata_fields.html.
FIXME: If CKAN would pass c to package_metadata_fields.html anyway, we
wouldn't need to override this template here. -->
{% snippet 'package/snippets/package_metadata_fields.html', data=data, errors=errors, groups_available=groups_available, country_codes=country_codes %}

<div class="form-actions">
{# TODO: Go back to previous resource form #}
<button class="btn" name="save" value="go-resources" type="submit">{{ _('Previous') }}</button>
<button class="btn btn-primary" name="save" value="finish" type="submit">{{ _('Finish') }}</button>
</div>

</form>
129 changes: 129 additions & 0 deletions 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 '<select id="field-country_code" name="country_code"' in (
response)
assert '<option value="de"' in response
assert '<option value="es"' in response
assert '<option value="fr"' in response
assert '<option value="ie"' in response
assert '<option value="uk"' in response

# Fill out the new dataset stage 3 form and submit it.
form = response.forms[1]
form['country_code'] = 'uk'
response = form.submit('save', 3, 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')

# Check that the custom Country Code field appears with the correct
# value on the dataset read page.
assert '<p><strong>Country Code</strong>: uk</p>' 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 '<select id="field-country_code" name="country_code"' in (
response)
# Check that the right value is selected by default.
assert '<option value="uk" selected="selected">uk</option>' 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 '<p><strong>Country Code</strong>: fr</p>' 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 '<a href="/tag/idatasetform">idatasetform</a>' in response
assert '<a href="/tag/testing">testing</a>' in response
assert '<a href="/tag/test_update_tag">test_update_tag</a>' 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?)
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -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
Expand Down

0 comments on commit 4331816

Please sign in to comment.