Renaming registered types names
Renaming registered types names

file:a/README.rst -> file:b/README.rst
--- 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 ["example_group_form"]
+
+    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
@@ -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/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">&nbsp;</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">&nbsp;</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/layout.html
+++ b/ckanext/example/theme/templates/layout.html
@@ -12,6 +12,7 @@
         ${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, _('New group'), controller='group', action='new', id=None)}
     </div>
   </py:match>
 

file:a/setup.py -> file:b/setup.py
--- 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