diff --git a/ckan/lib/plugins.py b/ckan/lib/plugins.py index f71dd75f04d..ce11b175958 100644 --- a/ckan/lib/plugins.py +++ b/ckan/lib/plugins.py @@ -158,73 +158,43 @@ def register_group_plugins(map): class DefaultDatasetForm(object): - """ - Provides a default implementation of the pluggable package - controller behaviour. + '''The default implementation of IDatasetForm. - This class has 2 purposes: + See ckan.plugins.interfaces.IDatasetForm. - - it provides a base class for IDatasetForm implementations to use - if only a subset of the 5 method hooks need to be customised. + This class has two purposes: - - it provides the fallback behaviour if no plugin is setup to - provide the fallback behaviour. + 1. It provides a base class for IDatasetForm implementations to inherit + from. + + 2. It is used as the default fallback plugin, if no IDatasetForm plugin + registers itself as the fallback. Note - this isn't a plugin implementation. This is deliberate, as we don't want this being registered. - """ - def new_template(self): - """ - Returns a string representing the location of the template to be - rendered for the new page - """ - return 'package/new.html' - def edit_template(self): - """ - Returns a string representing the location of the template to be - rendered for the edit page - """ - return 'package/edit.html' - - def comments_template(self): - """ - Returns a string representing the location of the template to be - rendered for the comments page - """ - return 'package/comments.html' + ''' + def form_to_db_schema_options(self, options): + '''Return different form_to_db_schemas under different conditions. - def search_template(self): - """ - Returns a string representing the location of the template to be - rendered for the search page (if present) - """ - return 'package/search.html' + For example: - def read_template(self): - """ - Returns a string representing the location of the template to be - rendered for the read page - """ - return 'package/read.html' + - if a context is provided, and it contains a schema, return that + schema + - if a dataset is being created via the api then return + form_to_db_schema_api_create() + - if a dataset is being updated via the api then return + form_to_db_schema_api_update() + - if a dataset is being created via the web form then return + form_to_db_schema() - def history_template(self): - """ - Returns a string representing the location of the template to be - rendered for the history page - """ - return 'package/history.html' + The schemas are defined by the methods below. - def package_form(self): - return 'package/new_package_form.html' + Because of this method, if an IDatasetForm plugin inherits from this + class then its form_to_db_schema() method will only be called when a + dataset is being created or updated over the web interface, and not + when the api is being used. - def form_to_db_schema_options(self, options): - ''' This allows us to select different schemas for different - purpose eg via the web interface or via the api or creation vs - updating. It is optional and if not available form_to_db_schema - should be used. - If a context is provided, and it contains a schema, it will be - returned. ''' schema = options.get('context', {}).get('schema', None) if schema: @@ -248,26 +218,28 @@ def form_to_db_schema_api_create(self): def form_to_db_schema_api_update(self): return logic.schema.default_update_package_schema() - def db_to_form_schema(self): - '''This is an interface to manipulate data from the database - into a format suitable for the form (optional)''' - return logic.schema.db_to_form_package_schema() - def db_to_form_schema_options(self, options): - '''This allows the selectino of different schemas for different - purposes. It is optional and if not available, ``db_to_form_schema`` - should be used. - If a context is provided, and it contains a schema, it will be - returned. + '''Return different db_to_form_schemas under different conditions. + + For example: + + - if a context is provided, and it contains a schema, return that + schema + - otherwise return db_to_form_schema() + + The schemas are defined by the methods below. + ''' schema = options.get('context', {}).get('schema', None) if schema: return schema return self.db_to_form_schema() + def db_to_form_schema(self): + return logic.schema.db_to_form_package_schema() + def check_data_dict(self, data_dict, schema=None): - '''Check if the return data is correct, mostly for checking out - if spammers are submitting only part of the form''' + '''Check for spammers submitting only part of the form.''' # Resources might not exist yet (eg. Add Dataset) surplus_keys_schema = ['__extras', '__junk', 'state', 'groups', @@ -311,6 +283,27 @@ def setup_template_variables(self, context, data_dict): except logic.NotAuthorized: c.auth_for_change_state = False + def new_template(self): + return 'package/new.html' + + def read_template(self): + return 'package/read.html' + + def edit_template(self): + return 'package/edit.html' + + def comments_template(self): + return 'package/comments.html' + + def search_template(self): + return 'package/search.html' + + def history_template(self): + return 'package/history.html' + + def package_form(self): + return 'package/new_package_form.html' + class DefaultGroupForm(object): """ diff --git a/ckan/plugins/interfaces.py b/ckan/plugins/interfaces.py index a128f90d248..04e78ff7663 100644 --- a/ckan/plugins/interfaces.py +++ b/ckan/plugins/interfaces.py @@ -525,123 +525,198 @@ def get_helpers(self): class IDatasetForm(Interface): - """ - Allows customisation of the package controller as a plugin. + '''Customize CKAN's dataset (package) schemas and forms. - The behaviour of the plugin is determined by 5 method hooks: + By implementing this interface plugins can customise CKAN's dataset schema, + for example to add new custom fields to datasets. - - package_form(self) - - form_to_db_schema(self) - - db_to_form_schema(self) - - check_data_dict(self, data_dict, schema=None) - - setup_template_variables(self, context, data_dict) + Multiple IDatasetForm plugins can be used at once, each plugin associating + itself with different package types using the ``package_types()`` and + ``is_fallback()`` methods below, and then providing different schemas and + templates for different types of dataset. When a package controller action + is invoked, the ``type`` field of the package will determine which + IDatasetForm plugin (if any) gets delegated to. - Furthermore, there can be many implementations of this plugin registered - at once. With each instance associating itself with 0 or more package - type strings. When a package controller action is invoked, the package - type determines which of the registered plugins to delegate to. Each - implementation must implement two methods which are used to determine the - package-type -> plugin mapping: + When implementing IDatasetForm, you can inherit from + ``ckan.lib.plugins.DefaultDatasetForm``, which provides default + implementations for each of the methods defined in this interface. - - is_fallback(self) - - package_types(self) + See ``ckanext/example_idatasetform`` for an example plugin. - Implementations might want to consider mixing in - ckan.lib.plugins.DefaultDatasetForm which provides - default behaviours for the 5 method hooks. + ''' + def package_types(self): + '''Return an iterable of package types that this plugin handles. - """ + If a request involving a package of one of the returned types is made, + then this plugin instance will be delegated to. - ##### These methods control when the plugin is delegated to ##### + There cannot be two IDatasetForm plugins that return the same package + type, if this happens then CKAN will raise an exception at startup. + + :rtype: iterable of strings + + ''' def is_fallback(self): - """ - Returns true iff this provides the fallback behaviour, when no other - plugin instance matches a package's type. + '''Return ``True`` if this plugin is the fallback plugin. - There must be exactly one fallback controller defined, any attempt to - register more than one will throw an exception at startup. If there's - no fallback registered at startup the - ckan.lib.plugins.DefaultDatasetForm is used as the fallback. - """ + When no IDatasetForm plugin's ``package_types()`` match the ``type`` of + the package being processed, the fallback plugin is delegated to + instead. - def package_types(self): - """ - Returns an iterable of package type strings. + There cannot be more than one IDatasetForm plugin whose + ``is_fallback()`` method returns ``True``, if this happens CKAN will + raise an exception at startup. - If a request involving a package of one of those types is made, then - this plugin instance will be delegated to. + If no IDatasetForm plugin's ``is_fallback()`` method returns ``True``, + CKAN will use ``ckan.lib.plugins.DefaultDatasetForm`` as the fallback. - There must only be one plugin registered to each package type. Any - attempts to register more than one plugin instance to a given package - type will raise an exception at startup. - """ + :rtype: boolean - ##### End of control methods + ''' + + def form_to_db_schema(self): + '''Return the schema for mapping dataset dicts from form to db. + + CKAN will use the returned schema to validate and convert data coming + from users (via the dataset form) before entering that data into the + database. + + If it inherits from ``DefaultDatasetForm``, a plugin can call + ``DefaultDatasetForm``'s ``form_to_db_schema()`` method to get the + default schema and then modify and return it. + + CKAN's ``convert_to_tags()`` or ``convert_to_extras()`` function can be + used to convert custom fields into dataset tags or extras for storing + in the database. + + See ``ckanext/example_idatasetform`` for examples. + + :returns: a dictionary mapping dataset dict keys to lists of validator + and converter functions to be applied to those keys + :rtype: dictionary + + ''' + + def db_to_form_schema(self): + '''Return the schema to map dataset dicts from db to form. + + CKAN will use the returned schema to validate and convert data coming + from the database before it is passed to a template for rendering. + + If it inherits from ``DefaultDatasetForm``, a plugin can call + ``DefaultDatasetForm``'s ``db_to_form_schema()`` method to get the + default schema and then modify and return it. + + If you have used ``convert_to_tags()`` or ``convert_to_extras()`` in + your form_to_db_schema then you should use ``convert_from_tags()`` or + ``convert_from_extras()`` in your db_to_form_schema to convert the tags + or extras in the database back into your custom dataset fields. + + See ``ckanext/example_idatasetform`` for examples. + + :returns: a dictionary mapping dataset dict keys to lists of validator + and converter functions to be applied to those keys + :rtype: dictionary + + ''' + + def check_data_dict(self, data_dict, schema=None): + '''Check if the given data_dict is correct, raise DataError if not. + + This function is called when a user creates or updates a dataset. The + given ``data_dict`` is the dataset submitted by the user via the + dataset form, before it has been validated and converted by the schema + returned by ``form_to_db_schema()`` above. + + :raises ckan.lib.navl.dictization_functions.DataError: if the given + ``data_dict`` is not correct + + ''' + + def setup_template_variables(self, context, data_dict): + '''Add variables to the template context for use in templates. + + This function is called before a dataset template is rendered. If you + have custom dataset templates that require some additional variables, + you can add them to the template context ``ckan.plugins.toolkit.c`` + here and they will be available in your templates. See + ``ckanext/example_idatasetform`` for an example. + + ''' - ##### Hooks for customising the PackageController's behaviour ##### - ##### TODO: flesh out the docstrings a little more. ##### def new_template(self): - """ - Returns a string representing the location of the template to be - rendered for the new page - """ + '''Return the path to the template for the new dataset page. + + The path should be relative to the plugin's templates dir, e.g. + ``'package/new.html'``. + + :rtype: string + + ''' + + def read_template(self): + '''Return the path to the template for the dataset read page. + + The path should be relative to the plugin's templates dir, e.g. + ``'package/read.html'``. + + :rtype: string + + ''' + + def edit_template(self): + '''Return the path to the template for the dataset edit page. + + The path should be relative to the plugin's templates dir, e.g. + ``'package/edit.html'``. + + :rtype: string + + ''' def comments_template(self): - """ - Returns a string representing the location of the template to be - rendered for the comments page - """ + '''Return the path to the template for the dataset comments page. + + The path should be relative to the plugin's templates dir, e.g. + ``'package/comments.html'``. + + :rtype: string + + ''' def search_template(self): - """ - Returns a string representing the location of the template to be - rendered for the search page (if present) - """ + '''Return the path to the template for use in the dataset search page. - def read_template(self): - """ - Returns a string representing the location of the template to be - rendered for the read page - """ + This template is used to render each dataset that is listed in the + search results on the dataset search page. + + The path should be relative to the plugin's templates dir, e.g. + ``'package/search.html'``. + + :rtype: string + + ''' def history_template(self): - """ - Returns a string representing the location of the template to be - rendered for the history page - """ + '''Return the path to the template for the dataset history page. - def package_form(self): - """ - Returns a string representing the location of the template to be - rendered. e.g. "package/new_package_form.html". - """ + The path should be relative to the plugin's templates dir, e.g. + ``'package/history.html'``. - def form_to_db_schema(self): - """ - Returns the schema for mapping package data from a form to a format - suitable for the database. - """ + :rtype: string - def db_to_form_schema(self): - """ - Returns the schema for mapping package data from the database into a - format suitable for the form (optional) - """ + ''' - def check_data_dict(self, data_dict, schema=None): - """ - Check if the return data is correct. + def package_form(self): + '''Return the path to the template for the dataset form. - raise a DataError if not. - """ + The path should be relative to the plugin's templates dir, e.g. + ``'package/form.html'``. - def setup_template_variables(self, context, data_dict): - """ - Add variables to c just prior to the template being rendered. - """ + :rtype: string - ##### End of hooks ##### + ''' class IGroupForm(Interface): diff --git a/doc/conf.py b/doc/conf.py index 0ff3acff9cf..4cd60ec1e10 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -27,6 +27,7 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo'] +autodoc_member_order = 'bysource' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates']