Modified template to include the original content from the content div
--- a/README.rst
+++ b/README.rst
@@ -12,6 +12,8 @@
* A custom Pylons controller for overriding some core CKAN behaviour
* A custom Package edit form
+
+* A custom Group edit form
* A plugin that allows for custom forms to be used for datasets based on
their "type".
--- a/ckanext/example/forms.py
+++ b/ckanext/example/forms.py
@@ -7,6 +7,7 @@
from ckan.logic import NotFound, NotAuthorized, ValidationError
from ckan.logic import tuplize_dict, clean_dict, parse_params
import ckan.logic.schema as default_schema
+from ckan.logic.schema import group_form_schema
from ckan.logic.schema import package_form_schema
import ckan.logic.validators as val
from ckan.lib.base import BaseController, render, c, model, abort, request
@@ -16,7 +17,7 @@
from ckan.lib.navl.dictization_functions import Invalid
from ckan.lib.navl.dictization_functions import validate, missing
from ckan.lib.navl.dictization_functions import DataError, flatten_dict, unflatten
-from ckan.plugins import IDatasetForm, IConfigurer
+from ckan.plugins import IDatasetForm, IGroupForm, IConfigurer
from ckan.plugins import implements, SingletonPlugin
from ckan.lib.navl.validators import (ignore_missing,
@@ -28,7 +29,94 @@
log = logging.getLogger(__name__)
-
+class ExampleGroupForm(SingletonPlugin):
+ """This plugin demonstrates how a class packaged as a CKAN
+ extension might extend CKAN behaviour by providing custom forms
+ based on the type of a Group.
+
+ In this case, we implement two extension interfaces to provide custom
+ forms for specific types of group.
+
+ - ``IConfigurer`` allows us to override configuration normally
+ found in the ``ini``-file. Here we use it to specify where the
+ form templates can be found.
+
+ - ``IGroupForm`` allows us to provide a custom form for a dataset
+ based on the 'type' that may be set for a group. Where the
+ 'type' matches one of the values in group_types then this
+ class will be used.
+ """
+ implements(IGroupForm, inherit=True)
+ implements(IConfigurer, inherit=True)
+
+ def update_config(self, config):
+ """
+ This IConfigurer implementation causes CKAN to look in the
+ ```templates``` directory when looking for the group_form()
+ """
+ here = os.path.dirname(__file__)
+ rootdir = os.path.dirname(os.path.dirname(here))
+ template_dir = os.path.join(rootdir, 'ckanext',
+ 'example', 'theme', 'templates')
+ config['extra_template_paths'] = ','.join([template_dir,
+ config.get('extra_template_paths', '')])
+
+ def group_form(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered. e.g. "forms/group_form.html".
+ """
+ return 'forms/group_form.html'
+
+ def group_types(self):
+ """
+ Returns an iterable of group type strings.
+
+ If a request involving a group of one of those types is made, then
+ this plugin instance will be delegated to.
+
+ There must only be one plugin registered to each group type. Any
+ attempts to register more than one plugin instance to a given group
+ type will raise an exception at startup.
+ """
+ return ["testgroup"]
+
+ def is_fallback(self):
+ """
+ Returns true iff this provides the fallback behaviour, when no other
+ plugin instance matches a group's type.
+
+ As this is not the fallback controller we should return False. If
+ we were wanting to act as the fallback, we'd return True
+ """
+ return False
+
+ def form_to_db_schema(self):
+ """
+ Returns the schema for mapping group data from a form to a format
+ suitable for the database.
+ """
+ return group_form_schema()
+
+ def db_to_form_schema(self):
+ """
+ Returns the schema for mapping group data from the database into a
+ format suitable for the form (optional)
+ """
+ return {}
+
+ def check_data_dict(self, data_dict):
+ """
+ Check if the return data is correct.
+
+ raise a DataError if not.
+ """
+
+ def setup_template_variables(self, context, data_dict):
+ """
+ Add variables to c just prior to the template being rendered.
+ """
+
class ExampleDatasetForm(SingletonPlugin):
"""This plugin demonstrates how a theme packaged as a CKAN
@@ -64,7 +152,7 @@
Returns a string representing the location of the template to be
rendered. e.g. "package/new_package_form.html".
"""
- return 'controller/package_plugin.html'
+ return 'forms/dataset_form.html'
def is_fallback(self):
"""
@@ -87,7 +175,7 @@
attempts to register more than one plugin instance to a given package
type will raise an exception at startup.
"""
- return ["example"]
+ return ["example_dataset_form"]
def setup_template_variables(self, context, data_dict=None):
"""
@@ -113,7 +201,6 @@
schema = {
'title': [not_empty, unicode],
'name': [not_empty, unicode, val.name_validator, val.package_name_validator],
- 'notes': [not_empty, unicode],
'date_released': [date_to_db, convert_to_extras],
'date_updated': [date_to_db, convert_to_extras],
--- a/ckanext/example/plugin.py
+++ b/ckanext/example/plugin.py
@@ -21,9 +21,11 @@
found in the ``ini``-file. Here we use it to specify the site
title, and to tell CKAN to look in this package for templates
and resources that customise the core look and feel.
+
- ``IGenshiStreamFilter`` allows us to filter and transform the
HTML stream just before it is rendered. In this case we use
it to rename "frob" to "foobar"
+
- ``IRoutes`` allows us to add new URLs, or override existing
URLs. In this example we use it to override the default
``/register`` behaviour with a custom controller
--- a/ckanext/example/theme/templates/forms/dataset_form.html
+++ b/ckanext/example/theme/templates/forms/dataset_form.html
@@ -12,7 +12,7 @@
</ul>
</div>
-<fieldset>
+<fieldset id='section-basic-information'>
<legend>Basic information</legend>
<dl>
<dt><label class="field_req" for="title">Title *</label></dt>
@@ -37,7 +37,7 @@
</dl>
</fieldset>
-<fieldset>
+<fieldset id='section-further-information'>
<legend>Details</legend>
<dl>
<dt><label class="field_opt" for="date_released">Date released</label></dt>
@@ -95,7 +95,7 @@
<dd class="field_error" py:if="errors.get('taxonomy_url', '')">${errors.get('taxonomy_url', '')}</dd>
</dl>
</fieldset>
-<fieldset>
+<fieldset id='section-resources'>
<legend>Resources</legend>
<table class="flexitable">
<thead>
@@ -125,7 +125,7 @@
<div class="field_error" py:if="errors.get('resources', '')">Package resource(s) incomplete.</div>
</fieldset>
-<fieldset>
+<fieldset id='section-more-details'>
<legend>More details</legend>
<dl>
<dt><label class="field_req" for="published_by">Published by *</label></dt>
--- /dev/null
+++ b/ckanext/example/theme/templates/forms/group_form.html
@@ -1,1 +1,102 @@
+<form id="group-edit" action="" method="post"
+ py:attrs="{'class':'has-errors'} if errors else {}"
+ xmlns:i18n="http://genshi.edgewall.org/i18n"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+<div class="error-explanation" py:if="error_summary">
+<h2>Errors in form</h2>
+<p>The form contains invalid entries:</p>
+<ul>
+ <li py:for="key, error in error_summary.items()">${"%s: %s" % (key, error)}</li>
+</ul>
+</div>
+
+<fieldset id="basic-information">
+ <dl>
+ <dt><label class="field_opt" for="name">The Title</label></dt>
+ <dd><input class="js-title" id="title" name="title" type="text" value="${data.get('title', '')}"/></dd>
+
+ <dt><label class="field_opt" for="title">Url</label></dt>
+ <dd class="name-field">
+ <span class="js-url-text url-text">${g.site_url+h.url_for(controller='group', action='index')+'/'}<span class="js-url-viewmode js-url-suffix"> </span><a style="display: none;" href="#" class="url-edit js-url-editlink js-url-viewmode">(edit)</a></span>
+ <input style="display: none;" id="name" maxlength="100" name="name" type="text" class="url-input js-url-editmode js-url-input" value="${data.get('name', '')}" />
+ <p class="js-url-is-valid"> </p>
+ </dd>
+ <dd style="display: none;" class="js-url-editmode instructions basic">2+ chars, lowercase, using only 'a-z0-9' and '-_'</dd>
+ <dd class="field_error" py:if="errors.get('name', '')">${errors.get('name', '')}</dd>
+
+ <dt class="description-label"><label class="field_opt" for="title">Description</label></dt>
+ <dd class="description-field"><div class="markdown-editor">
+ <ul class="button-row">
+ <li><button class="pretty-button js-markdown-edit depressed">Edit</button></li>
+ <li><button class="pretty-button js-markdown-preview">Preview</button></li>
+ </ul>
+ <textarea class="markdown-input" name="description" id="notes" placeholder="${_('Start with a summary sentence ...')}">${data.get('description','')}</textarea>
+ <div class="markdown-preview" style="display: none;"></div>
+ <span class="hints">You can use <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank">Markdown formatting</a> here.</span>
+ </div></dd>
+
+
+ <dt class="state-label" py:if="c.is_sysadmin or c.auth_for_change_state"><label class="field_opt" for="state">State</label></dt>
+ <dd class="state-field" py:if="c.is_sysadmin or c.auth_for_change_state">
+ <select id="state" name="state" >
+ <option py:attrs="{'selected': 'selected' if data.get('state') == 'active' else None}" value="active">active</option>
+ <option py:attrs="{'selected': 'selected' if data.get('state') == 'deleted' else None}" value="deleted">deleted</option>
+ </select>
+ </dd>
+ </dl>
+</fieldset>
+
+<fieldset id="extras">
+ <h3>Extras</h3>
+ <dl>
+ <py:with vars="extras = data.get('extras', [])">
+ <py:for each="num, extra in enumerate(data.get('extras', []))">
+ <dt><label for="extras__${num}__value">${extra.get('key')}</label></dt>
+ <dd>
+ <input id="extras__${num}__key" name="extras__${num}__key" type="hidden" value="${extra.get('key')}" />
+ <input id="extras__${num}__value" name="extras__${num}__value" type="text" value="${extra.get('value')}" />
+ <input type="checkbox" name="extras__${num}__deleted" checked="${extra.get('deleted')}">Delete</input>
+ </dd>
+ </py:for>
+
+ <py:for each="num in range(len(extras), len(extras) + 4)">
+ <dt><label for="extras__${num}__key">New key</label></dt>
+ <dd>
+ <input class="medium-width" id="extras__${num}__key" name="extras__${num}__key" type="text" />
+ with value
+ <input class="medium-width" id="extras__${num}__value" name="extras__${num}__value" type="text" />
+ </dd>
+ </py:for>
+ </py:with>
+ </dl>
+</fieldset>
+
+<fieldset id="datasets">
+ <h3>Datasets</h3>
+ <dl py:if="data.get('packages')">
+ <py:for each="num, package in enumerate(data.get('packages'))">
+ <dt><input checked="checked" id="datasets__${num}__name" name="packages__${num}__name" type="checkbox" value="${package['name']}"/></dt>
+ <dd>
+ <label for="packages__${num}__name">${package['name']}</label>
+ </dd>
+ </py:for>
+ </dl>
+ <p py:if="not data.get('packages')">There are no datasets currently in this group.</p>
+
+ <h3>Add datasets</h3>
+ <dl>
+ <dt><label class="field_opt" for="packages__${len(data.get('packages', []))}__name">Dataset</label></dt>
+ <dd><input class="autocomplete-dataset" id="datasets__${len(data.get('packages', []))}__name" name="packages__${len(data.get('packages', []))}__name" type="text" /></dd>
+ </dl>
+</fieldset>
+
+<div class="form-submit">
+ <input id="save" class="pretty-button primary" name="save" type="submit" value="${_('Save Changes')}" />
+ <py:if test="c.group">
+ <input id="cancel" class="pretty-button href-action" name="cancel" type="reset" value="${_('Cancel')}" action="${h.url_for(controller='group', action='read', id=c.group.name)}" />
+ </py:if>
+</div>
+</form>
+
--- a/ckanext/example/theme/templates/home/index.html
+++ b/ckanext/example/theme/templates/home/index.html
@@ -22,6 +22,7 @@
<p id="examplething">
Here is the frob
</p>
+ ${select("*")}
</div>
<xi:include href="layout.html" />
--- a/ckanext/example/theme/templates/layout.html
+++ b/ckanext/example/theme/templates/layout.html
@@ -9,9 +9,10 @@
<!-- ! a custom primary nav -->
<py:match path="//div[@class='menu']">
<div class="menu">
- ${h.nav_link(c, _('Home'), controller='home', action='index', id=None)}
- ${h.nav_link(c, _('Data'), controller='package', action='index', id=None)}
- ${h.nav_link(c, _('New dataset'), controller='package', action='new', id=None)}
+ ${h.nav_link(c, _('Home'), controller='home', action='index')}
+ ${h.nav_link(c, _('Data'), controller='package', action='index')}
+ ${h.nav_link(c, _('New dataset'), controller='package', action='new')}
+ ${h.nav_link(c, _('New group'), controller='group', action='new')}
</div>
</py:match>
--- a/setup.py
+++ b/setup.py
@@ -27,6 +27,7 @@
[ckan.plugins]
example=ckanext.example.plugin:ExamplePlugin
example_datasetform=ckanext.example.forms:ExampleDatasetForm
+ example_groupform=ckanext.example.forms:ExampleGroupForm
[ckan.forms]
example_form = ckanext.example.package_form:get_example_fieldset