[refactor][m]: rename extension from exampletheme to example as per new repository name.
--- /dev/null
+++ b/README.rst
@@ -1,1 +1,57 @@
+This CKAN Extension demonstrates some common patterns for customising a CKAN instance.
+It comprises:
+
+ * A CKAN Extension "plugin" at ``ckanext/example/plugin.py``
+ which, when loaded, overrides various settings in the core
+ ``ini``-file to provide:
+
+ * A path to local customisations of the core templates and stylesheets
+ * A "stream filter" that replaces arbitrary strings in rendered templates
+ * A "route" to override and extend the default behaviour of a core CKAN page
+
+ * A custom Pylons controller for overriding some core CKAN behaviour
+
+ * A custom Package edit form
+
+ * Some simple template customisations
+
+Installation
+============
+
+To install this package, from your CKAN virtualenv, run the following from your CKAN base folder (e.g. ``pyenv/``)::
+
+ pip install -e git+https://github.com/okfn/ckanext-example#egg=ckanext-example
+
+Then activate it by setting ``ckan.plugins = example`` in your main ``ini``-file.
+
+
+Orientation
+===========
+
+* Examine the source code, starting with ``ckanext/example/plugin.py``
+
+* To understand the nuts and bolts of this file, which is a CKAN
+ *Extension*, read in conjunction with the "Extension
+ documentation":http://packages.python.org/ckan/plugins.html
+
+* One thing the extension does is set the values of
+ ``extra_public_paths`` and ``extra_template_paths`` in the CKAN
+ config, which are "documented
+ here":http://packages.python.org/ckan/configuration.html#extra-template-paths
+
+* These are set to point at directories within
+ `ckanext/example/theme/`` (in this package). Here, we override
+ the home page, provide some extra style with an ``extra.css``, and
+ customise the navigation and header of the main template in the file ``layout.html``.
+
+ The latter file is a great place to make global theme alterations.
+ It uses the _layout template_ pattern "described in the Genshi
+ documentation":http://genshi.edgewall.org/wiki/GenshiTutorial#AddingaLayoutTemplate.
+ This allows you to use Xpath selectors to override snippets of HTML
+ globally.
+
+* The custom package edit form at ``package_form.py`` follows the
+ conventions in the "main CKAN
+ documentation":http://packages.python.org/ckan/forms.html
+
--- a/README.txt
+++ /dev/null
@@ -1,57 +1,1 @@
-This CKAN Extension demonstrates some common patterns for customising a CKAN instance.
-It comprises:
-
- * A CKAN Extension "plugin" at ``ckanext/exampletheme/plugin.py``
- which, when loaded, overrides various settings in the core
- ``ini``-file to provide:
-
- * A path to local customisations of the core templates and stylesheets
- * A "stream filter" that replaces arbitrary strings in rendered templates
- * A "route" to override and extend the default behaviour of a core CKAN page
-
- * A custom Pylons controller for overriding some core CKAN behaviour
-
- * A custom Package edit form
-
- * Some simple template customisations
-
-Installation
-============
-
-To install this package, from your CKAN virtualenv, run the following from your CKAN base folder (e.g. ``pyenv/``)::
-
- pip install -e hg+https://bitbucket.org/okfn/ckanext-exampletheme#egg=ckanext-exampletheme
-
-Then activate it by setting ``ckan.plugins = exampletheme`` in your main ``ini``-file.
-
-
-Orientation
-===========
-
-* Examine the source code, starting with ``ckanext/exampletheme/plugin.py``
-
-* To understand the nuts and bolts of this file, which is a CKAN
- *Extension*, read in conjunction with the "Extension
- documentation":http://packages.python.org/ckan/plugins.html
-
-* One thing the extension does is set the values of
- ``extra_public_paths`` and ``extra_template_paths`` in the CKAN
- config, which are "documented
- here":http://packages.python.org/ckan/configuration.html#extra-template-paths
-
-* These are set to point at directories within
- `ckanext/exampletheme/theme/`` (in this package). Here, we override
- the home page, provide some extra style with an ``extra.css``, and
- customise the navigation and header of the main template in the file ``layout.html``.
-
- The latter file is a great place to make global theme alterations.
- It uses the _layout template_ pattern "described in the Genshi
- documentation":http://genshi.edgewall.org/wiki/GenshiTutorial#AddingaLayoutTemplate.
- This allows you to use Xpath selectors to override snippets of HTML
- globally.
-
-* The custom package edit form at ``package_form.py`` follows the
- conventions in the "main CKAN
- documentation":http://packages.python.org/ckan/forms.html
-
--- /dev/null
+++ b/ckanext/example/__init__.py
@@ -1,1 +1,2 @@
+# package
--- /dev/null
+++ b/ckanext/example/controller.py
@@ -1,1 +1,40 @@
+import sys
+from ckan.lib.base import request
+from ckan.lib.base import c, g, h
+from ckan.lib.base import model
+from ckan.lib.base import render
+from ckan.lib.base import _
+from ckan.controllers.user import UserController
+
+
+class CustomUserController(UserController):
+ """This controller is an example to show how you might extend or
+ override core CKAN behaviour from an extension package.
+
+ It duplicates functionality in the core CKAN UserController's
+ register function, but extends it to make an email address
+ mandatory.
+ """
+ def custom_register(self):
+ if request.method == 'POST':
+ # custom validation that requires an email address
+ error = False
+ c.email = request.params.getone('email')
+ c.login = request.params.getone('login')
+ if not model.User.check_name_available(c.login):
+ error = True
+ h.flash_error(_("That username is not available."))
+ if not c.email:
+ error = True
+ h.flash_error(_("You must supply an email address."))
+ try:
+ self._get_form_password()
+ except ValueError, ve:
+ h.flash_error(ve)
+ error = True
+ if error:
+ return render('user/register.html')
+ # now delegate to core CKAN register method
+ return self.register()
+
--- /dev/null
+++ b/ckanext/example/package_form.py
@@ -1,1 +1,40 @@
+from sqlalchemy.util import OrderedDict
+from pylons.i18n import _
+from ckan.forms import common
+from ckan.forms import package
+
+
+# Setup the fieldset
+def build_example_form(is_admin=False,
+ user_editable_groups=None,
+ **kwargs):
+ """Customise the core CKAN dataset editing form by adding a new
+ field "temporal coverage", and changing the layout of the core
+ fields.
+ """
+ # Restrict fields
+ builder = package.build_package_form(
+ user_editable_groups=user_editable_groups)
+
+ # Extra fields
+ builder.add_field(common.DateRangeExtraField('temporal_coverage'))
+
+ # Layout
+ field_groups = OrderedDict([
+ (_('Customised Basic information'), ['title', 'name', 'url',
+ 'notes', 'tags']),
+ (_('Details'), ['author', 'author_email', 'groups',
+ 'maintainer', 'maintainer_email',
+ 'license_id', 'temporal_coverage' ]),
+ (_('Resources'), ['resources']),
+ ])
+ builder.set_displayed_fields(field_groups)
+ return builder
+
+
+def get_example_fieldset(is_admin=False, user_editable_groups=None, **kwargs):
+ return build_example_form(is_admin=is_admin,
+ user_editable_groups=user_editable_groups,
+ **kwargs).get_fieldset()
+
--- /dev/null
+++ b/ckanext/example/plugin.py
@@ -1,1 +1,93 @@
+import os
+from logging import getLogger
+from genshi.filters.transform import Transformer
+
+from ckan.plugins import implements, SingletonPlugin
+from ckan.plugins import IConfigurer
+from ckan.plugins import IGenshiStreamFilter
+from ckan.plugins import IRoutes
+
+log = getLogger(__name__)
+
+
+class ExamplePlugin(SingletonPlugin):
+ """This plugin demonstrates how a theme packaged as a CKAN
+ extension might extend CKAN behaviour.
+
+ In this case, we implement three extension interfaces:
+
+ - ``IConfigurer`` allows us to override configuration normally
+ 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
+ """
+ implements(IConfigurer, inherit=True)
+ implements(IGenshiStreamFilter, inherit=True)
+ implements(IRoutes, inherit=True)
+
+ def update_config(self, config):
+ """This IConfigurer implementation causes CKAN to look in the
+ ```public``` and ```templates``` directories present in this
+ package for any customisations.
+
+ It also shows how to set the site title here (rather than in
+ the main site .ini file), and causes CKAN to use the
+ customised package form defined in ``package_form.py`` in this
+ directory.
+ """
+ here = os.path.dirname(__file__)
+ rootdir = os.path.dirname(os.path.dirname(here))
+ our_public_dir = os.path.join(rootdir, 'ckanext',
+ 'example', 'theme', 'public')
+ template_dir = os.path.join(rootdir, 'ckanext',
+ 'example', 'theme',
+ 'templates')
+ # set our local template and resource overrides
+ config['extra_public_paths'] = ','.join([our_public_dir,
+ config.get('extra_public_paths', '')])
+ config['extra_template_paths'] = ','.join([template_dir,
+ config.get('extra_template_paths', '')])
+ # set the title
+ config['ckan.site_title'] = "An example CKAN theme"
+ # set the customised package form (see ``setup.py`` for entry point)
+ config['package_form'] = "example_form"
+
+ def filter(self, stream):
+ """Conform to IGenshiStreamFilter interface.
+
+ This example filter renames 'frob' to 'foobar' (this string is
+ found in the custom ``home/index.html`` template provided as
+ part of the package).
+ """
+ stream = stream | Transformer('//p[@id="examplething"]/text()')\
+ .substitute(r'frob', r'foobar')
+ return stream
+
+ def before_map(self, map):
+ """This IRoutes implementation overrides the standard
+ ``/user/register`` behaviour with a custom controller. You
+ might instead use it to provide a completely new page, for
+ example.
+
+ Note that we have also provided a custom register form
+ template at ``theme/templates/user/register.html``.
+ """
+ # Note that when we set up the route, we must use the form
+ # that gives it a name (i.e. in this case, 'register'), so it
+ # works correctly with the url_for helper::
+ # h.url_for('register')
+ map.connect('register',
+ '/user/register',
+ controller='ckanext.example.controller:CustomUserController',
+ action='custom_register')
+ map.connect('/package/new', controller='package_formalchemy', action='new')
+ map.connect('/package/edit/{id}', controller='package_formalchemy', action='edit')
+ return map
+
--- /dev/null
+++ b/ckanext/example/theme/public/css/extra.css
@@ -1,1 +1,5 @@
+body {
+ background-color: pink;
+}
+
--- /dev/null
+++ b/ckanext/example/theme/templates/home/index.html
@@ -1,1 +1,29 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+ xmlns:i18n="http://genshi.edgewall.org/i18n"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ py:strip="">
+ <py:def function="page_title">Home</py:def>
+
+ <py:def function="optional_head">
+ <style type="text/css">
+ #examplething {
+ background-color: yellow;
+ padding: 10px;
+ }
+ </style>
+ </py:def>
+
+ <div py:match="content">
+ <h2>Welcome to Example Theme!</h2>
+ <p>
+ This page left intentionally ugly
+ </p>
+ <p id="examplething">
+ Here is the frob
+ </p>
+ </div>
+
+ <xi:include href="layout.html" />
+</html>
+
--- /dev/null
+++ b/ckanext/example/theme/templates/layout.html
@@ -1,1 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:doap="http://usefulinc.com/ns/doap"
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"
+ py:strip=""
+ >
+<!-- ! a custom primary nav -->
+ <py:match path="//div[@class='menu']">
+ <div class="menu">
+ <ul>
+ <li>${h.nav_link(c, _('Home'), controller='home', action='index', id=None)}
+ </li>
+ <li>${h.nav_link(c, _('Data'), controller='package', action='index', id=None)}
+ </li>
+ </ul>
+ </div>
+ </py:match>
+
+<!-- make a really big search box in the top bar -->
+ <py:match path="//div[@id='top-bar']/div[@class='search-form']">
+ <div class="search-form">
+ <form action="${url(controller='package', action='search')}" method="GET">
+ <input type="search" class="search" name="q" value="" autocomplete="off" results="5" placeholder="What are you looking for?" id="bigsearch" />
+ <input type="submit" class="searchbutton" value="search" />
+ </form>
+ </div>
+ </py:match>
+
+ <xi:include href="layout_base.html" />
+</html>
+
--- /dev/null
+++ b/ckanext/example/theme/templates/user/register.html
@@ -1,1 +1,49 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+ xmlns:i18n="http://genshi.edgewall.org/i18n"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ py:strip="">
+
+ <py:match path="primarysidebar">
+ <li class="widget-container widget_text">
+ <h2>Have an OpenID?</h2>
+ <p>
+ If you have an account with Google, Yahoo or one of many other
+ OpenID providers, you can log in without signing up.
+ </p>
+ <ul>
+ <li>${h.link_to(_('Log in now'), h.url_for(conroller='user', action='login'))}</li>
+ </ul>
+ </li>
+ </py:match>
+
+ <py:def function="page_title">Register - User</py:def>
+ <div py:match="content">
+ <h2>Join the community</h2>
+
+ <form action="/user/register" method="post" class="simple-form" id="register_form">
+ <fieldset>
+ <legend i18n:msg="site_title">Register with CKAN</legend>
+
+ <label for="login">Login:</label>
+ <input name="login" value="${c.login}" />
+ <br/>
+ <label for="fullname">Full name (optional):</label>
+ <input name="fullname" value="${c.fullname}" />
+ <br/>
+ <label for="email">E-Mail:</label>
+ <input name="email" value="${c.email}" />
+ <br/>
+ <label for="password1">Password:</label>
+ <input type="password" name="password1" value="" />
+ <br/>
+ <label for="password2">Password (repeat):</label>
+ <input type="password" name="password2" value="" />
+ <br/>
+ </fieldset>
+ ${h.submit('s', _('Sign up'))}
+ </form>
+ </div>
+ <xi:include href="layout.html" />
+</html>
+
--- a/ckanext/exampletheme/__init__.py
+++ /dev/null
@@ -1,2 +1,1 @@
-# package
--- a/ckanext/exampletheme/controller.py
+++ /dev/null
@@ -1,40 +1,1 @@
-import sys
-from ckan.lib.base import request
-from ckan.lib.base import c, g, h
-from ckan.lib.base import model
-from ckan.lib.base import render
-from ckan.lib.base import _
-from ckan.controllers.user import UserController
-
-
-class CustomUserController(UserController):
- """This controller is an example to show how you might extend or
- override core CKAN behaviour from an extension package.
-
- It duplicates functionality in the core CKAN UserController's
- register function, but extends it to make an email address
- mandatory.
- """
- def custom_register(self):
- if request.method == 'POST':
- # custom validation that requires an email address
- error = False
- c.email = request.params.getone('email')
- c.login = request.params.getone('login')
- if not model.User.check_name_available(c.login):
- error = True
- h.flash_error(_("That username is not available."))
- if not c.email:
- error = True
- h.flash_error(_("You must supply an email address."))
- try:
- self._get_form_password()
- except ValueError, ve:
- h.flash_error(ve)
- error = True
- if error:
- return render('user/register.html')
- # now delegate to core CKAN register method
- return self.register()
-
--- a/ckanext/exampletheme/package_form.py
+++ /dev/null
@@ -1,40 +1,1 @@
-from sqlalchemy.util import OrderedDict
-from pylons.i18n import _
-from ckan.forms import common
-from ckan.forms import package
-
-
-# Setup the fieldset
-def build_example_form(is_admin=False,
- user_editable_groups=None,
- **kwargs):
- """Customise the core CKAN dataset editing form by adding a new
- field "temporal coverage", and changing the layout of the core
- fields.
- """
- # Restrict fields
- builder = package.build_package_form(
- user_editable_groups=user_editable_groups)
-
- # Extra fields
- builder.add_field(common.DateRangeExtraField('temporal_coverage'))
-
- # Layout
- field_groups = OrderedDict([
- (_('Customised Basic information'), ['title', 'name', 'url',
- 'notes', 'tags']),
- (_('Details'), ['author', 'author_email', 'groups',
- 'maintainer', 'maintainer_email',
- 'license_id', 'temporal_coverage' ]),
- (_('Resources'), ['resources']),
- ])
- builder.set_displayed_fields(field_groups)
- return builder
-
-
-def get_example_fieldset(is_admin=False, user_editable_groups=None, **kwargs):
- return build_example_form(is_admin=is_admin,
- user_editable_groups=user_editable_groups,
- **kwargs).get_fieldset()
-
--- a/ckanext/exampletheme/plugin.py
+++ /dev/null
@@ -1,93 +1,1 @@
-import os
-from logging import getLogger
-from genshi.filters.transform import Transformer
-
-from ckan.plugins import implements, SingletonPlugin
-from ckan.plugins import IConfigurer
-from ckan.plugins import IGenshiStreamFilter
-from ckan.plugins import IRoutes
-
-log = getLogger(__name__)
-
-
-class ExampleThemePlugin(SingletonPlugin):
- """This plugin demonstrates how a theme packaged as a CKAN
- extension might extend CKAN behaviour.
-
- In this case, we implement three extension interfaces:
-
- - ``IConfigurer`` allows us to override configuration normally
- 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
- """
- implements(IConfigurer, inherit=True)
- implements(IGenshiStreamFilter, inherit=True)
- implements(IRoutes, inherit=True)
-
- def update_config(self, config):
- """This IConfigurer implementation causes CKAN to look in the
- ```public``` and ```templates``` directories present in this
- package for any customisations.
-
- It also shows how to set the site title here (rather than in
- the main site .ini file), and causes CKAN to use the
- customised package form defined in ``package_form.py`` in this
- directory.
- """
- here = os.path.dirname(__file__)
- rootdir = os.path.dirname(os.path.dirname(here))
- our_public_dir = os.path.join(rootdir, 'ckanext',
- 'exampletheme', 'theme', 'public')
- template_dir = os.path.join(rootdir, 'ckanext',
- 'exampletheme', 'theme',
- 'templates')
- # set our local template and resource overrides
- config['extra_public_paths'] = ','.join([our_public_dir,
- config.get('extra_public_paths', '')])
- config['extra_template_paths'] = ','.join([template_dir,
- config.get('extra_template_paths', '')])
- # set the title
- config['ckan.site_title'] = "An example CKAN theme"
- # set the customised package form (see ``setup.py`` for entry point)
- config['package_form'] = "example_form"
-
- def filter(self, stream):
- """Conform to IGenshiStreamFilter interface.
-
- This example filter renames 'frob' to 'foobar' (this string is
- found in the custom ``home/index.html`` template provided as
- part of the package).
- """
- stream = stream | Transformer('//p[@id="examplething"]/text()')\
- .substitute(r'frob', r'foobar')
- return stream
-
- def before_map(self, map):
- """This IRoutes implementation overrides the standard
- ``/user/register`` behaviour with a custom controller. You
- might instead use it to provide a completely new page, for
- example.
-
- Note that we have also provided a custom register form
- template at ``theme/templates/user/register.html``.
- """
- # Note that when we set up the route, we must use the form
- # that gives it a name (i.e. in this case, 'register'), so it
- # works correctly with the url_for helper::
- # h.url_for('register')
- map.connect('register',
- '/user/register',
- controller='ckanext.exampletheme.controller:CustomUserController',
- action='custom_register')
- map.connect('/package/new', controller='package_formalchemy', action='new')
- map.connect('/package/edit/{id}', controller='package_formalchemy', action='edit')
- return map
-
--- a/ckanext/exampletheme/theme/public/css/extra.css
+++ /dev/null
@@ -1,5 +1,1 @@
-body {
- background-color: pink;
-}
-
--- a/ckanext/exampletheme/theme/templates/home/index.html
+++ /dev/null
@@ -1,29 +1,1 @@
-<html xmlns:py="http://genshi.edgewall.org/"
- xmlns:i18n="http://genshi.edgewall.org/i18n"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- py:strip="">
- <py:def function="page_title">Home</py:def>
-
- <py:def function="optional_head">
- <style type="text/css">
- #examplething {
- background-color: yellow;
- padding: 10px;
- }
- </style>
- </py:def>
-
- <div py:match="content">
- <h2>Welcome to Example Theme!</h2>
- <p>
- This page left intentionally ugly
- </p>
- <p id="examplething">
- Here is the frob
- </p>
- </div>
-
- <xi:include href="layout.html" />
-</html>
-
--- a/ckanext/exampletheme/theme/templates/layout.html
+++ /dev/null
@@ -1,33 +1,1 @@
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:doap="http://usefulinc.com/ns/doap"
- xmlns:foaf="http://xmlns.com/foaf/0.1/"
- py:strip=""
- >
-<!-- ! a custom primary nav -->
- <py:match path="//div[@class='menu']">
- <div class="menu">
- <ul>
- <li>${h.nav_link(c, _('Home'), controller='home', action='index', id=None)}
- </li>
- <li>${h.nav_link(c, _('Data'), controller='package', action='index', id=None)}
- </li>
- </ul>
- </div>
- </py:match>
-
-<!-- make a really big search box in the top bar -->
- <py:match path="//div[@id='top-bar']/div[@class='search-form']">
- <div class="search-form">
- <form action="${url(controller='package', action='search')}" method="GET">
- <input type="search" class="search" name="q" value="" autocomplete="off" results="5" placeholder="What are you looking for?" id="bigsearch" />
- <input type="submit" class="searchbutton" value="search" />
- </form>
- </div>
- </py:match>
-
- <xi:include href="layout_base.html" />
-</html>
-
--- a/ckanext/exampletheme/theme/templates/user/register.html
+++ /dev/null
@@ -1,49 +1,1 @@
-<html xmlns:py="http://genshi.edgewall.org/"
- xmlns:i18n="http://genshi.edgewall.org/i18n"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- py:strip="">
-
- <py:match path="primarysidebar">
- <li class="widget-container widget_text">
- <h2>Have an OpenID?</h2>