[ckan-changes] [okfn/ckan] f4aa0e: pep8 fixes for plugins
GitHub
noreply at github.com
Mon Apr 30 11:56:31 UTC 2012
Branch: refs/heads/master
Home: https://github.com/okfn/ckan
Commit: f4aa0e8df4adcd38b32730317c935cfcb1121f4b
https://github.com/okfn/ckan/commit/f4aa0e8df4adcd38b32730317c935cfcb1121f4b
Author: Toby <toby.junk at gmail.com>
Date: 2012-04-26 (Thu, 26 Apr 2012)
Changed paths:
M ckan/plugins/core.py
M ckan/plugins/interfaces.py
M ckan/plugins/toolkit.py
Log Message:
-----------
pep8 fixes for plugins
diff --git a/ckan/plugins/core.py b/ckan/plugins/core.py
index 5260164..1fee98a 100644
--- a/ckan/plugins/core.py
+++ b/ckan/plugins/core.py
@@ -6,11 +6,10 @@
from inspect import isclass
from itertools import chain
from pkg_resources import iter_entry_points
-from pyutilib.component.core import PluginGlobals, ExtensionPoint as PluginImplementations, implements
+from pyutilib.component.core import PluginGlobals, implements
+from pyutilib.component.core import ExtensionPoint as PluginImplementations
from pyutilib.component.core import SingletonPlugin as _pca_SingletonPlugin
from pyutilib.component.core import Plugin as _pca_Plugin
-from pyutilib.component.core import PluginEnvironment
-from sqlalchemy.orm.interfaces import MapperExtension
from ckan.plugins.interfaces import IPluginObserver
@@ -23,18 +22,20 @@
log = logging.getLogger(__name__)
-# Entry point group.
+# Entry point group.
PLUGINS_ENTRY_POINT_GROUP = "ckan.plugins"
# Entry point group for system plugins (those that are part of core ckan and do
# not need to be explicitly enabled by the user)
SYSTEM_PLUGINS_ENTRY_POINT_GROUP = "ckan.system_plugins"
+
class PluginNotFoundException(Exception):
"""
Raised when a requested plugin cannot be found.
"""
+
class Plugin(_pca_Plugin):
"""
Base class for plugins which require multiple instances.
@@ -43,6 +44,7 @@ class Plugin(_pca_Plugin):
probably use SingletonPlugin.
"""
+
class SingletonPlugin(_pca_SingletonPlugin):
"""
Base class for plugins which are singletons (ie most of them)
@@ -52,6 +54,7 @@ class SingletonPlugin(_pca_SingletonPlugin):
same singleton instance.
"""
+
def _get_service(plugin):
"""
Return a service (ie an instance of a plugin class).
@@ -100,6 +103,7 @@ def load_all(config):
for plugin in plugins:
load(plugin)
+
def reset():
"""
Clear and reload all configured plugins
@@ -107,6 +111,7 @@ def reset():
from pylons import config
load_all(config)
+
def load(plugin):
"""
Load a single plugin, given a plugin name, class or instance
@@ -120,6 +125,7 @@ def load(plugin):
observer_plugin.after_load(service)
return service
+
def unload_all():
"""
Unload (deactivate) all loaded plugins
@@ -128,6 +134,7 @@ def unload_all():
for service in env.services.copy():
unload(service)
+
def unload(plugin):
"""
Unload a single plugin, given a plugin name, class or instance
@@ -144,6 +151,7 @@ def unload(plugin):
return service
+
def find_user_plugins(config):
"""
Return all plugins specified by the user in the 'ckan.plugins' config
@@ -159,10 +167,11 @@ def find_user_plugins(config):
plugins.extend(ep.load() for ep in entry_points)
return plugins
+
def find_system_plugins():
"""
Return all plugins in the ckan.system_plugins entry point group.
-
+
These are essential for operation and therefore cannot be enabled/disabled
through the configuration file.
"""
@@ -170,4 +179,3 @@ def find_system_plugins():
ep.load()
for ep in iter_entry_points(group=SYSTEM_PLUGINS_ENTRY_POINT_GROUP)
)
-
diff --git a/ckan/plugins/interfaces.py b/ckan/plugins/interfaces.py
index bc2eb88..4ca74f2 100644
--- a/ckan/plugins/interfaces.py
+++ b/ckan/plugins/interfaces.py
@@ -21,6 +21,7 @@
from inspect import isclass
from pyutilib.component.core import Interface as _pca_Interface
+
class Interface(_pca_Interface):
@classmethod
@@ -80,13 +81,15 @@ def before_map(self, map):
def after_map(self, map):
"""
- Called after routes map is set up. ``after_map`` can be used to add fall-back handlers.
+ Called after routes map is set up. ``after_map`` can be used to
+ add fall-back handlers.
:param map: Routes map object
:returns: Modified version of the map object
"""
return map
+
class IMapper(Interface):
"""
A subset of the SQLAlchemy mapper extension hooks.
@@ -104,7 +107,8 @@ class IMapper(Interface):
def before_insert(self, mapper, connection, instance):
"""
- Receive an object instance before that instance is INSERTed into its table.
+ Receive an object instance before that instance is INSERTed into
+ its table.
"""
def before_update(self, mapper, connection, instance):
@@ -132,6 +136,7 @@ def after_delete(self, mapper, connection, instance):
Receive an object instance after that instance is DELETEed.
"""
+
class ISession(Interface):
"""
A subset of the SQLAlchemy session extension hooks.
@@ -167,6 +172,7 @@ def after_rollback(self, session):
Execute after a rollback has occured.
"""
+
class IDomainObjectModification(Interface):
"""
Receives notification of new, changed and deleted datesets.
@@ -175,6 +181,7 @@ class IDomainObjectModification(Interface):
def notify(self, entity, operation):
pass
+
class IResourceUrlChange(Interface):
"""
Receives notification of changed urls.
@@ -183,6 +190,7 @@ class IResourceUrlChange(Interface):
def notify(self, resource):
pass
+
class ITagController(Interface):
'''
Hook into the Tag controller. These will usually be called just before
@@ -198,6 +206,7 @@ def before_view(self, tag_dict):
'''
return tag_dict
+
class IGroupController(Interface):
"""
Hook into the Group controller. These will
@@ -226,11 +235,13 @@ def delete(self, entity):
def before_view(self, pkg_dict):
'''
- Extensions will recieve this before the group gets displayed. The dictionary
- passed will be the one that gets sent to the template.
+ Extensions will recieve this before the group gets
+ displayed. The dictionary passed will be the one that gets
+ sent to the template.
'''
return pkg_dict
+
class IPackageController(Interface):
"""
Hook into the package controller.
@@ -288,17 +299,19 @@ def after_search(self, search_results, search_params):
def before_index(self, pkg_dict):
'''
- Extensions will receive what will be given to the solr for indexing.
- This is essentially a flattened dict (except for multlivlaued fields such as tags
- of all the terms sent to the indexer. The extension can modify this by returning
- an altered version.
+ Extensions will receive what will be given to the solr for
+ indexing. This is essentially a flattened dict (except for
+ multli-valued fields such as tags) of all the terms sent to
+ the indexer. The extension can modify this by returning an
+ altered version.
'''
return pkg_dict
def before_view(self, pkg_dict):
'''
- Extensions will recieve this before the dataset gets displayed. The dictionary
- passed will be the one that gets sent to the template.
+ Extensions will recieve this before the dataset gets
+ displayed. The dictionary passed will be the one that gets
+ sent to the template.
'''
return pkg_dict
@@ -332,6 +345,7 @@ def after_unload(self, service):
This method is passed the instantiated service object.
"""
+
class IConfigurable(Interface):
"""
Pass configuration to plugins and extensions
@@ -342,6 +356,7 @@ def configure(self, config):
Called by load_environment
"""
+
class IConfigurer(Interface):
"""
Configure CKAN (pylons) environment via the ``pylons.config`` object
@@ -382,6 +397,7 @@ def is_authorized(self, username, action, domain_obj):
other Authorizers to run; True will shortcircuit and return.
"""
+
class IActions(Interface):
"""
Allow adding of actions to the logic layer.
@@ -392,6 +408,7 @@ def get_actions(self):
function and the values being the functions themselves.
"""
+
class IAuthFunctions(Interface):
"""
Allow customisation of default Authorization implementation
@@ -402,6 +419,7 @@ def get_auth_functions(self):
implementation overrides
"""
+
class ITemplateHelpers(Interface):
"""
Allow adding extra template functions available via h variable
@@ -412,6 +430,7 @@ def get_helpers(self):
function and the values being the functions themselves.
"""
+
class IDatasetForm(Interface):
"""
Allows customisation of the package controller as a plugin.
@@ -499,7 +518,6 @@ def history_template(self):
rendered for the history page
"""
-
def package_form(self):
"""
Returns a string representing the location of the template to be
@@ -616,8 +634,6 @@ def history_template(self):
rendered for the history page
"""
-
-
def package_form(self):
"""
Returns a string representing the location of the template to be
@@ -649,4 +665,3 @@ def setup_template_variables(self, context, data_dict):
"""
##### End of hooks #####
-
diff --git a/ckan/plugins/toolkit.py b/ckan/plugins/toolkit.py
index fa1f2c3..120c0ac 100644
--- a/ckan/plugins/toolkit.py
+++ b/ckan/plugins/toolkit.py
@@ -8,10 +8,12 @@
__all__ = ['toolkit']
+
class CkanVersionException(Exception):
''' Exception raised if required ckan version is not available. '''
pass
+
class _Toolkit(object):
'''This class is intended to make functions/objects consistently
available to plugins, whilst giving developers the ability move
@@ -40,7 +42,8 @@ class _Toolkit(object):
'literal', # stop tags in a string being escaped
'get_action', # get logic action function
'check_access', # check logic function authorisation
- 'ObjectNotFound', # action not found exception (ckan.logic.NotFound)
+ 'ObjectNotFound', # action not found exception
+ # (ckan.logic.NotFound)
'NotAuthorized', # action not authorized exception
'ValidationError', # model update validation error
'CkanCommand', # class for providing cli interfaces
@@ -53,7 +56,6 @@ class _Toolkit(object):
'CkanVersionException',
]
-
def __init__(self):
self._toolkit = {}
@@ -85,7 +87,7 @@ def _initialize(self):
t['get_action'] = logic.get_action
t['check_access'] = logic.check_access
- t['ObjectNotFound'] = logic.NotFound ## Name change intentional
+ t['ObjectNotFound'] = logic.NotFound # Name change intentional
t['NotAuthorized'] = logic.NotAuthorized
t['ValidationError'] = logic.ValidationError
@@ -117,7 +119,8 @@ def _render_snippet(cls, template, data=None):
def _add_template_directory(cls, config, relative_path):
''' Function to aid adding extra template paths to the config.
The path is relative to the file calling this function. '''
- cls._add_served_directory(config, relative_path, 'extra_template_paths')
+ cls._add_served_directory(config, relative_path,
+ 'extra_template_paths')
@classmethod
def _add_public_directory(cls, config, relative_path):
================================================================
Commit: 38f1b5610fe48be60da14b90a00622510314264f
https://github.com/okfn/ckan/commit/38f1b5610fe48be60da14b90a00622510314264f
Author: Toby <toby.junk at gmail.com>
Date: 2012-04-26 (Thu, 26 Apr 2012)
Changed paths:
M ckan/__init__.py
M ckan/config/solr/CHANGELOG.txt
M ckan/config/solr/schema-1.4.xml
M ckan/controllers/package.py
M ckan/lib/alphabet_paginate.py
M ckan/lib/search/__init__.py
M ckan/lib/search/index.py
M ckan/logic/auth/delete.py
M ckan/logic/auth/publisher/delete.py
M ckan/public/css/style.css
M ckan/public/scripts/vendor/recline/css/graph.css
M ckan/public/scripts/vendor/recline/recline.js
M ckan/templates/_util.html
M ckan/templates/package/related_list.html
M ckan/templates/tag/index.html
M ckan/templates/user/list.html
M ckan/templates/user/login.html
M ckan/templates/user/logout.html
M ckan/tests/functional/test_search.py
M ckan/tests/functional/test_user.py
M ckan/tests/lib/test_alphabet_pagination.py
M ckan/tests/lib/test_solr_package_search.py
M ckanext/multilingual/solr/schema.xml
M doc/index.rst
M doc/solr-setup.rst
M doc/using-data-api.rst
M pip-requirements.txt
Log Message:
-----------
Merge branch 'master' of github.com:okfn/ckan
diff --git a/ckan/__init__.py b/ckan/__init__.py
index b433319..b1b1544 100644
--- a/ckan/__init__.py
+++ b/ckan/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '1.6.1b'
+__version__ = '1.8a'
__description__ = 'Comprehensive Knowledge Archive Network (CKAN) Software'
__long_description__ = \
'''CKAN software provides a hub for datasets. The flagship site running CKAN
diff --git a/ckan/config/solr/CHANGELOG.txt b/ckan/config/solr/CHANGELOG.txt
index 5fe664f..1e4e67f 100644
--- a/ckan/config/solr/CHANGELOG.txt
+++ b/ckan/config/solr/CHANGELOG.txt
@@ -1,6 +1,14 @@
CKAN SOLR schemas changelog
===========================
+v1.4 - (ckan>=1.7)
+--------------------
+* Add Ascii folding filter to text fields.
+* Add capacity field for public, private access.
+* Add title_string so you can sort alphabetically on title.
+* Fields related to analytics, access and view counts.
+* Add data_dict field for the whole package_dict.
+
v1.3 - (ckan>=1.5.1)
--------------------
* Use the index_id (hash of dataset id + site_id) as uniqueKey (#1430)
diff --git a/ckan/config/solr/schema-1.4.xml b/ckan/config/solr/schema-1.4.xml
index 29cb473..0409e71 100644
--- a/ckan/config/solr/schema-1.4.xml
+++ b/ckan/config/solr/schema-1.4.xml
@@ -51,6 +51,7 @@
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
+ <filter class="solr.ASCIIFoldingFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
@@ -63,6 +64,7 @@
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
+ <filter class="solr.ASCIIFoldingFilterFactory"/>
</analyzer>
</fieldType>
@@ -115,6 +117,8 @@
<field name="tags" type="string" indexed="true" stored="true" multiValued="true"/>
<field name="groups" type="string" indexed="true" stored="true" multiValued="true"/>
+ <field name="capacity" type="string" indexed="true" stored="true" multiValued="false"/>
+
<field name="res_description" type="textgen" indexed="true" stored="true" multiValued="true"/>
<field name="res_format" type="string" indexed="true" stored="true" multiValued="true"/>
<field name="res_url" type="string" indexed="true" stored="true" multiValued="true"/>
@@ -134,8 +138,8 @@
<field name="parent_of" type="text" indexed="true" stored="false" multiValued="true"/>
<field name="views_total" type="int" indexed="true" stored="false"/>
<field name="views_recent" type="int" indexed="true" stored="false"/>
- <field name="recources_accessed_total" type="int" indexed="true" stored="false"/>
- <field name="recources_accessed_recent" type="int" indexed="true" stored="false"/>
+ <field name="resources_accessed_total" type="int" indexed="true" stored="false"/>
+ <field name="resources_accessed_recent" type="int" indexed="true" stored="false"/>
<field name="metadata_created" type="date" indexed="true" stored="true" multiValued="false"/>
<field name="metadata_modified" type="date" indexed="true" stored="true" multiValued="false"/>
@@ -144,8 +148,9 @@
<!-- Copy the title field into titleString, and treat as a string
(rather than text type). This allows us to sort on the titleString -->
- <field name="titleString" type="string" indexed="true" stored="false" />
- <copyField source="title" dest="titleString"/>
+ <field name="title_string" type="string" indexed="true" stored="false" />
+
+ <field name="data_dict" type="string" indexed="false" stored="true" />
<dynamicField name="extras_*" type="text" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*" type="string" indexed="true" stored="false"/>
diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py
index ba12b86..9e99e0f 100644
--- a/ckan/controllers/package.py
+++ b/ckan/controllers/package.py
@@ -480,6 +480,7 @@ def edit(self, id, data=None, errors=None, error_summary=None):
c.errors_json = json.dumps(errors)
self._setup_template_variables(context, {'id': id}, package_type=package_type)
+ c.related_count = len(c.pkg.related)
# TODO: This check is to maintain backwards compatibility with the old way of creating
# custom forms. This behaviour is now deprecated.
@@ -749,6 +750,8 @@ def resource_read(self, id, resource_id):
c.package['isopen'] = False
c.datastore_api = h.url_for('datastore_read', id=c.resource.get('id'),
qualified=True)
+
+ c.related_count = len(c.pkg.related)
return render('package/resource_read.html')
def resource_embedded_dataviewer(self, id, resource_id):
diff --git a/ckan/lib/alphabet_paginate.py b/ckan/lib/alphabet_paginate.py
index 0afa88f..b75bf27 100644
--- a/ckan/lib/alphabet_paginate.py
+++ b/ckan/lib/alphabet_paginate.py
@@ -1,6 +1,7 @@
'''
-Based on webhelpers.paginator, but each page is for items beginning
- with a particular letter.
+Based on webhelpers.paginator, but:
+ * each page is for items beginning with a particular letter
+ * output is suitable for Bootstrap
Example:
c.page = h.Page(
@@ -43,7 +44,12 @@ def __init__(self, collection, alpha_attribute, page, other_text, paging_thresho
self.other_text = other_text
self.paging_threshold = paging_threshold
self.controller_name = controller_name
- self.available = dict( (c,0,) for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" )
+
+ self.letters = [char for char in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'] + [self.other_text]
+
+ # Work out which alphabet letters are 'available' i.e. have some results
+ # because we grey-out those which aren't.
+ self.available = dict( (c,0,) for c in self.letters )
for c in self.collection:
if isinstance(c, unicode):
x = c[0]
@@ -51,35 +57,42 @@ def __init__(self, collection, alpha_attribute, page, other_text, paging_thresho
x = c[self.alpha_attribute][0]
else:
x = getattr(c, self.alpha_attribute)[0]
+ x = x.upper()
+ if x not in self.letters:
+ x = self.other_text
self.available[x] = self.available.get(x, 0) + 1
def pager(self, q=None):
'''Returns pager html - for navigating between the pages.
e.g. Something like this:
- <div class='pager'>
- <span class="pager_curpage">A</span>
- <a class="pager_link" href="/package/list?page=B">B</a>
- <a class="pager_link" href="/package/list?page=C">C</a>
+ <ul class='pagination pagination-alphabet'>
+ <li class="active"><a href="/package/list?page=A">A</a></li>
+ <li><a href="/package/list?page=B">B</a></li>
+ <li><a href="/package/list?page=C">C</a></li>
...
- <a class="pager_link" href="/package/list?page=Z">Z</a
- <a class="pager_link" href="/package/list?page=Other">Other</a
- </div>
+ <li class="disabled"><a href="/package/list?page=Z">Z</a></li>
+ <li><a href="/package/list?page=Other">Other</a></li>
+ </ul>
'''
if self.item_count < self.paging_threshold:
return ''
pages = []
page = q or self.page
- letters = [char for char in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'] + [self.other_text]
- for letter in letters:
+ for letter in self.letters:
+ href = url_for(controller=self.controller_name, action='index', page=letter)
+ link = HTML.a(href=href, c=letter)
if letter != page:
if self.available.get(letter, 0):
- page_element = HTML.a(class_='pager_link', href=url_for(controller=self.controller_name, action='index', page=letter),c=letter)
+ li_class = ''
else:
- page_element = HTML.span(class_="pager_empty", c=letter)
+ li_class = 'disabled'
else:
- page_element = HTML.span(class_='pager_curpage', c=letter)
+ li_class = 'active'
+ attributes = {'class_': li_class} if li_class else {}
+ page_element = HTML.li(link, **attributes)
pages.append(page_element)
- div = HTML.tag('div', class_='pager', *pages)
+ ul = HTML.tag('ul', *pages)
+ div = HTML.div(ul, class_='pagination pagination-alphabet')
return div
diff --git a/ckan/lib/search/__init__.py b/ckan/lib/search/__init__.py
index fbb924a..b2774fc 100644
--- a/ckan/lib/search/__init__.py
+++ b/ckan/lib/search/__init__.py
@@ -26,7 +26,7 @@ def text_traceback():
SIMPLE_SEARCH = config.get('ckan.simple_search', False)
-SUPPORTED_SCHEMA_VERSIONS = ['1.3']
+SUPPORTED_SCHEMA_VERSIONS = ['1.4']
DEFAULT_OPTIONS = {
'limit': 20,
diff --git a/ckan/lib/search/index.py b/ckan/lib/search/index.py
index 086a39e..992721f 100644
--- a/ckan/lib/search/index.py
+++ b/ckan/lib/search/index.py
@@ -99,6 +99,11 @@ def index_package(self, pkg_dict):
if pkg_dict is None:
return
+ # add to string field for sorting
+ title = pkg_dict.get('title')
+ if title:
+ pkg_dict['title_string'] = title
+
if (not pkg_dict.get('state')) or ('active' not in pkg_dict.get('state')):
return self.delete_package(pkg_dict)
@@ -163,7 +168,7 @@ def index_package(self, pkg_dict):
pkg_dict = dict([(k.encode('ascii', 'ignore'), v) for (k, v) in pkg_dict.items()])
- for k in ('title','notes'):
+ for k in ('title', 'notes', 'title_string'):
if k in pkg_dict and pkg_dict[k]:
pkg_dict[k] = escape_xml_illegal_chars(pkg_dict[k])
diff --git a/ckan/logic/auth/delete.py b/ckan/logic/auth/delete.py
index c99bec3..c324049 100644
--- a/ckan/logic/auth/delete.py
+++ b/ckan/logic/auth/delete.py
@@ -27,6 +27,15 @@ def related_delete(context, data_dict):
related = get_related_object(context, data_dict)
userobj = model.User.get( user )
+
+ if related.datasets:
+ package = related.datasets[0]
+
+ pkg_dict = { 'id': package.id }
+ authorized = package_delete(context, pkg_dict).get('success')
+ if authorized:
+ return {'success': True}
+
if not userobj or userobj.id != related.owner_id:
return {'success': False, 'msg': _('Only the owner can delete a related item')}
diff --git a/ckan/logic/auth/publisher/delete.py b/ckan/logic/auth/publisher/delete.py
index 9d3388f..0aaa0d9 100644
--- a/ckan/logic/auth/publisher/delete.py
+++ b/ckan/logic/auth/publisher/delete.py
@@ -40,6 +40,12 @@ def related_delete(context, data_dict):
related = get_related_object(context, data_dict)
userobj = model.User.get( user )
+
+ if related.datasets:
+ package = related.datasets[0]
+ if _groups_intersect( userobj.get_groups('organization'), package.get_groups('organization') ):
+ return {'success': True}
+
if not userobj or userobj.id != related.owner_id:
return {'success': False, 'msg': _('Only the owner can delete a related item')}
diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css
index 7e9e609..283f5d3 100644
--- a/ckan/public/css/style.css
+++ b/ckan/public/css/style.css
@@ -379,28 +379,8 @@ ul.no-break li {
/* ============== */
/* = Pagination = */
/* ============== */
-.pager {
- width: 100%;
- text-align: center;
- margin: 0 0 1.2em 0;
- clear: both;
-}
-.pager span, .pager a {
- text-decoration: none;
- margin: 0em;
- border: none;
- padding: 0.3em 0.1em;
-}
-.pager a:hover, .pager a:active {
- color: #fff;
- background-color: #c22;
-}
-.pager span.pager_dotdot {
- color: #aaa;
-}
-.pager span.pager_curpage {
- font-weight: bold;
- border: 1px solid #ddd;
+.pagination-alphabet a {
+ padding: 0 6px;
}
/* ====== */
diff --git a/ckan/public/scripts/vendor/recline/css/graph.css b/ckan/public/scripts/vendor/recline/css/graph.css
index 88acf5f..413ac14 100644
--- a/ckan/public/scripts/vendor/recline/css/graph.css
+++ b/ckan/public/scripts/vendor/recline/css/graph.css
@@ -13,6 +13,11 @@
line-height: 13px;
}
+.recline-graph .graph .alert {
+ width: 450px;
+ margin: auto;
+}
+
/**********************************************************
* Editor
*********************************************************/
diff --git a/ckan/public/scripts/vendor/recline/recline.js b/ckan/public/scripts/vendor/recline/recline.js
index a4c01ca..271e9c5 100644
--- a/ckan/public/scripts/vendor/recline/recline.js
+++ b/ckan/public/scripts/vendor/recline/recline.js
@@ -757,22 +757,13 @@ my.Graph = Backbone.View.extend({
<label>Group Column (x-axis)</label> \
<div class="input editor-group"> \
<select> \
+ <option value="">Please choose ...</option> \
{{#fields}} \
<option value="{{id}}">{{label}}</option> \
{{/fields}} \
</select> \
</div> \
<div class="editor-series-group"> \
- <div class="editor-series"> \
- <label>Series <span>A (y-axis)</span></label> \
- <div class="input"> \
- <select> \
- {{#fields}} \
- <option value="{{id}}">{{label}}</option> \
- {{/fields}} \
- </select> \
- </div> \
- </div> \
</div> \
</div> \
<div class="editor-buttons"> \
@@ -784,13 +775,34 @@ my.Graph = Backbone.View.extend({
</div> \
</form> \
</div> \
- <div class="panel graph"></div> \
+ <div class="panel graph"> \
+ <div class="js-temp-notice alert alert-block"> \
+ <h3 class="alert-heading">Hey there!</h3> \
+ <p>There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.</p> \
+ <p>Please tell us by <strong>using the menu on the right</strong> and a graph will automatically appear.</p> \
+ </div> \
+ </div> \
</div> \
',
+ templateSeriesEditor: ' \
+ <div class="editor-series js-series-{{seriesIndex}}"> \
+ <label>Series <span>{{seriesName}} (y-axis)</span> \
+ [<a href="#remove" class="action-remove-series">Remove</a>] \
+ </label> \
+ <div class="input"> \
+ <select> \
+ <option value="">Please choose ...</option> \
+ {{#fields}} \
+ <option value="{{id}}">{{label}}</option> \
+ {{/fields}} \
+ </select> \
+ </div> \
+ </div> \
+ ',
events: {
'change form select': 'onEditorSubmit',
- 'click .editor-add': 'addSeries',
+ 'click .editor-add': '_onAddSeries',
'click .action-remove-series': 'removeSeries',
'click .action-toggle-help': 'toggleHelp'
},
@@ -807,7 +819,8 @@ my.Graph = Backbone.View.extend({
this.model.currentDocuments.bind('reset', this.redraw);
var stateData = _.extend({
group: null,
- series: [],
+ // so that at least one series chooser box shows up
+ series: [""],
graphType: 'lines-and-points'
},
options.state
@@ -817,21 +830,45 @@ my.Graph = Backbone.View.extend({
},
render: function() {
- htmls = $.mustache(this.template, this.model.toTemplateJSON());
+ var self = this;
+ var tmplData = this.model.toTemplateJSON();
+ var htmls = $.mustache(this.template, tmplData);
$(this.el).html(htmls);
- // now set a load of stuff up
this.$graph = this.el.find('.panel.graph');
- // for use later when adding additional series
- // could be simpler just to have a common template!
- this.$seriesClone = this.el.find('.editor-series').clone();
- this._updateSeries();
+
+ // set up editor from state
+ if (this.state.get('graphType')) {
+ this._selectOption('.editor-type', this.state.get('graphType'));
+ }
+ if (this.state.get('group')) {
+ this._selectOption('.editor-group', this.state.get('group'));
+ }
+ _.each(this.state.get('series'), function(series, idx) {
+ self.addSeries(idx);
+ self._selectOption('.editor-series.js-series-' + idx, series);
+ });
return this;
},
+ // Private: Helper function to select an option from a select list
+ //
+ _selectOption: function(id,value){
+ var options = this.el.find(id + ' select > option');
+ if (options) {
+ options.each(function(opt){
+ if (this.value == value) {
+ $(this).attr('selected','selected');
+ return false;
+ }
+ });
+ }
+ },
+
onEditorSubmit: function(e) {
var select = this.el.find('.editor-group select');
- $editor = this;
- var series = this.$series.map(function () {
+ var $editor = this;
+ var $series = this.el.find('.editor-series select');
+ var series = $series.map(function () {
return $(this).val();
});
var updatedState = {
@@ -870,10 +907,20 @@ my.Graph = Backbone.View.extend({
// }
},
+ // ### getGraphOptions
+ //
+ // Get options for Flot Graph
+ //
// needs to be function as can depend on state
+ //
+ // @param typeId graphType id (lines, lines-and-points etc)
getGraphOptions: function(typeId) {
var self = this;
// special tickformatter to show labels rather than numbers
+ // TODO: we should really use tickFormatter and 1 interval ticks if (and
+ // only if) x-axis values are non-numeric
+ // However, that is non-trivial to work out from a dataset (datasets may
+ // have no field type info). Thus at present we only do this for bars.
var tickFormatter = function (val) {
if (self.model.currentDocuments.models[val]) {
var out = self.model.currentDocuments.models[val].get(self.state.attributes.group);
@@ -886,20 +933,25 @@ my.Graph = Backbone.View.extend({
}
return val;
};
- // TODO: we should really use tickFormatter and 1 interval ticks if (and
- // only if) x-axis values are non-numeric
- // However, that is non-trivial to work out from a dataset (datasets may
- // have no field type info). Thus at present we only do this for bars.
- var options = {
+
+ var xaxis = {};
+ // check for time series on x-axis
+ if (this.model.fields.get(this.state.get('group')).get('type') === 'date') {
+ xaxis.mode = 'time';
+ xaxis.timeformat = '%y-%b';
+ }
+ var optionsPerGraphType = {
lines: {
- series: {
- lines: { show: true }
- }
+ series: {
+ lines: { show: true }
+ },
+ xaxis: xaxis
},
points: {
series: {
points: { show: true }
},
+ xaxis: xaxis,
grid: { hoverable: true, clickable: true }
},
'lines-and-points': {
@@ -907,6 +959,7 @@ my.Graph = Backbone.View.extend({
points: { show: true },
lines: { show: true }
},
+ xaxis: xaxis,
grid: { hoverable: true, clickable: true }
},
bars: {
@@ -930,7 +983,7 @@ my.Graph = Backbone.View.extend({
}
}
};
- return options[typeId];
+ return optionsPerGraphType[typeId];
},
setupTooltips: function() {
@@ -987,8 +1040,15 @@ my.Graph = Backbone.View.extend({
_.each(this.state.attributes.series, function(field) {
var points = [];
_.each(self.model.currentDocuments.models, function(doc, index) {
- var x = doc.get(self.state.attributes.group);
- var y = doc.get(field);
+ var xfield = self.model.fields.get(self.state.attributes.group);
+ var x = doc.getFieldValue(xfield);
+ // time series
+ var isDateTime = xfield.get('type') === 'date';
+ if (isDateTime) {
+ x = new Date(x);
+ }
+ var yfield = self.model.fields.get(field);
+ var y = doc.getFieldValue(yfield);
if (typeof x === 'string') {
x = index;
}
@@ -1006,23 +1066,25 @@ my.Graph = Backbone.View.extend({
// Public: Adds a new empty series select box to the editor.
//
- // All but the first select box will have a remove button that allows them
- // to be removed.
+ // @param [int] idx index of this series in the list of series
//
// Returns itself.
- addSeries: function (e) {
- e.preventDefault();
- var element = this.$seriesClone.clone(),
- label = element.find('label'),
- index = this.$series.length;
-
- this.el.find('.editor-series-group').append(element);
- this._updateSeries();
- label.append(' [<a href="#remove" class="action-remove-series">Remove</a>]');
- label.find('span').text(String.fromCharCode(this.$series.length + 64));
+ addSeries: function (idx) {
+ var data = _.extend({
+ seriesIndex: idx,
+ seriesName: String.fromCharCode(idx + 64 + 1),
+ }, this.model.toTemplateJSON());
+
+ var htmls = $.mustache(this.templateSeriesEditor, data);
+ this.el.find('.editor-series-group').append(htmls);
return this;
},
+ _onAddSeries: function(e) {
+ e.preventDefault();
+ this.addSeries(this.state.get('series').length);
+ },
+
// Public: Removes a series list item from the editor.
//
// Also updates the labels of the remaining series elements.
@@ -1030,26 +1092,12 @@ my.Graph = Backbone.View.extend({
e.preventDefault();
var $el = $(e.target);
$el.parent().parent().remove();
- this._updateSeries();
- this.$series.each(function (index) {
- if (index > 0) {
- var labelSpan = $(this).prev().find('span');
- labelSpan.text(String.fromCharCode(index + 65));
- }
- });
this.onEditorSubmit();
},
toggleHelp: function() {
this.el.find('.editor-info').toggleClass('editor-hide-info');
},
-
- // Private: Resets the series property to reference the select elements.
- //
- // Returns itself.
- _updateSeries: function () {
- this.$series = this.el.find('.editor-series select');
- }
});
})(jQuery, recline.View);
diff --git a/ckan/templates/_util.html b/ckan/templates/_util.html
index 8e320d8..5aa7c07 100644
--- a/ckan/templates/_util.html
+++ b/ckan/templates/_util.html
@@ -128,6 +128,7 @@
<py:def function="related_summary(related)">
<li class="span3">
<div class="thumbnail">
+ <button py:if="c.user and (c.userobj.id == related.owner_id or h.check_access('package_update',{'id':c.pkg.id}))" class="close" onclick="related_delete('${related.id}');">×</button>
<a href="${related.url}" class="image">
<img src="${related.image_url}" width="210" py:if="related.image_url" />
<img src="/images/photo-placeholder.png" width="210" py:if="not related.image_url" />
diff --git a/ckan/templates/package/related_list.html b/ckan/templates/package/related_list.html
index 49a75ea..baced80 100644
--- a/ckan/templates/package/related_list.html
+++ b/ckan/templates/package/related_list.html
@@ -40,6 +40,25 @@
</div>
<py:def function="optional_head">
+ <script type="text/javascript" py:if="c.user">
+ function related_delete(related_id) {
+ var data = { 'id' : related_id }
+ $.ajax({
+ type: "post",
+ url: CKAN.SITE_URL + '/api/3/action/related_delete',
+ data: JSON.stringify(data),
+ success: function (data) {
+ window.location.reload();
+ },
+ error: function(err, txt, w) {
+ // This needs to be far more informative.
+ var msg = '<strong>Error:</strong> Unable to delete related item';
+ $('<div class="alert alert-error" />').html(msg).hide().prependTo($('div#main')).fadeIn();
+ }
+ });
+
+ }
+ </script>
<py:if test="config.get('rdf_packages')">
<link rel="alternate" type="application/rdf+xml" title="RDF/XML" href="${config['rdf_packages'] + '/' + c.pkg.id + '.rdf' }" />
<link rel="alternate" type="application/turtle" title="RDF/Turtle" href="${config['rdf_packages'] + '/' + c.pkg.id + '.ttl' }" />
diff --git a/ckan/templates/tag/index.html b/ckan/templates/tag/index.html
index 17292e9..8c383c8 100644
--- a/ckan/templates/tag/index.html
+++ b/ckan/templates/tag/index.html
@@ -8,9 +8,9 @@
<div py:match="content">
<h2>Tags</h2>
- <form id="tag-search" action="" method="GET">
+ <form class="form-inline" id="tag-search" action="" method="GET">
<input type="text" id="q" name="q" value="${c.q}" />
- <input type="submit" name="search" value="${_('Search')} »" />
+ <input class="btn btn-primary" type="submit" name="search" value="${_('Search')} »" />
</form>
<hr />
diff --git a/ckan/templates/user/list.html b/ckan/templates/user/list.html
index 3c736b4..1ef2655 100644
--- a/ckan/templates/user/list.html
+++ b/ckan/templates/user/list.html
@@ -8,9 +8,10 @@
<py:match path="primarysidebar">
<li class="widget-container widget_text">
- <form id="user-search" class="user-search" action="" method="GET">
- <input type="text" id="q" name="q" value="${c.q}" />
- <input type="submit" class="btn btn-small" name="" value="${_('Search')} »" />
+ <h3>Search Users</h3>
+ <form id="user-search" class="form-inline user-search" action="" method="GET">
+ <input type="text" class="input-medium" id="q" name="q" value="${c.q}" />
+ <input type="submit" class="btn btn-small btn-primary" name="" value="${_('Search')} »" />
</form>
<p py:if="c.q" i18n:msg="item_count">
<strong>${c.page.item_count}</strong> users found.
diff --git a/ckan/templates/user/login.html b/ckan/templates/user/login.html
index 6cabb81..9fdf3df 100644
--- a/ckan/templates/user/login.html
+++ b/ckan/templates/user/login.html
@@ -18,25 +18,39 @@
<py:def function="page_title">Login - User</py:def>
<py:def function="page_heading">Login to ${g.site_title}</py:def>
+ <py:def function="body_class">no-sidebar</py:def>
<div py:match="content">
-
- <form action="${h.url_for('/login_generic')}" method="post" class="simple-form" id="login">
+
+ <form action="${h.url_for('/login_generic')}" method="post" class="form-horizontal" id="login">
<fieldset>
<!--legend i18n:msg="site_title">Login</legend-->
+ <div class="control-group">
+ <label class="control-label" for="login">Login:</label>
+ <div class="controls">
+ <input type="text" class="input-xlarge" name="login" id="login" value="" />
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="password">Password:</label>
+ <div class="controls">
+ <input type="password" name="password" id="password" value="" />
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="remember">Remember me:</label>
+ <!-- optional 2 year cookie expiry -->
+ <div class="controls">
+ <input type="checkbox" name="remember" id="remember" value="63072000" checked="checked"/>
+ </div>
+ </div>
- <label for="login">Login:</label>
- <input type="text" name="login" value="" />
- <br/>
- <label for="password">Password:</label>
- <input type="password" name="password" value="" />
- <!-- 50 year timeout -->
- <input type="hidden" name="remember" value="1576800000" />
- <br/>
+ <div class="form-actions">
+ <button name="s" id="s" type="submit" class="btn btn-primary">${_('Sign In')}</button>
+ —
+ <a href="${h.url_for('reset')}">Forgot your password?</a>
+ </div>
</fieldset>
- <input name="s" id="s" type="submit" class="btn primary" value="${_('Sign In')}"/>
- —
- <a href="${h.url_for(controller='user', action='request_reset')}">Forgot your password?</a>
</form>
<br/>
<!-- Simple OpenID Selector -->
diff --git a/ckan/templates/user/logout.html b/ckan/templates/user/logout.html
index e40ec5c..0f37272 100644
--- a/ckan/templates/user/logout.html
+++ b/ckan/templates/user/logout.html
@@ -6,6 +6,7 @@
<py:def function="page_title">Logout</py:def>
<py:def function="page_heading">Logout from ${g.site_title}</py:def>
+ <py:def function="body_class">no-sidebar</py:def>
<div py:match="content">
<p>You have logged out successfully.</p>
diff --git a/ckan/tests/functional/test_search.py b/ckan/tests/functional/test_search.py
index fe1802c..a9a9339 100644
--- a/ckan/tests/functional/test_search.py
+++ b/ckan/tests/functional/test_search.py
@@ -108,7 +108,7 @@ def test_search_foreign_chars(self):
res = self.app.get(offset)
assert 'Search - ' in res
self._check_search_results(res, u'th\xfcmb', ['<strong>1</strong>'])
- self._check_search_results(res, 'thumb', ['<strong>0</strong>'])
+ self._check_search_results(res, 'thumb', ['<strong>1</strong>'])
@search_related
def test_search_escape_chars(self):
diff --git a/ckan/tests/functional/test_user.py b/ckan/tests/functional/test_user.py
index 5ab1efc..eb44bd9 100644
--- a/ckan/tests/functional/test_user.py
+++ b/ckan/tests/functional/test_user.py
@@ -168,11 +168,14 @@ def test_login(self):
fv = res.forms['login']
fv['login'] = str(username)
fv['password'] = str(password)
+ fv['remember'] = False
res = fv.submit()
# check cookies set
cookies = self._get_cookie_headers(res)
assert cookies
+ for cookie in cookies:
+ assert not 'max-age' in cookie.lower(), cookie
# first get redirected to user/logged_in
assert_equal(res.status, 302)
@@ -206,6 +209,32 @@ def test_login(self):
print res
assert 'testlogin' in res.body, res.body
+ def test_login_remembered(self):
+ # create test user
+ username = u'testlogin2'
+ password = u'letmein'
+ CreateTestData.create_user(name=username,
+ password=password)
+ user = model.User.by_name(username)
+
+ # do the login
+ offset = url_for(controller='user', action='login')
+ res = self.app.get(offset)
+ fv = res.forms['login']
+ fv['login'] = str(username)
+ fv['password'] = str(password)
+ fv['remember'] = True
+ res = fv.submit()
+
+ # check cookies set
+ cookies = self._get_cookie_headers(res)
+ assert cookies
+ # check cookie is remembered via Max-Age and Expires
+ # (both needed for cross-browser compatibility)
+ for cookie in cookies:
+ assert 'Max-Age=63072000;' in cookie, cookie
+ assert 'Expires=' in cookie, cookie
+
def test_login_wrong_password(self):
# create test user
username = u'testloginwrong'
diff --git a/ckan/tests/lib/test_alphabet_pagination.py b/ckan/tests/lib/test_alphabet_pagination.py
index a1bf6ae..8afc276 100644
--- a/ckan/tests/lib/test_alphabet_pagination.py
+++ b/ckan/tests/lib/test_alphabet_pagination.py
@@ -1,5 +1,7 @@
import re
+from nose.tools import assert_equal
+
from ckan.tests import *
from ckan.tests import regex_related
from ckan.lib.create_test_data import CreateTestData
@@ -28,6 +30,16 @@ def setup_class(cls):
def teardown_class(cls):
model.repo.rebuild_db()
+ def test_00_model(self):
+ query = model.Session.query(model.Package)
+ page = h.AlphaPage(
+ collection=query,
+ alpha_attribute='title',
+ page='A',
+ other_text=other,
+ )
+ assert_equal(page.available, {'Other': 20, 'A': 10, 'C': 10, 'B': 10, 'E': 0, 'D': 10, 'G': 0, 'F': 0, 'I': 0, 'H': 0, 'K': 0, 'J': 0, 'M': 0, 'L': 0, 'O': 0, 'N': 0, 'Q': 0, 'P': 0, 'S': 0, 'R': 0, 'U': 0, 'T': 0, 'W': 0, 'V': 0, 'Y': 0, 'X': 0, 'Z': 0})
+
def test_01_package_page(self):
query = model.Session.query(model.Package)
page = h.AlphaPage(
@@ -37,11 +49,12 @@ def test_01_package_page(self):
other_text=other,
)
pager = page.pager()
- assert pager.startswith('<div class="pager">'), pager
- assert '<span class="pager_curpage">A</span>' in pager, pager
+ assert pager.startswith('<div class="pagination pagination-alphabet">'), pager
+ assert '<li class="active"><a href="/tag?page=A">A</a></li>' in pager, pager
url_base = '/packages'
- assert re.search('\<span class="pager_empty"\>B\<\/span\>', pager), pager
- assert re.search('\<span class="pager_empty"\>Other\<\/span\>', pager), pager
+ assert re.search(r'\<li\>\<a href="\/tag\?page=B"\>B\<\/a\>\<\/li\>', pager), pager
+ assert re.search(r'\<li class="disabled"\>\<a href="\/tag\?page=E"\>E\<\/a\>\<\/li\>', pager), pager
+ assert re.search(r'\<li\>\<a href="\/tag\?page=Other"\>Other\<\/a\>\<\/li\>', pager), pager
def test_02_package_items(self):
diff --git a/ckan/tests/lib/test_solr_package_search.py b/ckan/tests/lib/test_solr_package_search.py
index 6ec2b2f..75d54c0 100644
--- a/ckan/tests/lib/test_solr_package_search.py
+++ b/ckan/tests/lib/test_solr_package_search.py
@@ -292,7 +292,7 @@ def test_search_foreign_chars(self):
result = search.query_for(model.Package).run({'q': 'umlaut'})
assert result['results'] == ['gils'], result['results']
result = search.query_for(model.Package).run({'q': u'thumb'})
- assert result['count'] == 0, result['results']
+ assert result['results'] == ['gils'], result['results']
result = search.query_for(model.Package).run({'q': u'th\xfcmb'})
assert result['results'] == ['gils'], result['results']
diff --git a/ckanext/multilingual/solr/schema.xml b/ckanext/multilingual/solr/schema.xml
index 8475187..fb957d3 100644
--- a/ckanext/multilingual/solr/schema.xml
+++ b/ckanext/multilingual/solr/schema.xml
@@ -16,7 +16,7 @@
limitations under the License.
-->
-<schema name="ckan" version="1.3">
+<schema name="ckan" version="1.4">
<types>
<fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
@@ -373,6 +373,8 @@
<field name="tags" type="string" indexed="true" stored="true" multiValued="true"/>
<field name="groups" type="string" indexed="true" stored="true" multiValued="true"/>
+ <field name="capacity" type="string" indexed="true" stored="true" multiValued="false"/>
+
<field name="res_description" type="textgen" indexed="true" stored="true" multiValued="true"/>
<field name="res_format" type="string" indexed="true" stored="true" multiValued="true"/>
<field name="res_url" type="string" indexed="true" stored="true" multiValued="true"/>
@@ -390,11 +392,19 @@
<field name="linked_from" type="text" indexed="true" stored="false" multiValued="true"/>
<field name="child_of" type="text" indexed="true" stored="false" multiValued="true"/>
<field name="parent_of" type="text" indexed="true" stored="false" multiValued="true"/>
+ <field name="views_total" type="int" indexed="true" stored="false"/>
+ <field name="views_recent" type="int" indexed="true" stored="false"/>
+ <field name="resources_accessed_total" type="int" indexed="true" stored="false"/>
+ <field name="resources_accessed_recent" type="int" indexed="true" stored="false"/>
<field name="metadata_created" type="date" indexed="true" stored="true" multiValued="false"/>
<field name="metadata_modified" type="date" indexed="true" stored="true" multiValued="false"/>
<field name="indexed_ts" type="date" indexed="true" stored="true" default="NOW" multiValued="false"/>
+
+ <!-- Copy the title field into titleString, and treat as a string
+ (rather than text type). This allows us to sort on the titleString -->
+ <field name="title_string" type="string" indexed="true" stored="false" />
<!-- Multilingual -->
<field name="text_en" type="text_en" indexed="true" stored="true"/>
@@ -424,6 +434,8 @@
<field name="text_pl" type="text_pl" indexed="true" stored="true"/>
<field name="title_pl" type="text_pl" indexed="true" stored="true"/>
+ <field name="data_dict" type="string" indexed="false" stored="true" />
+
<dynamicField name="extras_*" type="text" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*" type="string" indexed="true" stored="false"/>
</fields>
diff --git a/doc/index.rst b/doc/index.rst
index b1c71f0..601d4c7 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -2,6 +2,10 @@
Welcome to CKAN's Administration Guide
=======================================
+.. note ::
+
+ This is the documentation for CKAN version '|version|'. If you are using a different version, use the links on the bottom right corner of the page to select the appropriate documentation.
+
This Administration Guide covers how to set up and manage `CKAN <http://ckan.org>`_ software.
* The first two sections cover your two options for installing CKAN: package or source install.
diff --git a/doc/solr-setup.rst b/doc/solr-setup.rst
index 4529797..3c158b2 100644
--- a/doc/solr-setup.rst
+++ b/doc/solr-setup.rst
@@ -71,7 +71,7 @@ so, create a symbolic link to the schema file in the config folder. Use the late
supported by the CKAN version you are installing (it will generally be the highest one)::
sudo mv /etc/solr/conf/schema.xml /etc/solr/conf/schema.xml.bak
- sudo ln -s ~/ckan/ckan/config/solr/schema-1.3.xml /etc/solr/conf/schema.xml
+ sudo ln -s ~/ckan/ckan/config/solr/schema-1.4.xml /etc/solr/conf/schema.xml
Now restart jetty::
@@ -93,6 +93,7 @@ will have different paths in the Solr server URL::
http://localhost:8983/solr/ckan-schema-1.2 # Used by CKAN up to 1.5
http://localhost:8983/solr/ckan-schema-1.3 # Used by CKAN 1.5.1
+ http://localhost:8983/solr/ckan-schema-1.4 # Used by CKAN 1.7
http://localhost:8983/solr/some-other-site # Used by another site
To set up a multicore Solr instance, repeat the steps on the previous section
diff --git a/doc/using-data-api.rst b/doc/using-data-api.rst
index 09caf21..50c7db2 100644
--- a/doc/using-data-api.rst
+++ b/doc/using-data-api.rst
@@ -2,22 +2,44 @@
Using the Data API
==================
+The following provides an introduction to using the CKAN :doc:`DataStore
+<datastore>` Data API.
+
Introduction
============
-The Data API builds directly on ElasticSearch, with a resource API endpoint
-being equivalent to a single index 'type' in ElasticSearch (we tend to refer to
-it as a 'table'). This means you can often directly re-use `ElasticSearch
-client libraries`_ when connecting to the API endpoint.
+Each 'table' in the DataStore is an ElasticSearch_ index type ('table'). As
+such the Data API for each CKAN resource is directly equivalent to a single
+index 'type' in ElasticSearch (we tend to refer to it as a 'table').
+
+This means you can (usually) directly re-use `ElasticSearch client libraries`_
+when connecting to a Data API endpoint. It also means that what follows is, in
+essence, a tutorial in using the ElasticSearch_ API.
+
+The following short set of slides provide a brief overview and introduction to
+the DataStore and the Data API.
-Furthermore, it means that what is presented below is essentially a tutorial in the ElasticSearch API.
+.. raw:: html
+ <iframe src="https://docs.google.com/presentation/embed?id=1UhEqvEPoL_VWO5okYiEPfZTLcLYWqtvRRmB1NBsWXY8&start=false&loop=false&delayms=3000" frameborder="0" width="480" height="389" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>
+
+.. _ElasticSearch: http://elasticsearch.org/
.. _ElasticSearch client libraries: http://www.elasticsearch.org/guide/appendix/clients.html
Quickstart
==========
-``{{endpoint}}`` refers to the data API endpoint (or ElasticSearch index / table).
+``{{endpoint}}`` refers to the data API endpoint (or ElasticSearch index /
+table). For example, on the DataHub_ this gold prices data resource
+http://datahub.io/dataset/gold-prices/resource/b9aae52b-b082-4159-b46f-7bb9c158d013
+would have its Data API endpoint at:
+http://datahub.io/api/data/b9aae52b-b082-4159-b46f-7bb9c158d013. If you were
+just using ElasticSearch standalone an example of an endpoint would be:
+http://localhost:9200/gold-prices/monthly-price-table.
+
+.. note:: every resource on a CKAN instance for which a DataStore table is
+ enabled provides links to its Data API endpoint via the Data API
+ button at the top right of the resource page.
Key urls:
@@ -28,6 +50,8 @@ Key urls:
* Schema (Mapping): ``{{endpoint}}/_mapping``
+.. _DataHub: http://datahub.io/
+
Examples
--------
diff --git a/pip-requirements.txt b/pip-requirements.txt
index 4460f14..9ff9d10 100644
--- a/pip-requirements.txt
+++ b/pip-requirements.txt
@@ -5,11 +5,11 @@
#
# pip install --ignore-installed -r pip-requirements.txt
--e git+https://github.com/okfn/ckan@release-v1.6.1#egg=ckan
+-e git+https://github.com/okfn/ckan@master#egg=ckan
# CKAN dependencies
--r https://github.com/okfn/ckan/raw/release-v1.6.1/requires/lucid_conflict.txt
--r https://github.com/okfn/ckan/raw/release-v1.6.1/requires/lucid_present.txt
--r https://github.com/okfn/ckan/raw/release-v1.6.1/requires/lucid_missing.txt
+-r https://github.com/okfn/ckan/raw/master/requires/lucid_conflict.txt
+-r https://github.com/okfn/ckan/raw/master/requires/lucid_present.txt
+-r https://github.com/okfn/ckan/raw/master/requires/lucid_missing.txt
# NOTE: Developers, please do not edit this file. Changes should go in the
# appropriate files in the `requires' directory.
================================================================
Commit: fe973717d4a6ed00b326af921e3df246e5b3a67b
https://github.com/okfn/ckan/commit/fe973717d4a6ed00b326af921e3df246e5b3a67b
Author: Toby <toby.junk at gmail.com>
Date: 2012-04-30 (Mon, 30 Apr 2012)
Changed paths:
M ckan/logic/converters.py
M ckan/templates/_snippet/data-api-help.html
M ckan/templates/js_strings.html
M ckan/templates/package/layout.html
M ckanext/multilingual/plugin.py
M ckanext/organizations/forms.py
M ckanext/organizations/templates/organization_package_form.html
M doc/solr-setup.rst
Log Message:
-----------
Merge branch 'master' of github.com:okfn/ckan
diff --git a/ckan/logic/converters.py b/ckan/logic/converters.py
index 6b927ae..18a67df 100644
--- a/ckan/logic/converters.py
+++ b/ckan/logic/converters.py
@@ -77,7 +77,8 @@ def callable(key, data, errors, context):
for k in data.keys():
if k[0] == 'tags':
if data[k].get('vocabulary_id') == v.id:
- tags.append(data[k]['name'])
+ name = data[k].get('display_name', data[k]['name'])
+ tags.append(name)
data[key] = tags
return callable
diff --git a/ckan/templates/_snippet/data-api-help.html b/ckan/templates/_snippet/data-api-help.html
index a387417..16349df 100644
--- a/ckan/templates/_snippet/data-api-help.html
+++ b/ckan/templates/_snippet/data-api-help.html
@@ -17,7 +17,7 @@
<div class="modal-body">
<p><strong>Access resource data via a web API with powerful query
support</strong>. Further information in the <a
- href="http://docs.ckan.org/en/latest/storage/datastore.html" target="_blank">main
+ href="http://docs.ckan.org/en/latest/using-data-api.html" target="_blank">main
CKAN Data API and DataStore documentation</a>.</p>
<div class="accordion-group">
diff --git a/ckan/templates/js_strings.html b/ckan/templates/js_strings.html
index 76a0d7e..a22ea2f 100644
--- a/ckan/templates/js_strings.html
+++ b/ckan/templates/js_strings.html
@@ -13,61 +13,62 @@
/*
* Used in application.js.
*/
- CKAN.Strings.checking = "${_('Checking...')}";
- CKAN.Strings.urlIsTooShort = "${_('Type at least two characters...')}";
- CKAN.Strings.urlIsUnchanged = "${_('This is the current URL.')}";
- CKAN.Strings.urlIsAvailable = "${_('This URL is available!')}";
- CKAN.Strings.urlIsNotAvailable = "${_('This URL is already used, please use a different one.')}";
- CKAN.Strings.failedToSave = "${_('Failed to save, possibly due to invalid data ')}";
- CKAN.Strings.addDataset = "${_('Add Dataset')}";
- CKAN.Strings.addGroup = "${_('Add Group')}";
- CKAN.Strings.youHaveUnsavedChanges = "${_("You have unsaved changes. Make sure to click 'Save Changes' below before leaving this page.")}";
- CKAN.Strings.loading = "${_('Loading...')}";
- CKAN.Strings.noNameBrackets = "${_('(no name)')}";
- CKAN.Strings.deleteThisResourceQuestion = "${_('Delete the resource \'%name%\'?')}";
- CKAN.Strings.previewNotAvailableForDataType = "${_('Preview not available for data type: ')}";
- CKAN.Strings.failedToGetCredentialsForUpload = "${_('Failed to get credentials for storage upload. Upload cannot proceed')}";
- CKAN.Strings.checkingUploadPermissions = "${_('Checking upload permissions ...')}";
- CKAN.Strings.uploadingFile = "${_('Uploading file ...')}";
- CKAN.Strings.dataFile = "${_('Data File')}";
- CKAN.Strings.api = "${_('API')}";
- CKAN.Strings.visualization = "${_('Visualization')}";
- CKAN.Strings.image = "${_('Image')}";
- CKAN.Strings.metadata = "${_('Metadata')}";
- CKAN.Strings.documentation = "${_('Documentation')}";
- CKAN.Strings.code = "${_('Code')}";
- CKAN.Strings.example = "${_('Example')}";
+ CKAN.Strings = ${
+ h.json.dumps(dict(
+ checking = _('Checking...'),
+ urlIsTooShort = _('Type at least two characters...'),
+ urlIsUnchanged = _('This is the current URL.'),
+ urlIsAvailable = _('This URL is available!'),
+ urlIsNotAvailable = _('This URL is already used, please use a different one.'),
+ failedToSave = _('Failed to save, possibly due to invalid data '),
+ addDataset = _('Add Dataset'),
+ addGroup = _('Add Group'),
+ youHaveUnsavedChanges = _("You have unsaved changes. Make sure to click 'Save Changes' below before leaving this page."),
+ loading = _('Loading...'),
+ noNameBrackets = _('(no name)'),
+ deleteThisResourceQuestion = _('Delete the resource \'%name%\'?'),
+ previewNotAvailableForDataType = _('Preview not available for data type: '),
+ failedToGetCredentialsForUpload = _('Failed to get credentials for storage upload. Upload cannot proceed'),
+ checkingUploadPermissions = _('Checking upload permissions ...'),
+ uploadingFile = _('Uploading file ...'),
+ dataFile = _('Data File'),
+ api = _('API'),
+ visualization = _('Visualization'),
+ image = _('Image'),
+ metadata = _('Metadata'),
+ documentation = _('Documentation'),
+ code = _('Code'),
+ example = _('Example'),
+
+ upload = _('Upload'),
+ cancel = _('Cancel'),
+ name = _('Name'),
+ description = _('Description'),
+ url = _('Url'),
+ format = _('Format'),
+ resourceType = _('Resource Type'),
+ datastoreEnabled = _('DataStore enabled'),
+ sizeBracketsBytes = _('Size (Bytes)'),
+ mimetype = _('Mimetype'),
+ created = _('Created'),
+ lastModified = _('Last Modified'),
+ mimetypeInner = _('Mimetype (Inner)'),
+ hash = _('Hash'),
+ id = _('ID'),
+ doneEditing = _('Done'),
+ resourceHasUnsavedChanges = _('This resource has unsaved changes.'),
+ edit = _('Edit'),
+ preview = _('Preview'),
+ resourceFormatPlaceholder = _('e.g. csv, html, xls, rdf, ...'),
+ unknown = _('Unknown'),
+ extraFields = _('Extra Fields'),
+ addExtraField = _('Add Extra Field'),
+ deleteResource = _('Delete Resource'),
+ youCanUseMarkdown = _('You can use %aMarkdown formatting%b here.'),
+ shouldADataStoreBeEnabled = _('Should a %aDataStore table and Data API%b be enabled for this resource?'),
+ datesAreInISO = _('Dates are in %aISO Format%b — eg. %c2012-12-25%d or %c2010-05-31T14:30%d.'),
+ dataFileUploaded = _('Data File (Uploaded)')
+ ), indent=4)}
- /*
- * Used in templates.js.
- */
- CKAN.Strings.upload = "${_('Upload')}";
- CKAN.Strings.cancel = "${_('Cancel')}";
- CKAN.Strings.name = "${_('Name')}";
- CKAN.Strings.description = "${_('Description')}";
- CKAN.Strings.url = "${_('Url')}";
- CKAN.Strings.format = "${_('Format')}";
- CKAN.Strings.resourceType = "${_('Resource Type')}";
- CKAN.Strings.datastoreEnabled = "${_('DataStore enabled')}";
- CKAN.Strings.sizeBracketsBytes = "${_('Size (Bytes)')}";
- CKAN.Strings.mimetype = "${_('Mimetype')}";
- CKAN.Strings.created = "${_('Created')}";
- CKAN.Strings.lastModified = "${_('Last Modified')}";
- CKAN.Strings.mimetypeInner = "${_('Mimetype (Inner)')}";
- CKAN.Strings.hash = "${_('Hash')}";
- CKAN.Strings.id = "${_('ID')}";
- CKAN.Strings.doneEditing = "${_('Done')}";
- CKAN.Strings.resourceHasUnsavedChanges = "${_('This resource has unsaved changes.')}";
- CKAN.Strings.edit = "${_('Edit')}";
- CKAN.Strings.preview = "${_('Preview')}";
- CKAN.Strings.resourceFormatPlaceholder = "${_('e.g. csv, html, xls, rdf, ...')}";
- CKAN.Strings.unknown = "${_('Unknown')}";
- CKAN.Strings.extraFields = "${_('Extra Fields')}";
- CKAN.Strings.addExtraField = "${_('Add Extra Field')}";
- CKAN.Strings.deleteResource = "${_('Delete Resource')}";
- CKAN.Strings.youCanUseMarkdown = "${_('You can use %aMarkdown formatting%b here.')}";
- CKAN.Strings.shouldADataStoreBeEnabled = "${_('Should a %aDataStore table and Data API%b be enabled for this resource?')}";
- CKAN.Strings.datesAreInISO = "${_('Dates are in %aISO Format%b — eg. %c2012-12-25%d or %c2010-05-31T14:30%d.')}";
- CKAN.Strings.dataFileUploaded = "${_('Data File (Uploaded)')}";
</script>
diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html
index 5015093..3646d0f 100644
--- a/ckan/templates/package/layout.html
+++ b/ckan/templates/package/layout.html
@@ -34,18 +34,20 @@
</li>
</py:otherwise>
</py:choose>
- <li class="${'active' if c.action=='history' else ''}">${h.subnav_link(h.icon('page_stack') + _('History'), controller='package', action='history', id=c.pkg.name)}</li>
-
<li class="${'active' if c.action=='related' else ''}">${h.subnav_link(h.icon('package') + _('Related') + ' (%s)' % c.related_count, controller='related', action='list', id=c.pkg.name)}</li>
+
<py:if test="h.check_access('package_update',{'id':c.pkg.id})">
<li class="divider">|</li>
+ </py:if>
+ <li class="${'active' if c.action=='history' else ''}">${h.subnav_link(h.icon('page_stack') + _('History'), controller='package', action='history', id=c.pkg.name)}</li>
+
+ <py:if test="h.check_access('package_update',{'id':c.pkg.id})">
<li class="${'active' if c.action=='edit' else ''}">
${h.subnav_link(h.icon('package_edit') + _('Settings'), controller='package', action='edit', id=c.pkg.name)}
</li>
</py:if>
-
<li class="${'active' if c.action=='authz' else ''}" py:if="h.check_access('package_edit_permissions',{'id':c.pkg.id})">
${h.subnav_link(h.icon('lock') + _('Authorization'), controller='package', action='authz', id=c.pkg.name)}
</li>
diff --git a/ckanext/multilingual/plugin.py b/ckanext/multilingual/plugin.py
index a4696bd..c050f57 100644
--- a/ckanext/multilingual/plugin.py
+++ b/ckanext/multilingual/plugin.py
@@ -75,7 +75,7 @@ def translate_data_dict(data_dict):
translated_flattened[key] = fallback_translations.get(
value, value)
- elif isinstance(value, int):
+ elif isinstance(value, (int, dict)):
translated_flattened[key] = value
else:
diff --git a/ckanext/organizations/forms.py b/ckanext/organizations/forms.py
index c14e5e1..de60afb 100644
--- a/ckanext/organizations/forms.py
+++ b/ckanext/organizations/forms.py
@@ -221,9 +221,9 @@ def package_form(self):
def db_to_form_schema(self):
'''This is an interface to manipulate data from the database
into a format suitable for the form (optional)'''
- schema = default_package_schema()
- schema['groups']['capacity'] = [ ignore_missing, unicode ]
- return schema
+ #schema = default_package_schema()
+ #schema['groups']['capacity'] = [ ignore_missing, unicode ]
+ #return schema
def form_to_db_schema(self):
schema = default_package_schema()
diff --git a/ckanext/organizations/templates/organization_package_form.html b/ckanext/organizations/templates/organization_package_form.html
index 82a4404..e5cd0ec 100644
--- a/ckanext/organizations/templates/organization_package_form.html
+++ b/ckanext/organizations/templates/organization_package_form.html
@@ -143,6 +143,49 @@
<div class="instructions">
<p>Upload or link data files, APIs and other materials related to your dataset.</p>
</div>
+ <div class="row">
+ <div class="span4">
+ <ul class="resource-list resource-list-edit drag-drop-list">
+ </ul>
+ <ul class="resource-list resource-list-add">
+ <li><a href="#" class="js-resource-add">${h.icon('page_white_add')}New resource...</a></li>
+ </ul>
+ </div>
+ <div class="span8">
+ <div style="display: none;" class="resource-panel">
+ <button class="btn btn-danger resource-panel-close">x</button>
+ <div class="resource-details resource-add">
+ <ul class="nav nav-tabs">
+ <li><a data-toggle="tab" href="#link-file">Link to a file</a></li>
+ <li><a data-toggle="tab" href="#link-api">Link to an API</a></li>
+ <li><a data-toggle="tab" href="#upload-file">Upload a file</a></li>
+ </ul>
+ <div class="tab-content">
+ <div class="tab-pane" id="link-file">
+ <div class="form-inline js-add-url-form">
+ <label class="field_opt" for="url">File URL</label>
+ <input name="add-resource-url" type="text" class="input-small" placeholder="http://mydataset.com/file.csv"/>
+ <input name="add-resource-save" type="submit" class="btn btn-primary" value="Add" />
+ </div>
+ </div>
+ <div class="tab-pane" id="link-api">
+ <div class="form-inline js-add-api-form">
+ <label class="field_opt" for="url">API URL</label>
+ <input name="add-resource-url" type="text" class="input-small" placeholder="http://mydataset.com/api/"/>
+ <input name="add-resource-save" type="submit" class="btn btn-primary" value="Add" />
+ </div>
+ </div>
+ <div class="tab-pane" id="upload-file">
+ <div class="js-add-upload-form">
+ </div>
+ <div class="alert alert-block" style="display: none;"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
<div class="js-resource-edit-barebones">
<!-- The resource editor deletes these fields and replaces them with a dynamic editor.
They are required for the form to render correctly when not in resource-edit mode. -->
@@ -152,22 +195,6 @@
</py:for>
</py:for>
</div>
- <ul class="resource-list resource-list-edit drag-drop-list">
- </ul>
- <ul class="resource-list resource-list-add">
- <li><a href="#" class="js-resource-add">${h.icon('page_white_add')}New resource...</a></li>
- </ul>
- <div style="display: none;" class="resource-panel">
- <button class="btn btn-danger resource-panel-close">x</button>
- <div class="resource-details resource-add">
- <ul class="button-row">
- <li><h4>Add a resource:</h4></li>
- <li><button class="btn js-link-file">Link to a file</button></li>
- <li><button class="btn js-link-api">Link to an API</button></li>
- <li class="js-upload-file ckan-logged-in" style="display: none;"><button class="btn js-upload-file">Upload a file</button></li>
- </ul>
- </div>
- </div>
</fieldset>
<fieldset class="tab-pane fade" id='further-information'>
diff --git a/doc/solr-setup.rst b/doc/solr-setup.rst
index 3c158b2..395da67 100644
--- a/doc/solr-setup.rst
+++ b/doc/solr-setup.rst
@@ -50,13 +50,12 @@ and the admin site::
http://localhost:8983/solr/admin
-.. note:: If you get the message `Could not start Jetty servlet engine because no Java Development Kit (JDK) was found.` then you will have to edit /etc/profile and add this line to the end such as this to the end (adjusting the path for your machine's jdk install):
+.. note:: If you get the message ``Could not start Jetty servlet engine because no Java Development Kit (JDK) was found.`` then you will have to edit the ``JAVA_HOME`` setting in ``/etc/default/jetty`` (adjusting the path for your machine's JDK install):
``JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64/``
Now run::
- export JAVA_HOME
sudo service jetty start
================================================================
Commit: ded67ed21753d2d2d1a123b2325a1296080ce08b
https://github.com/okfn/ckan/commit/ded67ed21753d2d2d1a123b2325a1296080ce08b
Author: Toby <toby.junk at gmail.com>
Date: 2012-04-30 (Mon, 30 Apr 2012)
Changed paths:
M ckan/templates/facets.html
Log Message:
-----------
facets.html make comments template only
diff --git a/ckan/templates/facets.html b/ckan/templates/facets.html
index f8c104b..001be8c 100644
--- a/ckan/templates/facets.html
+++ b/ckan/templates/facets.html
@@ -5,7 +5,7 @@
py:strip=""
>
-<!--
+<!--!
Construct a facet <div> populated with links to filtered results.
name
@@ -40,7 +40,7 @@
</div>
</py:def>
-<!--
+<!--!
Generate <li>s for facet items. The generated tags are not wrapped by any
other tag, ie - it's up to the caller to wrap them in something suitable.
@@ -75,7 +75,7 @@
</li>
</py:def>
-<!--
+<!--!
DEPRECATED. Provided only for backward compatibility with existing plugins.
Use `facet_div` instead.
@@ -129,7 +129,7 @@
</div>
</py:def>
-<!--
+<!--!
DEPRECATED. Provided only for backward compatibility with existing plugins.
Use `facet_li` instead.
================================================================
Compare: https://github.com/okfn/ckan/compare/681d544...ded67ed
More information about the ckan-changes
mailing list