diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c97cfe438de..54c4515c370 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,25 @@ Changelog --------- +v2.4.8 2017-08-02 +================= + +* Fix in organization / group form image URL field (#3661) +* Fix activity test to use utcnow (#3644) +* Changed required permission from 'update' to 'manage_group' (#3631) +* Catch invalid sort param exception (#3630) +* Choose direction of recreated package relationship depending on its type (#3626) +* Fix render_datetime for dates before year 1900 (#3611) +* Fix KeyError in 'package_create' (#3027) +* Allow slug preview to work with autocomplete fields (#2501) +* Fix filter results button not working for organization/group (#3620) +* Allow underscores in URL slug preview on create dataset (#3612) +* Create new resource view if resource format changed (#3515) +* Fixed incorrect escaping in `mail_to` +* Autocomplete fields are more responsive - 300ms timeout instead of 1s (#3693) +* Fixed dataset count display for groups (#3711) +* Restrict access to form pages (#3684) + v2.4.7 2017-03-22 ================= diff --git a/ckan/__init__.py b/ckan/__init__.py index 31137a9bcc6..b2fc98b4f90 100644 --- a/ckan/__init__.py +++ b/ckan/__init__.py @@ -1,4 +1,4 @@ -__version__ = '2.4.7' +__version__ = '2.4.8' __description__ = 'CKAN Software' __long_description__ = \ diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index 345544d1e94..a043a617fe5 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -170,14 +170,24 @@ def index(self): context['user_id'] = c.userobj.id context['user_is_admin'] = c.userobj.sysadmin - data_dict_global_results = { - 'all_fields': False, - 'q': q, - 'sort': sort_by, - 'type': group_type or 'group', - } - global_results = self._action('group_list')(context, - data_dict_global_results) + try: + data_dict_global_results = { + 'all_fields': False, + 'q': q, + 'sort': sort_by, + 'type': group_type or 'group', + } + global_results = self._action('group_list')( + context, data_dict_global_results) + except ValidationError as e: + if e.error_dict and e.error_dict.get('message'): + msg = e.error_dict['message'] + else: + msg = str(e) + h.flash_error(msg) + c.page = h.Page([], 0) + return render(self._index_template(group_type), + extra_vars={'group_type': group_type}) data_dict_page_results = { 'all_fields': True, @@ -405,6 +415,7 @@ def bulk_process(self, id): data_dict = {'id': id} try: + self._check_access('bulk_update_public', context, {'org_id': id}) # Do not query for the group datasets when dictizing, as they will # be ignored and get requested on the controller anyway data_dict['include_datasets'] = False @@ -413,7 +424,7 @@ def bulk_process(self, id): except NotFound: abort(404, _('Group not found')) except NotAuthorized: - abort(401, _('Unauthorized to read group %s') % id) + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) #use different form names so that ie7 can be detected form_names = set(["bulk_action.public", "bulk_action.delete", @@ -673,16 +684,21 @@ def members(self, id): 'user': c.user or c.author} try: + data_dict = {'id': id} + check_access('group_edit_permissions', context, data_dict) c.members = self._action('member_list')( context, {'id': id, 'object_type': 'user'} ) - data_dict = {'id': id} data_dict['include_datasets'] = False c.group_dict = self._action('group_show')(context, data_dict) - except NotAuthorized: - abort(401, _('Unauthorized to delete group %s') % '') except NotFound: abort(404, _('Group not found')) + except NotAuthorized: + abort( + 403, + _('User %r not authorized to edit members of %s') % ( + c.user, id)) + return self._render_template('group/members.html', group_type) def member_new(self, id): @@ -691,7 +707,11 @@ def member_new(self, id): context = {'model': model, 'session': model.Session, 'user': c.user or c.author} - #self._check_access('group_delete', context, {'id': id}) + try: + self._check_access('group_member_create', context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to create group %s members') % '') + try: data_dict = {'id': id} data_dict['include_datasets'] = False diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index e2f4ba23f29..bf9d459eaec 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -583,6 +583,15 @@ def new(self, data=None, errors=None, error_summary=None): def resource_edit(self, id, resource_id, data=None, errors=None, error_summary=None): + context = {'model': model, 'session': model.Session, + 'api_version': 3, 'for_edit': True, + 'user': c.user, 'auth_user_obj': c.userobj} + data_dict = {'id': id} + + try: + check_access('package_update', context, data_dict) + except NotAuthorized: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) if request.method == 'POST' and not data: data = data or clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( @@ -590,10 +599,6 @@ def resource_edit(self, id, resource_id, data=None, errors=None, # we don't want to include save as it is part of the form del data['save'] - context = {'model': model, 'session': model.Session, - 'api_version': 3, 'for_edit': True, - 'user': c.user or c.author, 'auth_user_obj': c.userobj} - data['package_id'] = id try: if resource_id: @@ -610,10 +615,6 @@ def resource_edit(self, id, resource_id, data=None, errors=None, abort(401, _('Unauthorized to edit this resource')) redirect(h.url_for(controller='package', action='resource_read', id=id, resource_id=resource_id)) - - context = {'model': model, 'session': model.Session, - 'api_version': 3, 'for_edit': True, - 'user': c.user or c.author, 'auth_user_obj': c.userobj} pkg_dict = get_action('package_show')(context, {'id': id}) if pkg_dict['state'].startswith('draft'): # dataset has not yet been fully created diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index ba6ee213798..30a0aa022bb 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -17,8 +17,7 @@ from urllib import urlencode from paste.deploy.converters import asbool -from webhelpers.html import escape, HTML, literal, url_escape -from webhelpers.html.tools import mail_to +from webhelpers.html import HTML, literal, url_escape from webhelpers.html.tags import * from lib.markdown import markdown from webhelpers import paginate @@ -45,6 +44,7 @@ from ckan.common import ( _, ungettext, g, c, request, session, json, OrderedDict ) +from markupsafe import Markup, escape get_available_locales = i18n.get_available_locales get_locales_dict = i18n.get_locales_dict @@ -116,7 +116,7 @@ def get_site_protocol_and_host(): If `ckan.site_url` is set like this:: ckan.site_url = http://example.com - + Then this function would return a tuple `('http', 'example.com')` If the setting is missing, `(None, None)` is returned instead. @@ -307,6 +307,11 @@ def full_current_url(): return (url_for(request.environ['CKAN_CURRENT_URL'], qualified=True)) +def current_url(): + ''' Returns current url unquoted''' + return urllib.unquote(request.environ['CKAN_CURRENT_URL']) + + def lang(): ''' Return the language code for the current locale eg `en` ''' return request.environ.get('CKAN_LANG') @@ -994,6 +999,24 @@ def render_datetime(datetime_, date_format=None, with_hours=False): return '' # if date_format was supplied we use it if date_format: + + # See http://bugs.python.org/issue1777412 + if datetime_.year < 1900: + year = str(datetime_.year) + + date_format = re.sub('(?{year}'.format(year=year[-2:]), + date_format) + date_format = re.sub('(?{year}'.format(year=year), + date_format) + + datetime_ = datetime.datetime(2016, datetime_.month, datetime_.day, + datetime_.hour, datetime_.minute, + datetime_.second) + + return datetime_.strftime(date_format) + return datetime_.strftime(date_format) # the localised date return formatters.localised_nice_date(datetime_, show_date=True, @@ -2076,6 +2099,13 @@ def license_options(existing_license_id=None): for license_id in license_ids] +def mail_to(email_address, name): + email = escape(email_address) + author = escape(name) + html = Markup(u'{1}'.format(email, author)) + return html + + # these are the functions that will end up in `h` template helpers __allowed_functions__ = [ # functions defined in ckan.lib.helpers @@ -2137,6 +2167,7 @@ def license_options(existing_license_id=None): 'debug_inspect', 'dict_list_reduce', 'full_current_url', + 'current_url', 'popular', 'debug_full_info_as_list', 'get_facet_title', diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py index 3e1cb635003..5f8714402c7 100644 --- a/ckan/logic/action/create.py +++ b/ckan/logic/action/create.py @@ -306,8 +306,10 @@ def resource_create(context, data_dict): _get_action('package_update')(context, pkg_dict) context.pop('defer_commit') except ValidationError, e: - errors = e.error_dict['resources'][-1] - raise ValidationError(errors) + try: + raise ValidationError(e.error_dict['resources'][-1]) + except (KeyError, IndexError): + raise ValidationError(e.error_dict) ## Get out resource_id resource from model as it will not appear in ## package_show until after commit diff --git a/ckan/logic/action/update.py b/ckan/logic/action/update.py index 4902530850f..5dd76f084d5 100644 --- a/ckan/logic/action/update.py +++ b/ckan/logic/action/update.py @@ -127,6 +127,7 @@ def resource_update(context, data_dict): resource = model.Resource.get(id) context["resource"] = resource + old_resource_format = resource.format if not resource: log.error('Could not find resource ' + id) @@ -158,14 +159,23 @@ def resource_update(context, data_dict): updated_pkg_dict = _get_action('package_update')(context, pkg_dict) context.pop('defer_commit') except ValidationError, e: - errors = e.error_dict['resources'][n] - raise ValidationError(errors) + try: + raise ValidationError(e.error_dict['resources'][-1]) + except (KeyError, IndexError): + raise ValidationError(e.error_dict) upload.upload(id, uploader.get_max_resource_size()) model.repo.commit() resource = _get_action('resource_show')(context, {'id': id}) + if old_resource_format != resource['format']: + _get_action('resource_create_default_resource_views')( + {'model': context['model'], 'user': context['user'], + 'ignore_auth': True}, + {'package': updated_pkg_dict, + 'resource': resource}) + for plugin in plugins.PluginImplementations(plugins.IResourceController): plugin.after_update(context, resource) diff --git a/ckan/logic/auth/create.py b/ckan/logic/auth/create.py index 581e192bc0b..4b8167e9baf 100644 --- a/ckan/logic/auth/create.py +++ b/ckan/logic/auth/create.py @@ -211,7 +211,7 @@ def _check_group_auth(context, data_dict): groups = groups - set(pkg_groups) for group in groups: - if not authz.has_user_permission_for_group_or_org(group.id, user, 'update'): + if not authz.has_user_permission_for_group_or_org(group.id, user, 'manage_group'): return False return True diff --git a/ckan/logic/auth/update.py b/ckan/logic/auth/update.py index ff527da0f92..c6aff43d94a 100644 --- a/ckan/logic/auth/update.py +++ b/ckan/logic/auth/update.py @@ -173,14 +173,15 @@ def group_edit_permissions(context, data_dict): user = context['user'] group = logic_auth.get_group_object(context, data_dict) - authorized = authz.has_user_permission_for_group_or_org(group.id, - user, - 'update') + authorized = authz.has_user_permission_for_group_or_org( + group.id, user, 'update') if not authorized: - return {'success': False, - 'msg': _('User %s not authorized to edit permissions of group %s') % - (str(user), group.id)} + return { + 'success': False, + 'msg': _('User %s not authorized to' + ' edit permissions of group %s') % + (str(user), group.id)} else: return {'success': True} diff --git a/ckan/model/package.py b/ckan/model/package.py index 9bd7d695c6e..35873401b04 100644 --- a/ckan/model/package.py +++ b/ckan/model/package.py @@ -227,16 +227,18 @@ def add_relationship(self, type_, related_package, comment=u''): if type_ in package_relationship.PackageRelationship.get_forward_types(): subject = self object_ = related_package + direction = "forward" elif type_ in package_relationship.PackageRelationship.get_reverse_types(): type_ = package_relationship.PackageRelationship.reverse_to_forward_type(type_) assert type_ subject = related_package object_ = self + direction = "reverse" else: raise KeyError, 'Package relationship type: %r' % type_ rels = self.get_relationships(with_package=related_package, - type=type_, active=False, direction="forward") + type=type_, active=False, direction=direction) if rels: rel = rels[0] if comment: diff --git a/ckan/model/user.py b/ckan/model/user.py index 2d2a37f4348..c75e2d6cd93 100644 --- a/ckan/model/user.py +++ b/ckan/model/user.py @@ -53,13 +53,8 @@ def by_email(cls, email): @classmethod def get(cls, user_reference): - # double slashes in an openid often get turned into single slashes - # by browsers, so correct that for the openid lookup - corrected_openid_user_ref = cls.DOUBLE_SLASH.sub('://\\1', - user_reference) query = meta.Session.query(cls).autoflush(False) query = query.filter(or_(cls.name == user_reference, - cls.openid == corrected_openid_user_ref, cls.id == user_reference)) return query.first() diff --git a/ckan/public/base/javascript/modules/autocomplete.js b/ckan/public/base/javascript/modules/autocomplete.js index ed7c676051f..a7b292d3c07 100644 --- a/ckan/public/base/javascript/modules/autocomplete.js +++ b/ckan/public/base/javascript/modules/autocomplete.js @@ -24,7 +24,7 @@ this.ckan.module('autocomplete', function (jQuery, _) { label: false, items: 10, source: null, - interval: 1000, + interval: 300, dropdownClass: '', containerClass: '', i18n: { diff --git a/ckan/public/base/javascript/modules/autocomplete.min.js b/ckan/public/base/javascript/modules/autocomplete.min.js index b6321b6976f..ba3d7dbc93d 100644 --- a/ckan/public/base/javascript/modules/autocomplete.min.js +++ b/ckan/public/base/javascript/modules/autocomplete.min.js @@ -1,4 +1,4 @@ -this.ckan.module('autocomplete',function(jQuery,_){return{options:{tags:false,key:false,label:false,items:10,source:null,interval:1000,dropdownClass:'',containerClass:'',i18n:{noMatches:_('No matches found'),emptySearch:_('Start typing…'),inputTooShort:function(data){return _('Input is too short, must be at least one character').ifPlural(data.min,'Input is too short, must be at least %(min)d characters');}}},initialize:function(){jQuery.proxyAll(this,/_on/,/format/);this.setupAutoComplete();},setupAutoComplete:function(){var settings={width:'resolve',formatResult:this.formatResult,formatNoMatches:this.formatNoMatches,formatInputTooShort:this.formatInputTooShort,dropdownCssClass:this.options.dropdownClass,containerCssClass:this.options.containerClass};if(!this.el.is('select')){if(this.options.tags){settings.tags=this._onQuery;}else{settings.query=this._onQuery;settings.createSearchChoice=this.formatTerm;} +this.ckan.module('autocomplete',function(jQuery,_){return{options:{tags:false,key:false,label:false,items:10,source:null,interval:300,dropdownClass:'',containerClass:'',i18n:{noMatches:_('No matches found'),emptySearch:_('Start typing…'),inputTooShort:function(data){return _('Input is too short, must be at least one character').ifPlural(data.min,'Input is too short, must be at least %(min)d characters');}}},initialize:function(){jQuery.proxyAll(this,/_on/,/format/);this.setupAutoComplete();},setupAutoComplete:function(){var settings={width:'resolve',formatResult:this.formatResult,formatNoMatches:this.formatNoMatches,formatInputTooShort:this.formatInputTooShort,dropdownCssClass:this.options.dropdownClass,containerCssClass:this.options.containerClass};if(!this.el.is('select')){if(this.options.tags){settings.tags=this._onQuery;}else{settings.query=this._onQuery;settings.createSearchChoice=this.formatTerm;} settings.initSelection=this.formatInitialValue;} else{if(/MSIE (\d+\.\d+);/.test(navigator.userAgent)){var ieversion=new Number(RegExp.$1);if(ieversion<=7){return}}} var select2=this.el.select2(settings).data('select2');if(this.options.tags&&select2&&select2.search){select2.search.on('keydown',this._onKeydown);} diff --git a/ckan/public/base/javascript/modules/slug-preview.js b/ckan/public/base/javascript/modules/slug-preview.js index 63fb08d94e7..c82688cfb20 100644 --- a/ckan/public/base/javascript/modules/slug-preview.js +++ b/ckan/public/base/javascript/modules/slug-preview.js @@ -18,7 +18,7 @@ this.ckan.module('slug-preview-target', { // Watch for updates to the target field and update the hidden slug field // triggering the "change" event manually. - el.on('keyup.slug-preview', function (event) { + el.on('keyup.slug-preview input.slug-preview', function (event) { sandbox.publish('slug-target-changed', this.value); //slug.val(this.value).trigger('change'); }); diff --git a/ckan/public/base/javascript/modules/slug-preview.min.js b/ckan/public/base/javascript/modules/slug-preview.min.js index c9bb6f77f47..f7bb91ccd3a 100644 --- a/ckan/public/base/javascript/modules/slug-preview.min.js +++ b/ckan/public/base/javascript/modules/slug-preview.min.js @@ -1,3 +1,3 @@ -this.ckan.module('slug-preview-target',{initialize:function(){var sandbox=this.sandbox;var options=this.options;var el=this.el;sandbox.subscribe('slug-preview-created',function(preview){el.after(preview);});if(el.val()==''){sandbox.subscribe('slug-preview-modified',function(){el.off('.slug-preview');});el.on('keyup.slug-preview',function(event){sandbox.publish('slug-target-changed',this.value);});}}});this.ckan.module('slug-preview-slug',function(jQuery,_){return{options:{prefix:'',placeholder:'',i18n:{url:_('URL'),edit:_('Edit')}},initialize:function(){var sandbox=this.sandbox;var options=this.options;var el=this.el;var _=sandbox.translate;var slug=el.slug();var parent=slug.parents('.control-group');var preview;if(!(parent.length)){return;} +this.ckan.module('slug-preview-target',{initialize:function(){var sandbox=this.sandbox;var options=this.options;var el=this.el;sandbox.subscribe('slug-preview-created',function(preview){el.after(preview);});if(el.val()==''){sandbox.subscribe('slug-preview-modified',function(){el.off('.slug-preview');});el.on('keyup.slug-preview input.slug-preview',function(event){sandbox.publish('slug-target-changed',this.value);});}}});this.ckan.module('slug-preview-slug',function(jQuery,_){return{options:{prefix:'',placeholder:'',i18n:{url:_('URL'),edit:_('Edit')}},initialize:function(){var sandbox=this.sandbox;var options=this.options;var el=this.el;var _=sandbox.translate;var slug=el.slug();var parent=slug.parents('.control-group');var preview;if(!(parent.length)){return;} if(!parent.hasClass('error')){preview=parent.slugPreview({prefix:options.prefix,placeholder:options.placeholder,i18n:{'URL':this.i18n('url'),'Edit':this.i18n('edit')}});slug.keypress(function(){if(event.charCode){sandbox.publish('slug-preview-modified',preview[0]);}});sandbox.publish('slug-preview-created',preview[0]);if(jQuery('html').hasClass('ie7')){jQuery('.btn').on('click',preview,function(){jQuery('.controls').ie7redraw();});preview.hide();setTimeout(function(){preview.show();jQuery('.controls').ie7redraw();},10);}} sandbox.subscribe('slug-target-changed',function(value){slug.val(value).trigger('change');});}};}); \ No newline at end of file diff --git a/ckan/public/base/javascript/plugins/jquery.url-helpers.js b/ckan/public/base/javascript/plugins/jquery.url-helpers.js index 6ff9e6e4c7c..f92d62923da 100644 --- a/ckan/public/base/javascript/plugins/jquery.url-helpers.js +++ b/ckan/public/base/javascript/plugins/jquery.url-helpers.js @@ -119,14 +119,14 @@ '4f0 4f1 4f2 4f3 4f4 4f5 4f6 4f7 4f8 4f9 4fa 4fb 4fc 4fd 4fe 4ff ' + '50a 50b 50c 50d 50e 50f 51a 51b 51c 51d 53a 53b 53c 53d 53e 53f ' + '54a 54b 54c 54d 54e 54f 56a 56b 56c 56d 56e 56f 57a 57b 57c 57d ' + - '57e 57f').split(' '); + '57e 57f 5f').split(' '); var replacement = ('- 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I P Q R S T ' + 'U V W X Y a b c d e f g h i p q r s t u v w x y A a A a A a C c C c ' + 'D d E e E e E e E e G g G g H h H h I i I i IJ ij J j K k k L l L l ' + 'N n N n N n n O o OE oe R r R r R r S s T t T t T t U u U u U u W w ' + - 'Y y Y Z b B b b b b C C c D E F f G Y h i I K k A a A a E e E e I i ' + - 'R r R r U u U u S s n d 8 8 Z z A a E e O o Y y l n t j db qp < ? ? ' + + 'Y y Y Z b B b b b b C C c D E F f G Y h i I K k A a A a E e E e I i ' + + 'R r R r U u U u S s n d 8 8 Z z A a E e O o Y y l n t j db qp < ? ? ' + 'B U A E e J j a a a b c e d d e e g g g Y x u h h i i w m n n N o oe ' + 'm o r R R S f f f f t t u Z Z 3 3 ? ? 5 C O B a e i o u c d A ' + 'E H i A B r A E Z H O I E E T r E S I I J jb A B B r D E X 3 N N P ' + @@ -154,7 +154,7 @@ 'h H h E e E e I X x K k jt jt H h H h H h M m l A a A a AE ae E e ' + 'e e E e X X 3 3 3 3 N n N n O o O o O o E e Y y Y y Y y H h R r bI ' + 'bi F f X x X x H h G g T t Q q W w d r L Iu O y m o N U Y S d h l ' + - 'lu d y w 2 n u y un').split(' '); + 'lu d y w 2 n u y un _').split(' '); // Map the Unicode characters to their counterparts in an object. var map = {}; diff --git a/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js b/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js index bb194ff1d71..904a62a40ef 100644 --- a/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js +++ b/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js @@ -1,3 +1,3 @@ (function($,window){$.url={escape:function(string){return window.encodeURIComponent(string||'').replace(/%20/g,'+');},slugify:function(string,trim){var str='';var index=0;var length=string.length;var map=this.map;for(;index=?#/');assert.equal(target,'%26%3C%3E%3D%3F%23%2F');});it('should convert spaces to + rather than %20',function(){var target=jQuery.url.escape(' ');assert.equal(target,'+');});});describe('.slugify()',function(){it('should replace spaces with hyphens',function(){var target=jQuery.url.slugify('apples and pears');assert.equal(target,'apples-and-pears');});it('should lowecase all characters',function(){var target=jQuery.url.slugify('APPLES AND PEARS');assert.equal(target,'apples-and-pears');});it('should convert unknown characters to hyphens',function(){var target=jQuery.url.slugify('apples & pears');assert.equal(target,'apples-pears');});it('should nomalise hyphens',function(){var target=jQuery.url.slugify('apples---pears');assert.equal(target,'apples-pears','remove duplicate hyphens');target=jQuery.url.slugify('--apples-pears');assert.equal(target,'apples-pears','strip preceding hyphens');target=jQuery.url.slugify('apples-pears--');assert.equal(target,'apples-pears','strip trailing hyphens');});it('should try and asciify unicode characters',function(){var target=jQuery.url.slugify('éåøç');assert.equal(target,'eaoc');});});}); \ No newline at end of file +describe('jQuery.url',function(){describe('.escape()',function(){it('should escape special characters',function(){var target=jQuery.url.escape('&<>=?#/');assert.equal(target,'%26%3C%3E%3D%3F%23%2F');});it('should convert spaces to + rather than %20',function(){var target=jQuery.url.escape(' ');assert.equal(target,'+');});});describe('.slugify()',function(){it('should replace spaces with hyphens',function(){var target=jQuery.url.slugify('apples and pears');assert.equal(target,'apples-and-pears');});it('should lowecase all characters',function(){var target=jQuery.url.slugify('APPLES AND PEARS');assert.equal(target,'apples-and-pears');});it('should convert unknown characters to hyphens',function(){var target=jQuery.url.slugify('apples & pears');assert.equal(target,'apples-pears');});it('should nomalise hyphens',function(){var target=jQuery.url.slugify('apples---pears');assert.equal(target,'apples-pears','remove duplicate hyphens');target=jQuery.url.slugify('--apples-pears');assert.equal(target,'apples-pears','strip preceding hyphens');target=jQuery.url.slugify('apples-pears--');assert.equal(target,'apples-pears','strip trailing hyphens');});it('should try and asciify unicode characters',function(){var target=jQuery.url.slugify('éåøç');assert.equal(target,'eaoc');});it('should allow underscore characters',function(){var target=jQuery.url.slugify('apples_pears');assert.equal(target,'apples_pears');});});}); \ No newline at end of file diff --git a/ckan/templates/group/read.html b/ckan/templates/group/read.html index 865d13839bc..5abca7e114f 100644 --- a/ckan/templates/group/read.html +++ b/ckan/templates/group/read.html @@ -32,7 +32,12 @@ {% block secondary_content %} {{ super() }} - {% for facet in c.facet_titles %} - {{ h.snippet('snippets/facet_list.html', title=c.facet_titles[facet], name=facet, extras={'id':c.group_dict.id}) }} - {% endfor %} +
+
+ {% for facet in c.facet_titles %} + {{ h.snippet('snippets/facet_list.html', title=c.facet_titles[facet], name=facet, extras={'id':c.group_dict.id}) }} + {% endfor %} +
+ close +
{% endblock %} diff --git a/ckan/templates/group/snippets/group_item.html b/ckan/templates/group/snippets/group_item.html index ae69eb707c7..407f631825a 100644 --- a/ckan/templates/group/snippets/group_item.html +++ b/ckan/templates/group/snippets/group_item.html @@ -28,9 +28,9 @@

{{ group.display_name }}

{% endif %} {% endblock %} {% block datasets %} - {% if group.packages %} - {{ ungettext('{num} Dataset', '{num} Datasets', group.packages).format(num=group.packages) }} - {% elif group.packages == 0 %} + {% if group.package_count %} + {{ ungettext('{num} Dataset', '{num} Datasets', group.package_count).format(num=group.package_count) }} + {% elif group.package_count == 0 %} {{ _('0 Datasets') }} {% endif %} {% endblock %} diff --git a/ckan/templates/macros/form.html b/ckan/templates/macros/form.html index 934e1c9ba93..f434a43c1a0 100644 --- a/ckan/templates/macros/form.html +++ b/ckan/templates/macros/form.html @@ -118,7 +118,7 @@ {% macro markdown(name, id='', label='', value='', placeholder='', error="", classes=[], attrs={}, is_required=false) %} {% set classes = (classes|list) %} {% do classes.append('control-full') %} - {% set markdown_tooltip = "

__Bold text__ or _italic text_

# title
## secondary title
### etc

* list
* of
* items

http://auto.link.ed/

Full markdown syntax

Please note: HTML tags are stripped out for security reasons

" %} + {% set markdown_tooltip = "

__Bold text__ or _italic text_

# title
## secondary title
### etc

* list
* of
* items

http://auto.link.ed/

Full markdown syntax

Please note: HTML tags are stripped out for security reasons

" %} {%- set extra_html = caller() if caller -%} {% call input_block(id or name, label or name, error, classes, control_classes=["editor"], extra_html=extra_html, is_required=is_required) %} @@ -406,14 +406,14 @@ #} {% macro image_upload(data, errors, field_url='image_url', field_upload='image_upload', field_clear='clear_upload', is_url=false, is_upload=false, is_upload_enabled=false, placeholder=false, - url_label='', upload_label='') %} + url_label='', upload_label='', field_name='image_url') %} {% set placeholder = placeholder if placeholder else _('http://example.com/my-image.jpg') %} {% set url_label = url_label or _('Image URL') %} {% set upload_label = upload_label or _('Image') %} {% if is_upload_enabled %}
+ data-module-field_url="{{ field_url }}" data-module-field_upload="{{ field_upload }}" data-module-field_clear="{{ field_clear }}" data-module-upload_label="{{ upload_label }}" data-module-field_name="{{ field_name }}"> {% endif %} {{ input(field_url, label=url_label, id='field-image-url', placeholder=placeholder, value=data.get(field_url), error=errors.get(field_url), classes=['control-full']) }} diff --git a/ckan/templates/organization/read.html b/ckan/templates/organization/read.html index a0c08735a23..ecd1c4341a1 100644 --- a/ckan/templates/organization/read.html +++ b/ckan/templates/organization/read.html @@ -35,7 +35,12 @@ {% endblock %} {% block organization_facets %} - {% for facet in c.facet_titles %} - {{ h.snippet('snippets/facet_list.html', title=c.facet_titles[facet], name=facet, extras={'id':c.group_dict.id}) }} - {% endfor %} +
+
+ {% for facet in c.facet_titles %} + {{ h.snippet('snippets/facet_list.html', title=c.facet_titles[facet], name=facet, extras={'id':c.group_dict.id}) }} + {% endfor %} +
+ close +
{% endblock %} diff --git a/ckan/templates/snippets/language_selector.html b/ckan/templates/snippets/language_selector.html index dfe62c1686a..fa48b643ce2 100644 --- a/ckan/templates/snippets/language_selector.html +++ b/ckan/templates/snippets/language_selector.html @@ -1,10 +1,9 @@ -{% set current_url = request.environ.CKAN_CURRENT_URL %} {% set current_lang = request.environ.CKAN_LANG %}