From cc115ef5ba1d476572a6f795a3ec25c3fd9f32b0 Mon Sep 17 00:00:00 2001 From: Ian Murray Date: Fri, 10 Feb 2012 14:58:18 +0000 Subject: [PATCH 01/84] [#1751] Provide feedback on the tabs containing errors in the dataset-edit form --- ckan/controllers/package.py | 1 + ckan/public/css/forms.css | 3 +++ ckan/public/css/style.css | 1 + ckan/public/scripts/application.js | 9 +++++++++ ckan/templates/package/edit.html | 11 ++++++----- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index 961661d1a7b..e8c9037e840 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -501,6 +501,7 @@ def edit(self, id, data=None, errors=None, error_summary=None): errors = errors or {} vars = {'data': data, 'errors': errors, 'error_summary': error_summary} + c.errors_json = json.dumps(errors) self._setup_template_variables(context, {'id': id}, package_type=package_type) diff --git a/ckan/public/css/forms.css b/ckan/public/css/forms.css index 97406579d9f..670e8f9ec1a 100644 --- a/ckan/public/css/forms.css +++ b/ckan/public/css/forms.css @@ -139,6 +139,9 @@ form.has-errors .field_error, form.has-errors .error-explanation { position: relative; background: transparent url(../images/icons/error.png) left 3px no-repeat; } +.fieldset_button_error { + background: transparent url(../images/icons/error.png) left center no-repeat; } + .error-explanation, #errorExplanation { background: #fff; diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 33640224e24..c249424238e 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -971,6 +971,7 @@ ul.dataset-edit-nav li a { display: block; padding: 7px 0 7px 10px; margin-bottom: 7px; + margin-left: 20px; border: 1px transparent solid; } ul.dataset-edit-nav li a.active, diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 61c03ec5f67..1d6ba479054 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -93,6 +93,15 @@ $(e.target).attr('disabled','disabled'); return false; }); + + // Highlight form errors in the tab buttons + for (field_id in form_errors) { + var field = $('#'+field_id); + if (field !== undefined) { + var fieldset_id = field.parents('fieldset').last().attr('id'); + $('#section-'+fieldset_id).addClass('fieldset_button_error'); + } + } } var isGroupEdit = $('body.group.edit').length > 0; if (isGroupEdit) { diff --git a/ckan/templates/package/edit.html b/ckan/templates/package/edit.html index 6bd9a39735c..0984f281985 100644 --- a/ckan/templates/package/edit.html +++ b/ckan/templates/package/edit.html @@ -11,6 +11,7 @@ @@ -18,11 +19,11 @@
  • From a900ecb2c95a35eb6038fa40a8a2d5f66bcaa6ea Mon Sep 17 00:00:00 2001 From: Ian Murray Date: Mon, 13 Feb 2012 12:58:45 +0000 Subject: [PATCH 02/84] [#1607][dataset form] Added the errors_json to the package-new form. Not really applicable to a standard CKAN install, but useful for extensions that may provide their own dataset-new forms --- ckan/controllers/package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index e8c9037e840..5baebd38104 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -455,6 +455,7 @@ def new(self, data=None, errors=None, error_summary=None): errors = errors or {} error_summary = error_summary or {} vars = {'data': data, 'errors': errors, 'error_summary': error_summary} + c.errors_json = json.dumps(errors) self._setup_template_variables(context, {'id': id}) From e30156552a998749da58b4fa73bc27e1910635f0 Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Wed, 15 Feb 2012 15:21:34 +0000 Subject: [PATCH 03/84] Whitespace cleanup (ahem) and fixing issues with group members --- ckan/lib/dictization/model_save.py | 44 +++++++++++++++--------------- ckan/model/group.py | 7 +++-- ckan/model/user.py | 26 +++++++++--------- ckan/public/scripts/application.js | 3 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ckan/lib/dictization/model_save.py b/ckan/lib/dictization/model_save.py index 47a46b8b5a4..2d37807a058 100644 --- a/ckan/lib/dictization/model_save.py +++ b/ckan/lib/dictization/model_save.py @@ -8,7 +8,7 @@ def resource_dict_save(res_dict, context): model = context["model"] session = context["session"] trigger_url_change = False - + id = res_dict.get("id") obj = None if id: @@ -21,7 +21,7 @@ def resource_dict_save(res_dict, context): table = class_mapper(model.Resource).mapped_table fields = [field.name for field in table.c] - + for key, value in res_dict.iteritems(): if isinstance(value, list): continue @@ -68,7 +68,7 @@ def package_resource_list_save(res_dicts, package, context): else: resource.state = 'deleted' resource_list.append(resource) - tag_package_tag = dict((package_tag.tag, package_tag) + tag_package_tag = dict((package_tag.tag, package_tag) for package_tag in package.package_tag_all) @@ -89,7 +89,7 @@ def package_extras_save(extra_dicts, obj, context): for extra_dict in extra_dicts: if extra_dict.get("deleted"): continue - + if extra_dict['value'] is None: pass elif extras_as_string: @@ -138,7 +138,7 @@ def group_extras_save(extras_dicts, context): return result_dict def package_tag_list_save(tag_dicts, package, context): - + allow_partial_update = context.get("allow_partial_update", False) if not tag_dicts and allow_partial_update: return @@ -147,10 +147,10 @@ def package_tag_list_save(tag_dicts, package, context): session = context["session"] pending = context.get('pending') - tag_package_tag = dict((package_tag.tag, package_tag) + tag_package_tag = dict((package_tag.tag, package_tag) for package_tag in package.package_tag_all) - + tag_package_tag_inactive = dict( [ (tag,pt) for tag,pt in tag_package_tag.items() if pt.state in ['deleted', 'pending-deleted'] ] @@ -200,7 +200,7 @@ def package_membership_list_save(group_dicts, package, context): members = session.query(model.Member).filter_by(table_id = package.id) - group_member = dict((member.group, member) + group_member = dict((member.group, member) for member in members) groups = set() @@ -234,7 +234,7 @@ def package_membership_list_save(group_dicts, package, context): member_obj.state = 'active' session.add(member_obj) - + def relationship_list_save(relationship_dicts, package, attr, context): allow_partial_update = context.get("allow_partial_update", False) @@ -249,7 +249,7 @@ def relationship_list_save(relationship_dicts, package, attr, context): relationships = [] for relationship_dict in relationship_dicts: - obj = table_dict_save(relationship_dict, + obj = table_dict_save(relationship_dict, model.PackageRelationship, context) relationships.append(obj) @@ -264,7 +264,7 @@ def relationship_list_save(relationship_dicts, package, attr, context): def package_dict_save(pkg_dict, context): import uuid - + model = context["model"] package = context.get("package") allow_partial_update = context.get("allow_partial_update", False) @@ -338,8 +338,8 @@ def group_member_save(context, group_dict, member_table_name): def group_dict_save(group_dict, context): - import uuid - + import uuid + model = context["model"] session = context["session"] group = context.get("group") @@ -347,12 +347,12 @@ def group_dict_save(group_dict, context): Group = model.Group if group: - group_dict["id"] = group.id + group_dict["id"] = group.id group = table_dict_save(group_dict, Group, context) if not group.id: group.id = str(uuid.uuid4()) - + context['group'] = group group_member_save(context, group_dict, 'packages') @@ -367,7 +367,7 @@ def group_dict_save(group_dict, context): for key in old_extras - new_extras: del group.extras[key] for key in new_extras: - group.extras[key] = extras[key] + group.extras[key] = extras[key] return group @@ -378,11 +378,11 @@ def user_dict_save(user_dict, context): model = context['model'] session = context['session'] user = context.get('user_obj') - + User = model.User if user: user_dict['id'] = user.id - + if 'password' in user_dict and not len(user_dict['password']): del user_dict['password'] @@ -410,7 +410,7 @@ def package_api_to_dict(api1_dict, context): updated_extras.update(value) new_value = [] - + for extras_key, extras_value in updated_extras.iteritems(): if extras_value is not None: new_value.append({"key": extras_key, @@ -431,7 +431,7 @@ def package_api_to_dict(api1_dict, context): dictized["resources"] = [{'url': download_url}] download_url = dictized.pop('download_url', None) - + return dictized def group_api_to_dict(api1_dict, context): @@ -443,7 +443,7 @@ def group_api_to_dict(api1_dict, context): if key == 'packages': new_value = [{"id": item} for item in value] if key == 'extras': - new_value = [{"key": extra_key, "value": value[extra_key]} + new_value = [{"key": extra_key, "value": value[extra_key]} for extra_key in value] dictized[key] = new_value @@ -454,7 +454,7 @@ def task_status_dict_save(task_status_dict, context): task_status = context.get("task_status") allow_partial_update = context.get("allow_partial_update", False) if task_status: - task_status_dict["id"] = task_status.id + task_status_dict["id"] = task_status.id task_status = table_dict_save(task_status_dict, model.TaskStatus, context) return task_status diff --git a/ckan/model/group.py b/ckan/model/group.py index e90b35a4c35..de823556d9d 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -116,14 +116,15 @@ def set_approval_status(self, status): pass def members_of_type(self, object_type, capacity=None): + from ckan import model object_type_string = object_type.__name__.lower() query = Session.query(object_type).\ filter(group_table.c.id == self.id).\ - filter(member_table.c.state == 'active').\ - filter(member_table.c.table_name == object_type_string) + filter(model.Member.state == 'active').\ + filter(model.Member.table_name == object_type_string) if capacity: - query = query.filter(member_table.c.capacity == capacity) + query = query.filter(model.Member.capacity == capacity) query = query.join(member_table, member_table.c.table_id == getattr(object_type,'id') ).\ join(group_table, group_table.c.id == member_table.c.group_id) diff --git a/ckan/model/user.py b/ckan/model/user.py index ed07c0c5e37..96660ef839d 100644 --- a/ckan/model/user.py +++ b/ckan/model/user.py @@ -23,15 +23,15 @@ ) class User(DomainObject): - + VALID_NAME = re.compile(r"^[a-zA-Z0-9_\-]{3,255}$") DOUBLE_SLASH = re.compile(':\/([^/])') - + @classmethod def by_openid(cls, openid): obj = Session.query(cls).autoflush(False) return obj.filter_by(openid=openid).first() - + @classmethod def get(cls, user_reference): # double slashes in an openid often get turned into single slashes @@ -57,7 +57,7 @@ def email_hash(self): if self.email: e = self.email.strip().lower().encode('utf8') return hashlib.md5(e).hexdigest() - + def get_reference_preferred_for_uri(self): '''Returns a reference (e.g. name, id, openid) for this user suitable for the user\'s URI. @@ -73,7 +73,7 @@ def get_reference_preferred_for_uri(self): else: ref = self.id return ref - + def _set_password(self, password): """Hash password on the fly.""" if isinstance(password, unicode): @@ -104,7 +104,7 @@ def validate_password(self, password): :return: Whether the password is valid. :rtype: bool """ - if not password or not self.password: + if not password or not self.password: return False if isinstance(password, unicode): password_8bit = password.encode('ascii', 'ignore') @@ -114,7 +114,7 @@ def validate_password(self, password): return self.password[40:] == hashed_pass.hexdigest() password = property(_get_password, _set_password) - + @classmethod def check_name_valid(cls, name): if not name \ @@ -147,10 +147,10 @@ def number_administered_packages(self): def is_in_group(self, group): return group in self.get_groups() - + def get_groups(self, group_type=None, capacity=None): import ckan.model as model - + q = model.Session.query(model.Group)\ .join(model.Member, model.Member.group_id == model.Group.id and \ model.Member.table_name == 'user' ).\ @@ -160,12 +160,12 @@ def get_groups(self, group_type=None, capacity=None): if capacity: q = q.filter( model.Member.capacity == capacity ) return q.all() - + if '_groups' not in self.__dict__: self._groups = q.all() - + groups = self._groups - if group_type: + if group_type: groups = [g for g in groups if g.type == group_type] return groups @@ -173,7 +173,7 @@ def get_groups(self, group_type=None, capacity=None): @classmethod def search(cls, querystr, sqlalchemy_query=None): '''Search name, fullname, email and openid. - + ''' import ckan.model as model if sqlalchemy_query is None: diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index ac8dc467a47..5381b2fc86e 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -404,10 +404,9 @@ CKAN.Utils = function($, my) { input_box.attr('name', new_name) input_box.attr('id', new_name) - var capacity = $("input:radio[name=add-user-capacity]:checked").val(); parent_dd.before( '' + - '' + + '' + '
    ' + ui.item.label + '
    ' ); From 9b99b253340de7d29d58ac244acb10352c8a97aa Mon Sep 17 00:00:00 2001 From: Ian Murray Date: Wed, 15 Feb 2012 12:13:34 +0000 Subject: [PATCH 04/84] [#1607][facets.html] Added a different function for generating facet filters as an unboxed list of
  • items. This allows for more flexible layout of filters. Such as a nested layout. --- ckan/templates/facets.html | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ckan/templates/facets.html b/ckan/templates/facets.html index 0ffe47bbd91..15a6ceb013f 100644 --- a/ckan/templates/facets.html +++ b/ckan/templates/facets.html @@ -5,8 +5,8 @@ py:strip="" > - -
    + +

    ${title(code)}

    +

    ${if_empty}

    + + +
  • ${if_empty}
  • +
  • + ${label(name)} (${count}) +
  • + +
    From 765de64a671d213dbb6f40f136af0ed7616c165d Mon Sep 17 00:00:00 2001 From: Ian Murray Date: Wed, 15 Feb 2012 15:27:10 +0000 Subject: [PATCH 05/84] [#1607][gravatar] Made default gravatar image a configuration option. --- ckan/config/deployment.ini_tmpl | 5 +++++ ckan/lib/helpers.py | 14 ++++++++++++-- ckan/tests/lib/test_helpers.py | 34 ++++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/ckan/config/deployment.ini_tmpl b/ckan/config/deployment.ini_tmpl index 85183c1757a..b0cc65bbc45 100644 --- a/ckan/config/deployment.ini_tmpl +++ b/ckan/config/deployment.ini_tmpl @@ -111,6 +111,11 @@ ckan.site_url = ## Favicon (default is the CKAN software favicon) ckan.favicon = /images/icons/ckan.ico +## The gravatar default to use. This can be any of the pre-defined strings +## as defined on http://en.gravatar.com/site/implement/images/ (e.g. "identicon" +## or "mm"). Or it can be a url, e.g. "http://example.com/images/avatar.jpg" +ckan.gravatar_default = identicon + ## Solr support #solr_url = http://127.0.0.1:8983/solr diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 80926f47e17..d2f1ee1433e 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -7,6 +7,7 @@ """ import datetime import re +import urllib from webhelpers.html import escape, HTML, literal, url_escape from webhelpers.html.tools import mail_to @@ -249,14 +250,23 @@ def icon_html(url, alt=None): def icon(name, alt=None): return icon_html(icon_url(name),alt) -def linked_gravatar(email_hash, size=100, default="identicon"): +def linked_gravatar(email_hash, size=100, default=None): return literal(''' %s''' % gravatar(email_hash,size,default) ) -def gravatar(email_hash, size=100, default="identicon"): +_VALID_GRAVATAR_DEFAULTS = ['404', 'mm', 'identicon', 'monsterid', 'wavatar', 'retro'] +def gravatar(email_hash, size=100, default=None): + if default is None: + from pylons import config + default = config.get('ckan.gravatar_default', 'identicon') + + if not default in _VALID_GRAVATAR_DEFAULTS: + # treat the default as a url + default = urllib.quote(default, safe='') + return literal('''''' % (email_hash, size, default) diff --git a/ckan/tests/lib/test_helpers.py b/ckan/tests/lib/test_helpers.py index 2500c64b69e..aa15287f649 100644 --- a/ckan/tests/lib/test_helpers.py +++ b/ckan/tests/lib/test_helpers.py @@ -3,6 +3,8 @@ import datetime from nose.tools import assert_equal +from pylons import config + from ckan.tests import * from ckan.lib import helpers as h @@ -54,10 +56,40 @@ def test_time_ago_in_words_from_str(self): def test_gravatar(self): email = 'zephod@gmail.com' expected =[''] + ''] + # Hash the email address + import hashlib + email_hash = hashlib.md5(email).hexdigest() + res = h.linked_gravatar(email_hash, 200, default='mm') + for e in expected: + assert e in res, (e,res) + + def test_gravatar_config_set_default(self): + """Test when default gravatar is None, it is pulled from the config file""" + email = 'zephod@gmail.com' + default = config.get('ckan.gravatar_default', 'identicon') + expected =[''] # Hash the email address import hashlib email_hash = hashlib.md5(email).hexdigest() res = h.linked_gravatar(email_hash, 200) for e in expected: assert e in res, (e,res) + + def test_gravatar_encodes_url_correctly(self): + """Test when the default gravatar is a url, it gets urlencoded""" + email = 'zephod@gmail.com' + default = 'http://example.com/images/avatar.jpg' + expected =[''] + # Hash the email address + import hashlib + email_hash = hashlib.md5(email).hexdigest() + res = h.linked_gravatar(email_hash, 200, default=default) + for e in expected: + assert e in res, (e,res) + + From 53952ee9f28304cc33dd45de3633e7f80e333ee1 Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Thu, 16 Feb 2012 10:07:58 +0000 Subject: [PATCH 06/84] [1607] Adding all sub-groups to facets for searching on the group_read page --- ckan/controllers/group.py | 2 ++ ckan/model/group.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index b962a8d7c99..61f8cb1c2dd 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -219,6 +219,8 @@ def read(self, id): # Search within group q += ' groups: "%s"' % c.group_dict.get('name') + for gp in c.group.get_children_groups( ): + q += ' groups: "%s"' % gp.name try: description_formatted = ckan.misc.MarkdownFormat().to_html(c.group_dict.get('description','')) diff --git a/ckan/model/group.py b/ckan/model/group.py index de823556d9d..8ae351d26fe 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -119,15 +119,15 @@ def members_of_type(self, object_type, capacity=None): from ckan import model object_type_string = object_type.__name__.lower() query = Session.query(object_type).\ - filter(group_table.c.id == self.id).\ + filter(model.Group.id == self.id).\ filter(model.Member.state == 'active').\ filter(model.Member.table_name == object_type_string) if capacity: query = query.filter(model.Member.capacity == capacity) - query = query.join(member_table, member_table.c.table_id == getattr(object_type,'id') ).\ - join(group_table, group_table.c.id == member_table.c.group_id) + query = query.join(model.Member, member_table.c.table_id == getattr(object_type,'id') ).\ + join(model.Group, group_table.c.id == member_table.c.group_id) return query @@ -137,6 +137,14 @@ def add_child(self, object_instance): member = Member(group=self, table_id=getattr(object_instance,'id'), table_name=object_type_string) Session.add(member) + def get_children_groups(self): + # TODO: Investigate Using members_of_type gives an error about + # group appearing too many times in query + members = Session.query(Member).\ + filter_by(state=vdm.sqlalchemy.State.ACTIVE).\ + filter(Member.table_name == "group").\ + filter(Member.group_id == self.id).all() + return [ Group.get( m.table_id ) for m in members ] def active_packages(self, load_eager=True): query = Session.query(Package).\ From eb0d5e36851ded5543664e3eb002001fa05018ff Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Thu, 16 Feb 2012 11:51:50 +0000 Subject: [PATCH 07/84] [1607] Fixing up the faceting for group_read --- ckan/controllers/group.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index 61f8cb1c2dd..b962a8d7c99 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -219,8 +219,6 @@ def read(self, id): # Search within group q += ' groups: "%s"' % c.group_dict.get('name') - for gp in c.group.get_children_groups( ): - q += ' groups: "%s"' % gp.name try: description_formatted = ckan.misc.MarkdownFormat().to_html(c.group_dict.get('description','')) From 35bba3df40978e37e032d7647ac36d59fa92c47c Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Thu, 16 Feb 2012 15:24:02 +0000 Subject: [PATCH 08/84] [1607] Implemented CTE to obtain entire tree hierarchy from the given Group instance. Unfortunately the CTE only recurses one level when we ask Sqlalchemy to return Group instances, whereas asking for field names makes sure it recurses through the entire depth of the tree. --- ckan/model/group.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/ckan/model/group.py b/ckan/model/group.py index 8ae351d26fe..ae9075504c8 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -137,14 +137,15 @@ def add_child(self, object_instance): member = Member(group=self, table_id=getattr(object_instance,'id'), table_name=object_type_string) Session.add(member) - def get_children_groups(self): - # TODO: Investigate Using members_of_type gives an error about - # group appearing too many times in query - members = Session.query(Member).\ - filter_by(state=vdm.sqlalchemy.State.ACTIVE).\ - filter(Member.table_name == "group").\ - filter(Member.group_id == self.id).all() - return [ Group.get( m.table_id ) for m in members ] + def get_children_groups(self, type='group'): + # Returns a list of dicts where each dict contains "id", "name", and "title" + # When querying with a CTE specifying a model in the query parameter causes + # problems as it returns only the first level deep apparently not recursing + # any deeper than that. If we simplify and request only specific fields then + # if returns the full depth of the hierarchy. + results = Session.query("id","name", "title").\ + from_statement(HIERARCHY_CTE).params(id=self.id,type=type).all() + return [ { "id":idf, "name": name, "title": title } for idf,name,title in results ] def active_packages(self, load_eager=True): query = Session.query(Package).\ @@ -251,3 +252,16 @@ def __repr__(self): #TODO MemberRevision.related_packages = lambda self: [self.continuity.package] +HIERARCHY_CTE = """ + WITH RECURSIVE subtree(id) AS ( + SELECT M.* FROM public.member AS M + WHERE M.table_name = 'group' AND M.state = 'active' + UNION ALL + SELECT M.* FROM public.member M, subtree SG + WHERE M.table_id = SG.group_id AND M.table_name = 'group' AND + M.state = 'active' ) + + SELECT G.* FROM subtree AS ST + INNER JOIN public.group G ON G.id = ST.table_id + WHERE group_id = :id AND G.type = :type +""" \ No newline at end of file From 7d4ee82ae88454266456b29622c2f086f72d48cb Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Mon, 20 Feb 2012 11:14:27 +0000 Subject: [PATCH 09/84] [1607] Fix for resource_show permission problem with viewing deleted resources --- ckan/logic/auth/publisher/get.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ckan/logic/auth/publisher/get.py b/ckan/logic/auth/publisher/get.py index 7766b1ae1fa..46796abdb9c 100644 --- a/ckan/logic/auth/publisher/get.py +++ b/ckan/logic/auth/publisher/get.py @@ -65,11 +65,11 @@ def package_show(context, data_dict): """ Package show permission checks the user group if the state is deleted """ model = context['model'] package = get_package_object(context, data_dict) - + if package.state == 'deleted': if 'ignore_auth' in context and context['ignore_auth']: - return {'success': True} - + return {'success': True} + user = context.get('user') if not user: @@ -81,24 +81,24 @@ def package_show(context, data_dict): if not _groups_intersect( userobj.get_groups('publisher'), package.get_groups('publisher') ): return {'success': False, 'msg': _('User %s not authorized to read package %s') % (str(user),package.id)} - + return {'success': True} def resource_show(context, data_dict): - """ Resource show permission checks the user group if the package state is deleted """ + """ Resource show permission checks the user group if the package state is deleted """ model = context['model'] user = context.get('user') resource = get_resource_object(context, data_dict) - package = resource.revision_group.package + package = resource.resource_group.package if package.state == 'deleted': userobj = model.User.get( user ) if not userobj: - return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (str(user),package.id)} + return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (str(user),package.id)} if not _groups_intersect( userobj.get_groups('publisher'), package.get_groups('publisher') ): return {'success': False, 'msg': _('User %s not authorized to read package %s') % (str(user),package.id)} - - pkg_dict = {'id': pkg.id} + + pkg_dict = {'id': package.id} return package_show(context, pkg_dict) @@ -112,12 +112,12 @@ def group_show(context, data_dict): user = context.get('user') group = get_group_object(context, data_dict) userobj = model.User.get( user ) - + if group.state == 'deleted': if not user or \ not _groups_intersect( userobj.get_groups('publisher'), group.get_groups('publisher') ): - return {'success': False, 'msg': _('User %s not authorized to show group %s') % (str(user),group.id)} - + return {'success': False, 'msg': _('User %s not authorized to show group %s') % (str(user),group.id)} + return {'success': True} def tag_show(context, data_dict): From 34bc52aebb94842c9ee96104152092001ddd3021 Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Mon, 20 Feb 2012 11:27:42 +0000 Subject: [PATCH 10/84] [1607] Fix for alpha pagination when providing unicode and no lookup attribute --- ckan/lib/alphabet_paginate.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ckan/lib/alphabet_paginate.py b/ckan/lib/alphabet_paginate.py index 321c9b6d861..4277867463f 100644 --- a/ckan/lib/alphabet_paginate.py +++ b/ckan/lib/alphabet_paginate.py @@ -7,7 +7,7 @@ collection=query, page=request.params.get('page', 'A'), ) - Template: + Template: ${c.page.pager()} ${package_list(c.page.items)} ${c.page.pager()} @@ -21,7 +21,7 @@ from routes import url_for class AlphaPage(object): - def __init__(self, collection, alpha_attribute, page, other_text, paging_threshold=50, + def __init__(self, collection, alpha_attribute, page, other_text, paging_threshold=50, controller_name='tag'): ''' @param collection - sqlalchemy query of all the items to paginate @@ -34,7 +34,7 @@ def __init__(self, collection, alpha_attribute, page, other_text, paging_thresho start paginating them. @param controller_name - The name of the controller that will be linked to, which defaults to tag. The controller name should be the - same as the route so for some this will be the full + same as the route so for some this will be the full controller name such as 'A.B.controllers.C:ClassName' ''' self.collection = collection @@ -47,8 +47,8 @@ def __init__(self, collection, alpha_attribute, page, other_text, paging_thresho for c in self.collection: x = c if isinstance( c, unicode ) else getattr(c, self.alpha_attribute)[0] self.available[x] = self.available.get(x, 0) + 1 - - + + def pager(self, q=None): '''Returns pager html - for navigating between the pages. @@ -72,10 +72,10 @@ def pager(self, q=None): 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) else: - page_element = HTML.span(class_="pager_empty", c=letter) + page_element = HTML.span(class_="pager_empty", c=letter) else: page_element = HTML.span(class_='pager_curpage', c=letter) - pages.append(page_element) + pages.append(page_element) div = HTML.tag('div', class_='pager', *pages) return div @@ -89,7 +89,7 @@ def items(self): attribute = getattr(query.table.c, self.alpha_attribute) elif sqav.startswith("0.5"): - attribute = getattr(query._entity_zero().selectable.c, + attribute = getattr(query._entity_zero().selectable.c, self.alpha_attribute) else: entity = getattr(query.column_descriptions[0]['expr'], @@ -111,13 +111,15 @@ def items(self): if self.item_count >= self.paging_threshold: if self.page != self.other_text: if isinstance(self.collection[0], dict): - items = [x for x in self.collection if x[self.alpha_attribute][0:1].lower() == self.page.lower()] + items = [x for x in self.collection if x[self.alpha_attribute][0:1].lower() == self.page.lower()] + elif isinstance(self.collection[0], unicode): + items = [x for x in self.collection if x[0:1].lower() == self.page.lower()] else: items = [x for x in self.collection if getattr(x,self.alpha_attribute)[0:1].lower() == self.page.lower()] else: # regexp search if isinstance(self.collection[0], dict): - items = [x for x in self.collection if re.match('^[^a-zA-Z].*',x[self.alpha_attribute])] + items = [x for x in self.collection if re.match('^[^a-zA-Z].*',x[self.alpha_attribute])] else: items = [x for x in self.collection if re.match('^[^a-zA-Z].*',x)] items.sort() From 78604416839bd6f93e2c5e15444ccf030e7df8a5 Mon Sep 17 00:00:00 2001 From: Ian Murray Date: Tue, 21 Feb 2012 15:24:03 +0000 Subject: [PATCH 11/84] [#1607][dictization] Fixed bug with package dictization. The dictization of a package wasn't correctly identifying which groups a package is an active member of. Instead, it was returning all the groups that the package has ever been a member of. --- ckan/lib/dictization/model_dictize.py | 3 +- ckan/tests/lib/test_dictization.py | 48 +++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index f1f48edf7c6..2d60cc6a705 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -171,7 +171,8 @@ def package_dictize(pkg, context): group = model.group_table q = select([group], from_obj=member_rev.join(group, group.c.id == member_rev.c.group_id) - ).where(member_rev.c.table_id == pkg.id) + ).where(member_rev.c.table_id == pkg.id)\ + .where(member_rev.c.state == 'active') result = _execute_with_revision(q, member_rev, context) result_dict["groups"] = obj_list_dictize(result, context) #relations diff --git a/ckan/tests/lib/test_dictization.py b/ckan/tests/lib/test_dictization.py index 8d83a6f43dd..4d91e458dc8 100644 --- a/ckan/tests/lib/test_dictization.py +++ b/ckan/tests/lib/test_dictization.py @@ -1,4 +1,4 @@ -from nose.tools import assert_equal +from nose.tools import assert_equal, assert_not_in, assert_in from pprint import pprint, pformat from difflib import unified_diff @@ -219,8 +219,6 @@ def test_02_package_dictize(self): assert sorted(result.values()) == sorted(self.package_expected.values()) assert result == self.package_expected - - def test_03_package_to_api1(self): context = {"model": model, @@ -1002,3 +1000,47 @@ def test_20_activity_save(self): # We didn't pass in any data so this should be empty. assert not got['data'] + + + def test_21_package_dictization_with_deleted_group(self): + """ + Ensure that the dictization does not return groups that the dataset has + been removed from. + """ + # Create a new dataset and 2 new groups + model.repo.new_revision() + pkg = model.Package(name='testing-deleted-groups') + group_1 = model.Group(name='test-group-1') + group_2 = model.Group(name='test-group-2') + model.Session.add(pkg) + model.Session.add(group_1) + model.Session.add(group_2) + model.Session.flush() + + # Add the dataset to group_1, and signal that the dataset used + # to be a member of group_2 by setting its membership state to 'deleted' + membership_1 = model.Member(table_id = pkg.id, + table_name = 'package', + group = group_1, + group_id = group_1.id, + state = 'active') + + membership_2 = model.Member(table_id = pkg.id, + table_name = 'package', + group = group_2, + group_id = group_2.id, + state = 'deleted') + + model.Session.add(membership_1) + model.Session.add(membership_2) + model.repo.commit() + + # Dictize the dataset + context = {"model": model, + "session": model.Session} + + result = package_dictize(pkg, context) + self.remove_changable_columns(result) + assert_not_in('test-group-2', [ g['name'] for g in result['groups'] ]) + assert_in('test-group-1', [ g['name'] for g in result['groups'] ]) + From b1f93e8246773bfb6c6731bd1f1fab8b568acc05 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 21 Feb 2012 19:01:18 +0000 Subject: [PATCH 12/84] [dataset/read][l]: Notes expand/collapse ui redesigned. --- ckan/public/css/style.css | 34 ++++++++++---------- ckan/public/images/chevron-down.png | Bin 0 -> 6781 bytes ckan/public/images/chevron-up.png | Bin 0 -> 6727 bytes ckan/public/scripts/application.js | 36 ++++++++++++---------- ckan/public/scripts/templates.js | 8 ----- ckan/templates/group/read.html | 9 +++++- ckan/templates/package/read_core.html | 9 +++++- ckan/templates/package/resource_read.html | 4 ++- 8 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 ckan/public/images/chevron-down.png create mode 100644 ckan/public/images/chevron-up.png diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 5fb2a633c74..e9f5a008a57 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1282,31 +1282,31 @@ body.package.read #sidebar li.widget-container { border: 0 } .notes { - padding: 8px; - border-left: 2px solid #eee; background: url('../images/ldquo.png') no-repeat top left #f7f7f7; + border: 1px solid #eee; + border-radius: 5px; } -.notes #notes-toggle a { - cursor: pointer; +.notes > div { + padding: 8px; } -.notes #notes-toggle a.more:after { - content: ' »'; - font-size: 150%; - position: relative; - bottom: -1px; +#notes-toggle { + padding: 0; + height: 23px; } -.notes #notes-toggle a.less:before { - content: '« '; - font-size: 150%; - position: relative; - bottom: -1px; +.notes #notes-toggle button { + cursor: pointer; + width: 100%; + height: 23px; + padding: 4px; + border-radius: 0; + border: 0; + border-top: 1px solid #eee; } - #notes-extract p { margin-bottom: 0; } -#notes-remainder { - margin-top: 1em; +#notes-remainder p:last-child { + margin-bottom: 0; } .dataset-label { font-weight: bold; diff --git a/ckan/public/images/chevron-down.png b/ckan/public/images/chevron-down.png new file mode 100644 index 0000000000000000000000000000000000000000..8d6c121eb9b113235acad95786a44f5617cf1c2d GIT binary patch literal 6781 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 1){ - var extract = notes.children(':eq(0)'); - var remainder = notes.children(':gt(0)'); - notes.html($.tmpl(CKAN.Templates.notesField)); - notes.find('#notes-extract').html(extract); - notes.find('#notes-remainder').html(remainder); - notes.find('#notes-remainder').hide(); - notes.find('#notes-toggle a').click(function(event){ - notes.find('#notes-toggle a').toggle(); - var remainder = notes.find('#notes-remainder') - if ($(event.target).hasClass('more')) { - remainder.slideDown(); - } - else { - remainder.slideUp(); + var paragraphs = notes.find('#notes-extract > *'); + if (paragraphs.length==0) { + notes.hide(); + } + else if (paragraphs.length > 1) { + var remainder = notes.find('#notes-remainder'); + $.each(paragraphs,function(i,para) { + if (i > 0) remainder.append($(para).remove()); + }); + notes.find('#notes-toggle').show(); + notes.find('#notes-toggle button').click( + function(event){ + notes.find('#notes-toggle button').toggle(); + if ($(event.target).hasClass('more')) + remainder.slideDown(); + else + remainder.slideUp(); + return false; } - return false; - }) + ); } }; diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index fe82805fadb..edffa21bb5d 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -157,11 +157,3 @@ CKAN.Templates.resourceEntry = ' \ \ '; -CKAN.Templates.notesField = ' \ -
    \ -
    \ -
    \ -
    \ -
    \ -'; - diff --git a/ckan/templates/group/read.html b/ckan/templates/group/read.html index 3b4a3bcfdc4..7915b5fdcf2 100644 --- a/ckan/templates/group/read.html +++ b/ckan/templates/group/read.html @@ -28,7 +28,14 @@

    Administrators

    State: ${c.group['state']}

    - ${c.description_formatted} +
    + ${c.description_formatted} +
    + +

    Datasets

    diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index fcadb6298e7..23e67663725 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -11,7 +11,14 @@
    - ${c.pkg_notes_formatted} +
    + ${c.pkg_notes_formatted} +
    + +
    diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index a2173daf149..c2eae2bfe2c 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -30,7 +30,9 @@
    - ${c.resource.get('description') or '(No description)'} +
    + ${c.resource.get('description') or '(No description)'} +
    From 0349d593ac823310bf75714e9d7317f65ad17e36 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 21 Feb 2012 20:46:17 +0000 Subject: [PATCH 13/84] [icons][m]: Using icons for resource types everywhere I can across the site. --- ckan/lib/helpers.py | 34 ++++++++++++++--- ckan/public/css/style.css | 10 ++--- ckan/public/images/icons/package_add.png | Bin 0 -> 899 bytes ckan/public/images/icons/page_white.png | Bin 0 -> 294 bytes ckan/public/images/icons/page_white_code.png | Bin 0 -> 603 bytes .../images/icons/page_white_compressed.png | Bin 0 -> 724 bytes ckan/public/images/icons/page_white_cup.png | Bin 0 -> 639 bytes .../images/icons/page_white_database.png | Bin 0 -> 579 bytes ckan/public/images/icons/page_white_error.png | Bin 0 -> 623 bytes ckan/public/images/icons/page_white_excel.png | Bin 0 -> 663 bytes ckan/public/images/icons/page_white_gear.png | Bin 0 -> 402 bytes ckan/public/images/icons/page_white_link.png | Bin 0 -> 614 bytes ckan/public/images/icons/page_white_text.png | Bin 0 -> 342 bytes ckan/templates/facets.html | 4 +- ckan/templates/package/layout.html | 36 +++++++----------- ckan/templates/package/read_core.html | 17 +++++---- 16 files changed, 61 insertions(+), 40 deletions(-) create mode 100755 ckan/public/images/icons/package_add.png create mode 100755 ckan/public/images/icons/page_white.png create mode 100755 ckan/public/images/icons/page_white_code.png create mode 100755 ckan/public/images/icons/page_white_compressed.png create mode 100755 ckan/public/images/icons/page_white_cup.png create mode 100755 ckan/public/images/icons/page_white_database.png create mode 100755 ckan/public/images/icons/page_white_error.png create mode 100755 ckan/public/images/icons/page_white_excel.png create mode 100755 ckan/public/images/icons/page_white_gear.png create mode 100755 ckan/public/images/icons/page_white_link.png create mode 100755 ckan/public/images/icons/page_white_text.png diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 150083b0c6c..70d9c7ffd5a 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -305,11 +305,35 @@ def markdown_extract(text, extract_length=190): def icon_url(name): return url_for_static('/images/icons/%s.png' % name) -def icon_html(url, alt=None): - return literal('%s ' % (url, alt)) - -def icon(name, alt=None): - return icon_html(icon_url(name),alt) +def icon_html(url, alt=None, inline=True): + classes = '' + if inline: classes += 'inline-icon ' + return literal('%s ' % (url, alt, classes)) + +def icon(name, alt=None, inline=True): + return icon_html(icon_url(name),alt,inline) + +def resource_icon(res): + if False: + icon_name = 'page_white' + # if (res.is_404?): icon_name = 'page_white_error' + # also: 'page_white_gear' + # also: 'page_white_link' + return icon(icon_name) + else: + return format_icon(res.get('format','')) + +def format_icon(_format): + icon_name = 'page_white' + _format = _format.lower() + if ('json' in _format): icon_name = 'page_white_cup' + if ('csv' in _format): icon_name = 'page_white_gear' + if ('xls' in _format): icon_name = 'page_white_excel' + if ('zip' in _format): icon_name = 'page_white_compressed' + if ('api' in _format): icon_name = 'page_white_database' + if ('plain text' in _format): icon_name = 'page_white_text' + if ('xml' in _format): icon_name = 'page_white_code' + return icon(icon_name) def linked_gravatar(email_hash, size=100, default="identicon"): return literal('''i-HS{zx9u^IUGw>*=$qi z4z(fju8Kxf4E>slBg^es4|nAN~@NV_SFj zT24(nZf1>C;s&Oa#mmJBSw-p_cY~Y6U)hMyiI9#@b2~OpJ~{tQK#e@V>&ZUL%dC-& z4K3ksgdE?;g2XJ7a-IC z{t7T9P!AB)6MkIq=xo`8@fr4Pe+pxHJkq|8i#gBoi7`)S7Dry495@-9|NUzWL5S=I zI}8e@=#j{*V_TIRYJCHM<4>HSsjdT~`23%KH*e}YU%C<>JM(QmG2_K2&b4Ftok-)u zSoT&#!hAJ4MD=!?;n}ksXzJm;^DmMqtaEL0%KcAFfAn>=sgaW^@?6tnFI$DxIX=HQ z$n|KMeH{mAuJ2-IC#5QQ<|d}62BjvZR2H60wE-&H;pyTSqH(@-Vl>|&1p(LP>kg~E zYiz5X^`c$+%8#zC{u)yfe-5 zmgid={Z3k(ERKCKrE7DF;=x4^O+ pzO8rLO8p|Ip=x)jHOtWj`bJBmKdh_V<`47(gQu&X%Q~loCIFbEay|e6 literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/page_white_code.png b/ckan/public/images/icons/page_white_code.png new file mode 100755 index 0000000000000000000000000000000000000000..0c76bd1297751b66230f74719504b2adb02b1615 GIT binary patch literal 603 zcmV-h0;K(kP)^~*-1fljz_B$LUvK}k?BNXe#Y!m=zM!!V#}8bncK5m;8VP zw86G*RI63?Cd%b9bX|ueNlZ|wR6rj|r_)VIP@r2imh3?SN+^{|kY%~8B{maJ@F*OK z&VH9LwOeGt#DRjj0~v~8`>iO7!Ybi;zE$va`A^T#yW`y44;k^#O~K5*jD=qcUhPSc zvyy~q;5H_1WT1l~cqje9yfa+l!hu6xjdOJ8s;8E^+=QQ$tw p?%p!Hy#YapB=@+^9(46X{{RQg%9y;OKjr`c002ovPDHLkV1g7l326WT literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/page_white_compressed.png b/ckan/public/images/icons/page_white_compressed.png new file mode 100755 index 0000000000000000000000000000000000000000..2b6b1007f33dceb8fefd5ef0aa8fb5aeba0ea3a5 GIT binary patch literal 724 zcmV;_0xSKAP) zJ3A|*qoWOonz+4ZQ0KNhDB07SX1?#FrNy8%K)_l}y&kh`*KYdy`Y99&tgNgMLSSrc z?B?+B@HO@P-jS~z2Rgc6yy~Y~%>oJpBxsb$5<&nRLqiuR7K=@0SZj~jTs|sv_jWVX zGe?WflejOaq|Vec=s9+ahmXbyJ|T)Sl*?s82sr2H?Ce~HD5WI+Sz&tmWrN()wI2}+ zKqg92t*l^-#ae~;9%KFlWkmwnY=-UK`_|%ICZ#P1gdjK<2n38VXsuC7{WiU!fZFmm zW~Sda9(Qi@pxO}$ARY+;t##Ao27usOqNt7Hwq6K7G1il@xitj=LIM&{N&#SuX;x4x zmG6FhCg-$PI;hQ=;1iZ>F>^~@)IPi;l}fX?SZ!QiO=X<|pSVkNpJuLHzW(FT_~W-v z?vFpkyE>8ee4d=7wKauH5~dd_M7d2Aa=ICC{Nj7Blqv&DQEP#j_VeWV&WXL>c=LLK zsmYg^_JiDb;%U!UxO%qjFAvsDFj-kzT2$GbV(ZopPM$i$z`!7jvEk07BcC=6FMt4` z*0u3Sy`0b~%#(0000K literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/page_white_cup.png b/ckan/public/images/icons/page_white_cup.png new file mode 100755 index 0000000000000000000000000000000000000000..0a7d6f4a6f6d864d0118209b5cb64a456e83b095 GIT binary patch literal 639 zcmV-_0)YLAP)zE0Ay_3@1Z_7#f-XWL#E{8Al7>L$ z0Rx7lnddoqAyfT%&#`$;v0@*5YdW3w z7mLNoa=FAshK% zDiy@zakyMAxr-H?iQDZi^!t5;Eno2A=?>mMx`Vg(Z!?<53LHLvfTPa`$mjDcX*Qdv zR;ylN4OH+m)fVX&Z#yZpUae;ss@a$K&})gHovkhr@w#xyPVlfVgXti1_357y%I-UHDvRWYvPEX+#g+j4Q9ayba zh7uQN1j%HQgA=Fp9DfODAU^*3*FCs^6IpO7xg`RUXyP)(;=d!ly=#I^l3e0Cub`{H Z`5PU3+D2e&<<>s`J(VpX#y^kqzQ;#=2x({YMw9Q&ndHT&`BD$#%Ql?{+)-OuSA`r}MWJ zVg+2Gc(GW}a=BERPNy^;kEz$|38dTYlFQ{%5S!g@|8f8D_!Nu9_Ni2glF1}xG8xi! zorc39&F6EPOeWOt_XS`W2H_Bo$MXugy}SEctJQj=(TLXTHL(jRXfzs>NF=0SHk;94 zF!&HjdZNX(3U3;LY64IMX__Xv%_wjLC!J2`0Jw?X=zPK$C$`&dYPDKaC={e16bcE@ zgun^<0k;ak*=xLE)@(Lqu~MmsFoMCLY&0Qog`NO(h@kyxaA%EbwJLy8sU*Vi`~52K zX0wrqW;_LmMq@evX4iAM9Od(Q0eHP$1%L|xAh@vrqB`HPQLon}f3aAka=9!3hr=O- z5F9`#J_7Jhah=U(4RjaRhkS4Xkk98kDz-`i!r|~~AQ1TFcDw(@<8g{aBE)l)PNxNE zI(RPyc>9e{@WGSMU%i7*v{!&P$WLz25)0oc=Dl-yy%xYZAm4b-rttL7UjR#%`#j_F R;_mPK^TXNSN{byMk2AI5vbwp!K-%-@!-vPR3iikL1L7HA!^!~ChCFU#lnGzp88=I z67V8PHBo4(l$u?-AKmT8?#_0rKW9dUNRbpLc`}piywAM9$xZ-3fR1C75T(BjCn-l* zjUcci2oXXo-}iqun@#)+`W@kL_-U&|2>MxZy~3IdmRm&8b)9!2%ksg3R)nNnT*TJOC=6{2hG86Dz+<^p6qfG5$i^UNUh+u)CD7O2 zK>Ioazn;U|+X0x$=feveYZL1W*Fm%e5P1sajd#eW#^5(ddx76*pt$^)b}$Q4oPabL zLc^HF>Z{8za;f$LtN0P$6C?1{X*jtXkRJ8IEeyiSzencvH3Ux_y>y^}wfJrRCQN#9 z?&e+C>sSAfrE%mZD5RfZ`gSndD)=P?+nG5Oq$zmY&-v+gc7R6c0u8^Ke#|XOq?gF@othF3zFpM8Il<8BJrWqBtF>b#_ye4{0)Xbu6j&@UIhRE002ov JPDHLkV1nWI9dZBw literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/page_white_excel.png b/ckan/public/images/icons/page_white_excel.png new file mode 100755 index 0000000000000000000000000000000000000000..b977d7e52e2446ea01201c5c7209ac3a05f12c9f GIT binary patch literal 663 zcmV;I0%-k-P)^@R5;6x zlTS!gQ5431_q{u#M2 zg&W%y6a}>qj1Z|7Vu&-DW6d~k-n;jnHsjb-q#u0C^W!_5^C=MlKq<8oNCQ6qS00!X z5eI;XP=g!^f}j{hku}E1zZ?XCjE;`p19k(Rh%^AQQ54xysU+ocx$c#f61Z4HnT#3u~FR(3>BnZniMIF4DouI8Hi4u>cAK%EN)5PO(ip3(% zIgBx+QYirR){Z8QwV$9Z(Mpt=L-Or3#bf-G@66}txq0yc*T(zNTBDT0T8rO^JeNbSI-Tzf5!pBioy4NwAN^?iN#{;fH1Jke4Xa`^fR8m z%h6dq%xX)S?7`zae))(Xst^Scp6B8FejQW?RLTM8@0=vnnntuRGBM2dpo>gbCnTD= z^<;=JuqdSf@O>Z8^XdR?s+KEfhDdB_#ahFj^giCtzT(s8kA$AViyTqaAR;KGaLzUU z<=GqA4bRwpX|IG~*x>pZ!@zLr`XQ`od>m(`;jz|M_*1GDO#$7;n74ppb8=eiqh760 x0yt}J1#p`gw$`o!R{d7zU9~!Un@nJV{4bstt4Au+Up@c;002ovPDHLkV1kWhGjjj{ literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/page_white_gear.png b/ckan/public/images/icons/page_white_gear.png new file mode 100755 index 0000000000000000000000000000000000000000..106f5aa3611a4807ec8c21701c631730275089a4 GIT binary patch literal 402 zcmV;D0d4+?P)<@FR}JvtGRKa0_WfK^c7uXaFH3q@Y!Hnl8VySc`OtkPN3;#l*y*l23+99h*9JzA00}rAC!#M1dZ#v9YOBH|eC*${MmzzYjBu!!-< zK8tujf&(6i)1biy*F>4{f*Kd(IU-JsG&#b_@NgTnx@40)2@2%c;*=?-2Za=}O}7&( w%_K#(S>e1j&gfY?mR})n>>0+8p`iTe2d1K2h8#$+)&Kwi07*qoM6N<$f(2cptN;K2 literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/page_white_link.png b/ckan/public/images/icons/page_white_link.png new file mode 100755 index 0000000000000000000000000000000000000000..bf7bd1c9bfd78d689c73ba67cf914182933ee68c GIT binary patch literal 614 zcmV-s0-61ZP)OOAS;jTeL{ZSdz-%)SMH9tDF;N4B6%j=d15J&5qy`F#vB?Ar zqS1nH@%ny_XSI*Y>) z1f5QYdmzT>YciP<3WehS<{GovEaLGv27>{*-7f0&I$yJ^L%ZGPv1YT$V|u;*+ZCWz ztHI~CDVsuy($SfR6-`N~K?9GTB#l%%0h7 z-q`K-y~E)+s8lMyTrPL8^_pUo)9G|SluG5pPqw6!LJB_PzyJUM07*qoM6N<$f^=yZ AYybcN literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/page_white_text.png b/ckan/public/images/icons/page_white_text.png new file mode 100755 index 0000000000000000000000000000000000000000..813f712f726c935f9adf8d2f2dd0d7683791ef11 GIT binary patch literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-%6;pyTSA|c6o&@eC9QG)Hj&ExYL zO&oVL^)+cM^qd@ApywS>pwx0H@RDN}hq;7mU-SKczYQ-hnrr=;iDAQMZQ+*g=YOM= z!QlMQEn7FbaD->uKAYgo_j9)W&$$zS*W9}m(ey0q$&7l-XEWO0Y(9M=SnhLbwy;d>@~SY$Ku*0xPvIOQeV1x7u_z-2-X>_74(yfh7C znXL|3GZ+d2`3re2hs?MK${title(code)}
    diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index 70478f5d2e8..74de7b7febf 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -9,29 +9,21 @@
    + + + ${h.icon('package') + _('Resources') + ' (' + str(len(c.pkg_dict.get('resources',[]))) + ')'} +    +
  • ${h.subnav_link(c, h.icon('page_stack') + _('History'), controller='package', action='history', id=c.pkg.name)}
  •   |   diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 23e67663725..78420e92891 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -36,13 +36,16 @@

    Resources

    Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago

    -

    - - ${res.get('url', '')} - - - [cached] - +

    + ${h.resource_icon(res)} + + + ${res.get('url', '')} + + + [cached] + +

    From f55610def573a7fa829c0287821f802160bd6762 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 21 Feb 2012 21:50:07 +0000 Subject: [PATCH 14/84] [dataset/layout][m]: Tidying dataset navbar & link targets. --- ckan/public/css/style.css | 3 +++ ckan/public/scripts/application.js | 5 ++++ ckan/templates/package/layout.html | 42 +++++++++++++++++++----------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index f3b5ff885e7..17b164c0640 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -308,6 +308,9 @@ img.gravatar { /* ======================================== */ /* MinorNavigation extension: Dropdown Menu */ /* ======================================== */ +.dropdown-appears hr { + margin: 5px 0; +} #minornavigation .dropdown { position: relative; display: inline-block; diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 3ce7304b729..ba7d95d9b38 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -35,6 +35,10 @@ if (isDatasetView) { // Show extract of notes field CKAN.Utils.setupNotesExtract(); + $('.js-scroll-resources').click(function() { + var header = $('#dataset-resources > h3:first-child'); + $("html,body").animate({ scrollTop: header.offset().top }, 500); + }); } var isResourceView = $('body.package.resource_read').length > 0; @@ -63,6 +67,7 @@ window.location = ($(e.target).attr('action')); }); + var isDatasetEdit = $('body.package.edit').length > 0; if (isDatasetEdit) { CKAN.Utils.setupUrlEditor('package',readOnly=true); diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index 74de7b7febf..11229f36026 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -9,21 +9,33 @@
    • ${h.subnav_link(c, h.icon('package') + _('View'), controller='package', action='read', id=c.pkg.name)}
    • - + + + +
    • + ${h.icon('package-disabled') + _('Resources (0)')}    +
    • +
      + + + +
    • ${h.subnav_link(c, h.icon('page_stack') + _('History'), controller='package', action='history', id=c.pkg.name)}
    •   |   From 76d87e6fc9227864b302192e6492892151e9deac Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Wed, 22 Feb 2012 10:20:52 +0000 Subject: [PATCH 15/84] [1607] Added a lookup to determine whether the user is in any of the provided groups --- ckan/controllers/user.py | 24 ++++++++++++------------ ckan/model/user.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index 7a0cf3389bb..86424a3010e 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -11,7 +11,7 @@ from ckan.logic import NotFound, NotAuthorized, ValidationError from ckan.logic import check_access, get_action from ckan.logic import tuplize_dict, clean_dict, parse_params -from ckan.logic.schema import user_new_form_schema, user_edit_form_schema +from ckan.logic.schema import user_new_form_schema, user_edit_form_schema from ckan.logic.action.get import user_activity_list_html from ckan.lib.captcha import check_recaptcha, CaptchaError @@ -31,7 +31,7 @@ def __before__(self, action, **env): if c.action not in ('login','request_reset','perform_reset',): abort(401, _('Not authorized to see this page')) - ## hooks for subclasses + ## hooks for subclasses new_user_form = 'user/new_user_form.html' edit_user_form = 'user/edit_user_form.html' @@ -130,7 +130,7 @@ def new(self, data=None, errors=None, error_summary=None): if context['save'] and not data: return self._save_new(context) - + data = data or {} errors = errors or {} error_summary = error_summary or {} @@ -201,10 +201,10 @@ def edit(self, id=None, data=None, errors=None, error_summary=None): abort(404, _('User not found')) user_obj = context.get('user_obj') - + if not (ckan.authz.Authorizer().is_sysadmin(unicode(c.user)) or c.user == user_obj.name): abort(401, _('User %s not authorized to edit %s') % (str(c.user), id)) - + errors = errors or {} vars = {'data': data, 'errors': errors, 'error_summary': error_summary} @@ -244,9 +244,9 @@ def login(self): # #1662 restriction log.warn('Cannot mount CKAN at a URL and login with OpenID.') g.openid_enabled = False - + return render('user/login.html') - + def logged_in(self): if c.user: context = {'model': model, @@ -268,14 +268,14 @@ def logged_in(self): h.flash_error('Login failed. Bad username or password.' + \ ' (Or if using OpenID, it hasn\'t been associated with a user account.)') h.redirect_to(controller='user', action='login') - + def logged_out(self): c.user = None response.delete_cookie("ckan_user") response.delete_cookie("ckan_display_name") response.delete_cookie("ckan_apikey") return render('user/logout.html') - + def request_reset(self): if request.method == 'POST': id = request.params.get('user') @@ -337,7 +337,7 @@ def perform_reset(self, id): if request.method == 'POST': try: - context['reset_password'] = True + context['reset_password'] = True new_password = self._get_form_password() user_dict['password'] = new_password user_dict['reset_key'] = c.reset_key @@ -365,7 +365,7 @@ def _format_about(self, about): log.error('Could not print "about" field Field: %r Error: %r', about, e) html = _('Error: Could not parse About text') return html - + def _get_form_password(self): password1 = request.params.getone('password1') password2 = request.params.getone('password2') @@ -375,4 +375,4 @@ def _get_form_password(self): elif not password1 == password2: raise ValueError(_("The passwords you entered do not match.")) return password1 - + diff --git a/ckan/model/user.py b/ckan/model/user.py index 96660ef839d..780cb94ef00 100644 --- a/ckan/model/user.py +++ b/ckan/model/user.py @@ -148,6 +148,19 @@ def number_administered_packages(self): def is_in_group(self, group): return group in self.get_groups() + def is_in_groups(self, groupids): + """ Given a list of group ids, returns True if this user is in any of + those groups """ + guser = set( self.get_group_ids() ) + gids = set( groupids ) + + return len( guser.intersection( gids ) ) > 0 + + + def get_group_ids(self, group_type=None): + """ Returns a list of group ids that the current user belongs to """ + return [ g.id for g in self.get_groups( group_type=group_type ) ] + def get_groups(self, group_type=None, capacity=None): import ckan.model as model From 1e0bfcc5250f45b68dee8a9ad6f39f3775e5e161 Mon Sep 17 00:00:00 2001 From: Ian Murray Date: Wed, 22 Feb 2012 11:51:03 +0000 Subject: [PATCH 16/84] [#1607] Prevent register_pluggable_behaviour() functions from having effect if called more than once. This is only necessary when running unit tests --- ckan/controllers/group.py | 6 ++++++ ckan/controllers/package.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index b962a8d7c99..5f16e2ae6b4 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -38,6 +38,12 @@ def register_pluggable_behaviour(map): """ global _default_controller_behaviour + # This function should have not effect if called more than once. + # This should not occur in normal deployment, but it may happen when + # running unit tests. + if _default_controller_behaviour is not None: + return + # Create the mappings and register the fallback behaviour if one is found. for plugin in PluginImplementations(IGroupForm): if plugin.is_fallback(): diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index 5baebd38104..c1c528246f6 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -67,6 +67,12 @@ def register_pluggable_behaviour(map): """ global _default_controller_behaviour + # This function should have not effect if called more than once. + # This should not occur in normal deployment, but it may happen when + # running unit tests. + if _default_controller_behaviour is not None: + return + # Create the mappings and register the fallback behaviour if one is found. for plugin in PluginImplementations(IDatasetForm): if plugin.is_fallback(): From 8540fe7357bbbd24f8c53fd9e5ea96e712080ee1 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Wed, 22 Feb 2012 13:23:13 +0000 Subject: [PATCH 17/84] [1506][datasets][s]: Check authorisation before linking resource editor. --- ckan/templates/package/layout.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index 11229f36026..a6ab21bd45f 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -20,11 +20,13 @@
      • Basic Information
      • Further Information
      • -
      • Resources
      • Groups & Tags
      • Extras
      • Delete
      • diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index a6ab21bd45f..f5b8b25f27b 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -22,8 +22,8 @@
    - ${h.icon('package') + _('Resources') + ' (' + str(len(c.pkg_dict.get('resources',[]))) + ')'} + ${h.icon('page_white_stack') + _('Resources') + ' (' + str(len(c.pkg_dict.get('resources',[]))) + ')'}    @@ -42,7 +42,7 @@   |  
  • - ${h.subnav_link(c, h.icon('package_edit') + _('Edit'), controller='package', action='edit', id=c.pkg.name)} + ${h.subnav_link(c, h.icon('package_edit') + _('Settings'), controller='package', action='edit', id=c.pkg.name)}
  • diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index 31e0363da67..5c31e5893b1 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -82,7 +82,6 @@

    Errors in form

    -

    Resources: the files and APIs associated with this dataset

    From 75528b4cc623d1bcba792f2b81990468065b769f Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Wed, 22 Feb 2012 20:01:05 +0000 Subject: [PATCH 19/84] [1506][l]: Reorder resources in WUI. Lots of ux development on the new resource create/edit flow. --- ckan/public/css/style.css | 141 ++++++-------- ckan/public/scripts/application.js | 90 ++++----- ckan/public/scripts/templates.js | 185 ++++++++++--------- ckan/templates/package/editresources.html | 21 +++ ckan/templates/package/new_package_form.html | 30 ++- 5 files changed, 239 insertions(+), 228 deletions(-) create mode 100644 ckan/templates/package/editresources.html diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 44c0afc9daf..72735e837e6 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -242,6 +242,25 @@ img.gravatar { padding-right: 5px; } +.drag-drop-list { + list-style-type: none; + margin: 0; + padding: 0; +} +.drag-drop-list li { + margin-bottom: 3px; +} +.drag-drop-list .drag-bars { + font-size: 12px; + color: #ccc; + cursor: move; + display: inline-block; + text-align: center; + vertical-align: middle; + padding: 8px 10px; + margin-right: 10px; +} + /* =============== */ /* MinorNavigation */ /* =============== */ @@ -1006,10 +1025,18 @@ ul.dataset-edit-nav li a:hover { vertical-align: middle; } - /* ======================= */ /* = Edit Resources Page = */ /* ======================= */ +body.editresources #content { + width: 100%; + padding-right: 0; + margin-right: 0; + border-right: 0; +} +body.editresources #sidebar { + display: none; +} .dataset-editresources-form fieldset#basic-information, .dataset-editresources-form fieldset#groups, .dataset-editresources-form fieldset#further-information, @@ -1017,6 +1044,7 @@ ul.dataset-edit-nav li a:hover { .dataset-editresources-form fieldset#delete { display: none; } + .dataset-editresources-form input#Resource--url { width: 60%; } @@ -1040,85 +1068,6 @@ ul.dataset-edit-nav li a:hover { } -/* ================================ */ -/* = Edit Dataset Page: Resources = */ -/* ================================ */ -.resource-table-edit tbody tr td, -.resource-table-view tbody tr td { - /* Constrain structure against overflow */ - max-width: 200px; - overflow: hidden; - white-space: nowrap; -} -.resource-table-edit tr { - overflow: hidden; -} -th.resource-edit-delete, -td.resource-edit-delete { - /* Override screen.css */ - padding: 0; - width: 40px; -} -td.resource-edit-delete img { - padding: 8px; -} -a.resource-edit-expand { - background-image: url('/images/icons/arrow-closed.gif'); - padding-left: 13px; - background-position: left center; - background-repeat: no-repeat; -} -td.resource-edit { - padding: 10px; - vertical-align: top; -} -.resource-edit-expanded { - overflow: hidden; - margin: 0; -} -.resource-edit-expanded table { - /* Override screen.css */ - margin: 10px 0; -} -table.resource-table-edit td.resource-edit tbody td { - /* Override alternating background */ - background: transparent; -} -td.resource-edit-delete { - vertical-align: top; - text-align: center; -} -td.resource-edit input { - /* Override forms.css */ - width: 100%; -} -th.resource-edit-label { - width: 23%; - display: none; -} -th.resource-edit-value { - width: 27%; - display: none; -} -td.resource-edit-label, -td.resource-edit-value { - font-size: 0.92em; - line-height: 1.5em; - padding-top: 0; - padding-bottom: 4px; - background: inherit; -} -td.resource-edit-label { - text-align: right; - font-weight: bold; - padding-right: 4px; - vertical-align: middle; -} -td.resource-edit-value { - padding-left: 4px; - border-left: 1px dashed #aaa; -} - /* ==================== */ /* = Add Dataset Page = */ @@ -1566,3 +1515,35 @@ body.authz form button { color:#999; } + + + + + + +/* Dev */ + +.resource-details { + margin-bottom: 20px; +} +.resource-details input[type="text"] { + width: 350px; +} +.resource-details textarea { + width: 350px; + height: 40px; +} +.resource-details .dataset-label { + vertical-align: top; +} + + +.editresources-left { + width: 40%; + float: left; +} +.editresources-right { + width: 58%; + float: left; + padding-left: 8px; +} diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index c8a86460ee6..9aadb25f381 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -92,6 +92,9 @@ el: $el }); view.render(); + + $( ".drag-drop-list" ).sortable(); + $( ".drag-drop-list" ).disableSelection(); } var isGroupEdit = $('body.group.edit').length > 0; @@ -635,15 +638,32 @@ CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ CKAN.View.ResourceEditList = Backbone.View.extend({ initialize: function() { - _.bindAll(this, 'addResource', 'removeResource'); + _.bindAll(this, 'addResource', 'removeResource', 'sortStop'); this.collection.bind('add', this.addResource); this.collection.bind('remove', this.removeResource); this.collection.each(this.addResource); + this.el.bind("sortstop", this.sortStop); + $(this.el.find('li:first-child .js-resource-edit-toggle')).click(); + }, + + sortStop: function(event,ui) { + $.each(this.el.find('li'), function(li_idx, li) { + var resource = $(li).data('resource'); + if (resource) { + var $table = resource.view_table; + $.each($table.find('input'), function(input_idx, input) { + var name = $(input).attr('name'); + name = name.replace(/(resources__)\d+(.*)/, '$1'+li_idx+'$2') + $(input).attr('name',name); + }); + } + }); }, nextIndex: function() { var maxId=-1; - this.el.find('input').each(function(idx,input) { + var root = $('.editresources-right'); + root.find('input').each(function(idx,input) { var splitName=$(input).attr('name').split('__'); if (splitName.length>1) { var myId = parseInt(splitName[1]) @@ -654,12 +674,11 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ }, addResource: function(resource) { + var self = this; var position = this.nextIndex(); // Create a row from the template - var $tr = $(''); - $tr.html($.tmpl( - CKAN.Templates.resourceEntry, - { resource: resource.toTemplateJSON(), + var resource_object = { + resource: resource.toTemplateJSON(), num: position, resourceTypeOptions: [ ['file', 'Data File'] @@ -670,56 +689,39 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ , ['code', 'Code'] , ['example', 'Example'] ] - } - )); - $tr.find('.js-resource-edit-expanded').hide(); - this.el.append($tr); - resource.view_tr = $tr; + }; + var $li = $($.tmpl(CKAN.Templates.resourceEntry, resource_object)); + var $table = $($.tmpl(CKAN.Templates.resourceTable, resource_object)); + this.el.append($li); + $('.editresources-right').append($table); + + // Associate the resource with the DOM object + resource.view_row = $li; + resource.view_table = $table; + $li.data('resource', resource); // == Inner Function: Toggle the expanded options set == // var toggleOpen = function(triggerEvent) { if (triggerEvent) triggerEvent.preventDefault(); - var animTime = 350; - var expandedTable = $tr.find('.js-resource-edit-expanded'); - var finalHeight = expandedTable.height(); - var icon = 'closed'; - - if (expandedTable.is(':visible')) { - expandedTable.animate( - {height:0}, - animTime, - function() { - expandedTable.height(finalHeight); - expandedTable.hide(); - } - ); - } - else { - expandedTable.show(); - expandedTable.height(0); - // Transition to its true height - expandedTable.animate({height:finalHeight}, animTime); - $tr.find('.js-resource-edit-name').focus(); - icon = 'open'; - } - $tr.find('.js-resource-edit-toggle').css("background-image", "url('/images/icons/arrow-"+icon+".gif')"); + // Close all tables + $('.editresources-right .js-resource-edit-expanded').hide(); + $table.show(); }; // == Inner Function: Delete the row == // - var collection = this.collection; var deleteResource = function(triggerEvent) { if (triggerEvent) triggerEvent.preventDefault(); confirmMessage = CKAN.Strings.deleteThisResourceQuestion; resourceName = resource.attributes.name || CKAN.Strings.noNameBrackets; confirmMessage = confirmMessage.replace('%name%', resourceName); if (confirm(confirmMessage)) { - collection.remove(resource); + self.collection.remove(resource); } }; // == Inner Functions: Update the name as you type == // var setName = function(newName) { - $link = $tr.find('.js-resource-edit-toggle'); + $link = $li.find('.js-resource-edit-toggle'); newName = newName || (''+CKAN.Strings.noNameBrackets+''); // Need to structurally modify the DOM to force a re-render of text $link.html(''+newName+''); @@ -733,19 +735,19 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ toggleOpen(); } - var nameBox = $tr.find('input.js-resource-edit-name'); + var nameBox = $li.find('input.js-resource-edit-name'); CKAN.Utils.bindInputChanges(nameBox,nameBoxChanged); - $tr.find('.js-resource-edit-toggle').click(toggleOpen); - $tr.find('.js-resource-edit-delete').click(deleteResource); + $li.find('.js-resource-edit-toggle').click(toggleOpen); + $li.find('.js-resource-edit-delete').click(deleteResource); // Initialise name setName(resource.attributes.name); }, removeResource: function(resource) { - if (resource.view_tr) { - resource.view_tr.remove(); - delete resource.view_tr; + if (resource.view_row) { + resource.view_row.remove(); + delete resource.view_row; } }, }); diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index edffa21bb5d..e169804f585 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -65,95 +65,108 @@ CKAN.Templates.resourceUpload = ' \ CKAN.Templates.resourceEntry = ' \ - \ + \ + \ +'; + + +CKAN.Templates.resourceTable = ' \ +
    \ +
  • \ +
    |||
    \ ${resource.name}\ -
    \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
    '+CKAN.Strings.name+' \ - \ -
    '+CKAN.Strings.description+' \ - \ -
    '+CKAN.Strings.url+' \ - {{if resource.resource_type=="file.upload"}} \ - ${resource.url} \ - \ - {{/if}} \ - {{if resource.resource_type!="file.upload"}} \ - \ - {{/if}} \ -
    '+CKAN.Strings.format+' \ - \ - '+CKAN.Strings.resourceType+' \ - {{if resource.resource_type=="file.upload"}} \ - Data File (Uploaded) \ - \ - {{/if}} \ - {{if resource.resource_type!="file.upload"}} \ - \ - {{/if}} \ -
    '+CKAN.Strings.sizeBracketsBytes+' \ - \ - '+CKAN.Strings.mimetype+' \ - \ -
    '+CKAN.Strings.lastModified+' \ - \ - '+CKAN.Strings.mimetypeInner+' \ - \ -
    '+CKAN.Strings.hash+' \ - ${resource.hash || "Unknown"} \ - \ -
    '+CKAN.Strings.id+' \ - ${resource.id} \ - \ -
    \ -
    \ -
  • \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ \ \ From 92611ef80e171c9353d87736cfc458770ed7bf20 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 23 Feb 2012 13:17:16 +0000 Subject: [PATCH 23/84] [#1506][editresource][s]: Typing resource name dynamically updates resource list. --- ckan/public/scripts/application.js | 14 +++++++------- ckan/public/scripts/templates.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 9a1d73dcd4b..64583e32269 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -643,7 +643,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ this.collection.bind('remove', this.removeResource); this.collection.each(this.addResource); this.el.bind("sortstop", this.sortStop); - $(this.el.find('li:first-child .js-resource-edit-toggle')).click(); + $(this.el.find('li:first-child .js-resource-edit-open')).click(); }, sortStop: function(event,ui) { @@ -701,7 +701,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ $li.data('resource', resource); // == Inner Function: Toggle the expanded options set == // - var toggleOpen = function(triggerEvent) { + var openTable = function(triggerEvent) { if (triggerEvent) triggerEvent.preventDefault(); // Close all tables $('.editresources-right .js-resource-edit-expanded').hide(); @@ -721,10 +721,10 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ // == Inner Functions: Update the name as you type == // var setName = function(newName) { - $link = $li.find('.js-resource-edit-toggle'); + $link = $li.find('.js-resource-edit-open'); newName = newName || (''+CKAN.Strings.noNameBrackets+''); // Need to structurally modify the DOM to force a re-render of text - $link.html(''+newName+''); + $link.html(''+newName+''); }; var nameBoxChanged = function(e) { setName($(e.target).val()); @@ -732,13 +732,13 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ // Trigger animation if (resource.isNew()) { - toggleOpen(); + openTable(); } - var nameBox = $li.find('input.js-resource-edit-name'); + var nameBox = $table.find('input.js-resource-edit-name'); CKAN.Utils.bindInputChanges(nameBox,nameBoxChanged); - $li.find('.js-resource-edit-toggle').click(toggleOpen); + $li.find('.js-resource-edit-open').click(openTable); $li.find('.js-resource-edit-delete').click(deleteResource); // Initialise name setName(resource.attributes.name); diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 351e8c98538..e8845041e8c 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -67,7 +67,7 @@ CKAN.Templates.resourceUpload = ' \ CKAN.Templates.resourceEntry = ' \
  • \
    |||
    \ - ${resource.name}\ + ${resource.name}\
  • \ \ -'; - + \ + '; -CKAN.Templates.resourceTable = ' \ -
    FieldValue
    '+CKAN.Strings.name+' \ + \ +
    '+CKAN.Strings.description+' \ + \
    \ \ \ diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index 56fe328044a..a177df980e9 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -82,19 +82,18 @@

    Errors in form

    -
    -
      -
    -
    -
      -
    • Add a resource:

    • -
    • -
    • - -
    -
    +
      +
    +
    +
    -
    +
    +
      +
    • Add a resource:

    • +
    • +
    • + +
    From fc674b6abbb179c9e58c17dceaf75de0cabb0462 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 23 Feb 2012 15:17:42 +0000 Subject: [PATCH 26/84] [#1506][resourceedit][m]: Resource list cells are entirely clickable. --- ckan/public/css/style.css | 32 +++++++++++++++++++----------- ckan/public/scripts/application.js | 2 +- ckan/public/scripts/templates.js | 10 +++------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 75fd08128a9..223a68eac72 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -251,15 +251,6 @@ img.gravatar { margin-bottom: 3px; cursor: move; } -.drag-drop-list .drag-bars { - font-size: 12px; - color: #ccc; - display: inline-block; - text-align: center; - vertical-align: middle; - padding: 6px 10px; - margin-right: 10px; -} /* =============== */ /* MinorNavigation */ @@ -1529,7 +1520,7 @@ fieldset#resources { margin-bottom: 40px; } li.resource-edit { - margin-right: 10px; + margin-right: 20px; position: relative; z-index: 1; background: #fff; @@ -1537,17 +1528,34 @@ li.resource-edit { } li.resource-edit:hover { border-color: #ccc; + margin-right: 1px; + border-right: 0; +} +li.resource-edit a { + display: block; + cursor: move; +} +li.resource-edit .drag-bars { + font-size: 12px; + color: #ccc; + display: inline-block; + text-align: center; + vertical-align: middle; + padding: 5px 8px; +} +li.resource-edit:hover .drag-bars { + color: #999; } li.resource-edit.active { border-color: #888; border-right: 0; - background: #eee; + background: #f9f9f9; margin-right: 0; } .resource-details-container { - background: #eee; + background: #f9f9f9; border: 1px solid #888; box-shadow: 2px 2px 4px #888; margin-bottom: 20px; diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index cc7fea997ca..b13944f7afe 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -735,7 +735,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ var setName = function(newName) { newName = newName || (''+CKAN.Strings.noNameBrackets+''); // Need to structurally modify the DOM to force a re-render of text - $link = $li.find('.js-resource-edit-open'); + $link = $li.find('.js-resource-edit-name'); $link.html(''+newName+''); }; // == Inner function: Updates the icon as you type == // diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 8bd505dbdee..94e2a6f56a6 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -66,9 +66,11 @@ CKAN.Templates.resourceUpload = ' \ CKAN.Templates.resourceEntry = ' \
  • \ + \
    |||
    \ \ -
    ${resource.name}\ + ${resource.name}\ + \

    Resources

    - -
    - - ${h.resource_display_name(res)} -    - ${res.get('format')} -

    ${h.markdown_extract(res.get('description'))}

    -

    - Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago -

    -

    +

    -
    + ${h.resource_display_name(res)} + ${res.get('format')} + +
  • + + (none) From 2eda2fc84fb165ba6b4ce9cff751eaa0d3d1224f Mon Sep 17 00:00:00 2001 From: Ian Murray Date: Thu, 23 Feb 2012 18:11:22 +0000 Subject: [PATCH 28/84] [#1607][validators] Added a group validator that checks for existence of a group by id or name --- ckan/logic/validators.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ckan/logic/validators.py b/ckan/logic/validators.py index 0101bc726c0..512ae22fd39 100644 --- a/ckan/logic/validators.py +++ b/ckan/logic/validators.py @@ -111,6 +111,16 @@ def group_id_exists(group_id, context): raise Invalid(_("That group ID does not exist.")) return group_id +def group_id_or_name_exists(reference, context): + """ + Raises Invalid if a group identified by the name or id cannot be found. + """ + model = context['model'] + result = model.Group.get(reference) + if not result: + raise Invalid(_('That group name or ID does not exist.')) + return reference + def activity_type_exists(activity_type): """Raises Invalid if there is no registered activity renderer for the given activity_type. Otherwise returns the given activity_type. From 277bb7672f3cf5de7be578893c8219d0a3d294e3 Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Fri, 24 Feb 2012 11:20:49 +0000 Subject: [PATCH 29/84] Fixing problem with publisher_form and moving setup_template_variables out of plugin --- ckan/controllers/group.py | 2 +- ckan/lib/alphabet_paginate.py | 8 ++- ckan/lib/authenticator.py | 8 +-- ckan/lib/dictization/__init__.py | 8 +-- ckan/lib/dictization/model_dictize.py | 76 +++++++++++++-------------- ckan/logic/action/get.py | 2 +- ckan/logic/schema.py | 6 ++- ckanext/publisher_form/forms.py | 72 ++++++++++++++++--------- 8 files changed, 104 insertions(+), 78 deletions(-) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index 5f16e2ae6b4..6e5700cbdb7 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -176,7 +176,7 @@ def _form_to_db_schema(self, group_type=None): def _db_to_form_schema(self, group_type=None): '''This is an interface to manipulate data from the database into a format suitable for the form (optional)''' - return _lookup_plugin(group_type).form_to_db_schema() + return _lookup_plugin(group_type).db_to_form_schema() def _setup_template_variables(self, context, data_dict, group_type=None): return _lookup_plugin(group_type).setup_template_variables(context,data_dict) diff --git a/ckan/lib/alphabet_paginate.py b/ckan/lib/alphabet_paginate.py index 4277867463f..5777d5c87bf 100644 --- a/ckan/lib/alphabet_paginate.py +++ b/ckan/lib/alphabet_paginate.py @@ -45,11 +45,15 @@ def __init__(self, collection, alpha_attribute, page, other_text, paging_thresho self.controller_name = controller_name self.available = dict( (c,0,) for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ) for c in self.collection: - x = c if isinstance( c, unicode ) else getattr(c, self.alpha_attribute)[0] + if isinstance(c, unicode): + x = c + elif isinstance(c, dict): + x = c[self.alpha_attribute][0] + else: + x = getattr(c, self.alpha_attribute)[0] 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: diff --git a/ckan/lib/authenticator.py b/ckan/lib/authenticator.py index 8eea54ae347..b56711a3427 100644 --- a/ckan/lib/authenticator.py +++ b/ckan/lib/authenticator.py @@ -5,7 +5,7 @@ class OpenIDAuthenticator(object): implements(IAuthenticator) - + def authenticate(self, environ, identity): if 'repoze.who.plugins.openid.userid' in identity: openid = identity.get('repoze.who.plugins.openid.userid') @@ -15,16 +15,16 @@ def authenticate(self, environ, identity): else: return user.name return None - + class UsernamePasswordAuthenticator(object): implements(IAuthenticator) - + def authenticate(self, environ, identity): if not 'login' in identity or not 'password' in identity: return None user = User.by_name(identity.get('login')) - if user is None: + if user is None: return None if user.validate_password(identity.get('password')): return user.name diff --git a/ckan/lib/dictization/__init__.py b/ckan/lib/dictization/__init__.py index 66d4abf6dec..42f748b07d6 100644 --- a/ckan/lib/dictization/__init__.py +++ b/ckan/lib/dictization/__init__.py @@ -3,7 +3,7 @@ import sqlalchemy from pylons import config -# NOTE +# NOTE # The functions in this file contain very generic methods for dictizing objects # and saving dictized objects. If a specialised use is needed please do NOT extend # these functions. Copy code from here as needed. @@ -68,7 +68,7 @@ def obj_list_dictize(obj_list, context, sort_key=lambda x:x): return sorted(result_list, key=sort_key) def obj_dict_dictize(obj_dict, context, sort_key=lambda x:x): - '''Get a dict whose values are model objects + '''Get a dict whose values are model objects and represent it as a list of dicts''' result_list = [] @@ -93,7 +93,7 @@ def get_unique_constraints(table, context): def table_dict_save(table_dict, ModelClass, context): '''Given a dict and a model class, update or create a sqlalchemy object. - This will use an existing object if "id" is supplied OR if any unique + This will use an existing object if "id" is supplied OR if any unique constraints are met. e.g supplying just a tag name will get out that tag obj. ''' @@ -107,7 +107,7 @@ def table_dict_save(table_dict, ModelClass, context): unique_constriants = get_unique_constraints(table, context) id = table_dict.get("id") - + if id: obj = session.query(ModelClass).get(id) diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index 2d60cc6a705..e5d51259eb0 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -12,7 +12,7 @@ ## package save -def group_list_dictize(obj_list, context, +def group_list_dictize(obj_list, context, sort_key=lambda x:x['display_name'], reverse=False): active = context.get('active', True) @@ -93,10 +93,10 @@ def _execute_with_revision(q, rev_table, context): But you can provide revision_id, revision_date or pending in the context and it will filter to an earlier time or the latest unmoderated object revision. - + Raises NotFound if context['revision_id'] is provided, but the revision ID does not exist. - + Returns [] if there are no results. ''' @@ -113,7 +113,7 @@ def _execute_with_revision(q, rev_table, context): if not revision: raise NotFound revision_date = revision.timestamp - + if revision_date: q = q.where(rev_table.c.revision_timestamp <= revision_date) q = q.where(rev_table.c.expired_timestamp > revision_date) @@ -133,7 +133,7 @@ def package_dictize(pkg, context): but you can provide revision_id, revision_date or pending in the context and it will filter to an earlier time or the latest unmoderated object revision. - + May raise NotFound. TODO: understand what the specific set of circumstances are that cause this. ''' @@ -148,7 +148,7 @@ def package_dictize(pkg, context): #resources res_rev = model.resource_revision_table resource_group = model.resource_group_table - q = select([res_rev], from_obj = res_rev.join(resource_group, + q = select([res_rev], from_obj = res_rev.join(resource_group, resource_group.c.id == res_rev.c.resource_group_id)) q = q.where(resource_group.c.package_id == pkg.id) result = _execute_with_revision(q, res_rev, context) @@ -156,7 +156,7 @@ def package_dictize(pkg, context): #tags tag_rev = model.package_tag_revision_table tag = model.tag_table - q = select([tag, tag_rev.c.state, tag_rev.c.revision_timestamp], + q = select([tag, tag_rev.c.state, tag_rev.c.revision_timestamp], from_obj=tag_rev.join(tag, tag.c.id == tag_rev.c.tag_id) ).where(tag_rev.c.package_id == pkg.id) result = _execute_with_revision(q, tag_rev, context) @@ -183,7 +183,7 @@ def package_dictize(pkg, context): q = select([rel_rev]).where(rel_rev.c.object_package_id == pkg.id) result = _execute_with_revision(q, rel_rev, context) result_dict["relationships_as_object"] = obj_list_dictize(result, context) - + # Extra properties from the domain object # We need an actual Package object for this, not a PackageRevision if isinstance(pkg,PackageRevision): @@ -264,10 +264,10 @@ def tag_dictize(tag, context): result_dict["packages"] = obj_list_dictize( tag.packages_ordered, context) - - return result_dict -def user_list_dictize(obj_list, context, + return result_dict + +def user_list_dictize(obj_list, context, sort_key=lambda x:x['name'], reverse=False): result_list = [] @@ -288,13 +288,13 @@ def user_dictize(user, context): result_dict = table_dictize(user, context) del result_dict['password'] - + result_dict['display_name'] = user.display_name result_dict['email_hash'] = user.email_hash result_dict['number_of_edits'] = user.number_of_edits() result_dict['number_administered_packages'] = user.number_administered_packages() - return result_dict + return result_dict def task_status_dictize(task_status, context): return table_dictize(task_status, context) @@ -302,23 +302,23 @@ def task_status_dictize(task_status, context): ## conversion to api def group_to_api1(group, context): - + dictized = group_dictize(group, context) - dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) + dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) for extra in dictized["extras"]) dictized["packages"] = sorted([package["name"] for package in dictized["packages"]]) return dictized def group_to_api2(group, context): - + dictized = group_dictize(group, context) - dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) + dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) for extra in dictized["extras"]) dictized["packages"] = sorted([package["id"] for package in dictized["packages"]]) return dictized def tag_to_api1(tag, context): - + dictized = tag_dictize(tag, context) return sorted([package["name"] for package in dictized["packages"]]) @@ -342,18 +342,18 @@ def package_to_api1(pkg, context): dictized["groups"] = [group["name"] for group in dictized["groups"]] dictized["tags"] = [tag["name"] for tag in dictized["tags"]] - dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) + dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) for extra in dictized["extras"]) dictized['notes_rendered'] = ckan.misc.MarkdownFormat().to_html(pkg.notes) - resources = dictized["resources"] - + resources = dictized["resources"] + for resource in resources: resource_dict_to_api(resource, pkg.id, context) if pkg.resources: dictized['download_url'] = pkg.resources[0].url - + dictized['license'] = pkg.license.title if pkg.license else None dictized['ratings_average'] = pkg.get_average_rating() @@ -368,9 +368,9 @@ def package_to_api1(pkg, context): dictized['metadata_created'] = metadata_created.isoformat() \ if metadata_created else None - subjects = dictized.pop("relationships_as_subject") - objects = dictized.pop("relationships_as_object") - + subjects = dictized.pop("relationships_as_subject") + objects = dictized.pop("relationships_as_object") + relationships = [] for relationship in objects: model = context['model'] @@ -386,9 +386,9 @@ def package_to_api1(pkg, context): 'type': relationship['type'], 'object': pkg.get(relationship['object_package_id']).name, 'comment': relationship["comment"]}) - - - dictized['relationships'] = relationships + + + dictized['relationships'] = relationships return dictized def package_to_api2(pkg, context): @@ -397,16 +397,16 @@ def package_to_api2(pkg, context): dictized["groups"] = [group["id"] for group in dictized["groups"]] dictized.pop("revision_timestamp") - + dictized["tags"] = [tag["name"] for tag in dictized["tags"]] - dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) + dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) for extra in dictized["extras"]) - resources = dictized["resources"] - + resources = dictized["resources"] + for resource in resources: resource_dict_to_api(resource,pkg.id, context) - + dictized['license'] = pkg.license.title if pkg.license else None dictized['ratings_average'] = pkg.get_average_rating() @@ -420,9 +420,9 @@ def package_to_api2(pkg, context): if pkg.metadata_created else None dictized['notes_rendered'] = ckan.misc.MarkdownFormat().to_html(pkg.notes) - subjects = dictized.pop("relationships_as_subject") - objects = dictized.pop("relationships_as_object") - + subjects = dictized.pop("relationships_as_subject") + objects = dictized.pop("relationships_as_object") + relationships = [] for relationship in objects: model = context['model'] @@ -438,8 +438,8 @@ def package_to_api2(pkg, context): 'type': relationship['type'], 'object': relationship['object_package_id'], 'comment': relationship["comment"]}) - - dictized['relationships'] = relationships + + dictized['relationships'] = relationships return dictized def activity_dictize(activity, context): diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 8ff532b746b..42a4aff6696 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -882,7 +882,7 @@ def get_site_user(context, data_dict): def roles_show(context, data_dict): '''Returns the roles that users (and authorization groups) have on a particular domain_object. - + If you specify a user (or authorization group) then the resulting roles will be filtered by those of that user (or authorization group). diff --git a/ckan/logic/schema.py b/ckan/logic/schema.py index aa2610309bb..5455ad11713 100644 --- a/ckan/logic/schema.py +++ b/ckan/logic/schema.py @@ -170,6 +170,8 @@ def default_group_schema(): '__extras': [ignore], 'packages': { "id": [not_empty, unicode, package_id_or_name_exists], + "name": [not_empty, unicode], + "title":[not_empty, unicode], "__extras": [ignore] } } @@ -184,9 +186,9 @@ def group_form_schema(): } schema['users'] = { "name": [not_empty, unicode], - "capacity": [ignore_missing], + "capacity": [ignore_missing], "__extras": [ignore] - } + } return schema diff --git a/ckanext/publisher_form/forms.py b/ckanext/publisher_form/forms.py index 216d9f6f2ca..a1a4f72d8b6 100644 --- a/ckanext/publisher_form/forms.py +++ b/ckanext/publisher_form/forms.py @@ -19,6 +19,7 @@ from ckan.lib.navl.dictization_functions import DataError, flatten_dict, unflatten from ckan.plugins import IDatasetForm, IGroupForm, IConfigurer from ckan.plugins import implements, SingletonPlugin +from ckan.logic import check_access from ckan.lib.navl.validators import (ignore_missing, not_empty, @@ -31,13 +32,13 @@ class PublisherForm(SingletonPlugin): """ - This plugin implements an IGroupForm for form associated with a + This plugin implements an IGroupForm for form associated with a publisher group. ``IConfigurer`` is used to add the local template path and the IGroupForm supplies the custom form. """ implements(IGroupForm, inherit=True) - implements(IConfigurer, inherit=True) - + implements(IConfigurer, inherit=True) + def update_config(self, config): """ This IConfigurer implementation causes CKAN to look in the @@ -49,12 +50,12 @@ def update_config(self, config): 'publisher_form', 'templates') config['extra_template_paths'] = ','.join([template_dir, config.get('extra_template_paths', '')]) - + def group_form(self): """ Returns a string representing the location of the template to be rendered. e.g. "forms/group_form.html". - """ + """ return 'publisher_form.html' def group_types(self): @@ -75,11 +76,11 @@ def is_fallback(self): Returns true iff this provides the fallback behaviour, when no other plugin instance matches a group's type. - As this is not the fallback controller we should return False. If + As this is not the fallback controller we should return False. If we were wanting to act as the fallback, we'd return True """ - return False - + return False + def form_to_db_schema(self): """ Returns the schema for mapping group data from a form to a format @@ -93,7 +94,7 @@ def db_to_form_schema(self): format suitable for the form (optional) """ return {} - + def check_data_dict(self, data_dict): """ Check if the return data is correct. @@ -104,22 +105,42 @@ def check_data_dict(self, data_dict): def setup_template_variables(self, context, data_dict): """ Add variables to c just prior to the template being rendered. We should - use the available groups for the current user, but should be optional + use the available groups for the current user, but should be optional in case this is a top level group - """ - #c.user_groups = c.userobj.get_groups('publisher') - c.user_groups = ['One', 'Two', 'Three'] - - + """ + c.is_sysadmin = Authorizer().is_sysadmin(c.user) + if 'group' in context: + group = context['group'] + + try: + check_access('group_update', context) + c.is_superuser_or_groupadmin = True + except NotAuthorized: + c.is_superuser_or_groupadmin = False + + c.possible_parents = model.Session.query(model.Group).\ + filter(model.Group.state == 'active').\ + filter(model.Group.type == 'publisher').\ + filter(model.Group.name != group.id ).order_by(model.Group.title).all() + + c.parent = None + grps = group.get_groups('publisher') + if grps: + c.parent = grps[0] + + c.users = group.members_of_type(model.User) + + + class PublisherDatasetForm(SingletonPlugin): """ - This plugin implements a new publisher form for cases where we + This plugin implements a new publisher form for cases where we want to enforce group (type=publisher) membership on a dataset. """ implements(IDatasetForm, inherit=True) - implements(IConfigurer, inherit=True) - + implements(IConfigurer, inherit=True) + def update_config(self, config): """ This IConfigurer implementation causes CKAN to look in the @@ -131,12 +152,12 @@ def update_config(self, config): 'publisher_form', 'templates') config['extra_template_paths'] = ','.join([template_dir, config.get('extra_template_paths', '')]) - + def package_form(self): """ Returns a string representing the location of the template to be rendered. e.g. "package/new_package_form.html". - """ + """ return 'dataset_form.html' def is_fallback(self): @@ -144,7 +165,7 @@ def is_fallback(self): Returns true iff this provides the fallback behaviour, when no other plugin instance matches a package's type. - As this is not the fallback controller we should return False. If + As this is not the fallback controller we should return False. If we were wanting to act as the fallback, we'd return True """ return True @@ -166,14 +187,14 @@ def setup_template_variables(self, context, data_dict=None): """ Adds variables to c just prior to the template being rendered that can then be used within the form - """ + """ c.licences = [('', '')] + model.Package.get_license_options() c.publishers = [('Example publisher', 'Example publisher 2')] c.is_sysadmin = Authorizer().is_sysadmin(c.user) c.resource_columns = model.Resource.get_columns() c.groups_available = c.userobj.get_groups('publisher') if c.userobj else [] - - + + ## This is messy as auths take domain object not data_dict pkg = context.get('package') or c.pkg if pkg: @@ -186,7 +207,7 @@ def form_to_db_schema(self): suitable for the database. """ return package_form_schema() - + def db_to_form_schema(data): """ Returns the schema for mapping package data from the database into a @@ -200,4 +221,3 @@ def check_data_dict(self, data_dict): """ pass - \ No newline at end of file From f7ca4757ce10d357e5095017b0e4c13e08920975 Mon Sep 17 00:00:00 2001 From: Ross Jones Date: Fri, 24 Feb 2012 11:53:18 +0000 Subject: [PATCH 30/84] Adding the body_class to publisher_form edit so that the autocompletes work --- ckanext/publisher_form/forms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ckanext/publisher_form/forms.py b/ckanext/publisher_form/forms.py index a1a4f72d8b6..aeac790bb45 100644 --- a/ckanext/publisher_form/forms.py +++ b/ckanext/publisher_form/forms.py @@ -108,6 +108,7 @@ def setup_template_variables(self, context, data_dict): use the available groups for the current user, but should be optional in case this is a top level group """ + c.body_class = "group edit" c.is_sysadmin = Authorizer().is_sysadmin(c.user) if 'group' in context: group = context['group'] From daf506bfb6ae8f8e15ea4ed90c8676708584fd53 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 24 Feb 2012 12:02:48 +0000 Subject: [PATCH 31/84] [#1506][editresources][s]: Name box dynamically updates in keeping with server side logic. --- ckan/public/scripts/application.js | 22 +++++++++++++++++----- ckan/public/scripts/templates.js | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index b13944f7afe..f305700d576 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -606,6 +606,7 @@ CKAN.Utils = function($, my) { }; return my; + }(jQuery, CKAN.Utils || {}); @@ -732,8 +733,17 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ }; // == Inner Functions: Update the name as you type == // - var setName = function(newName) { - newName = newName || (''+CKAN.Strings.noNameBrackets+''); + var setName = function(name,description) { + var newName = name; + if (!newName) { + newName = description; + if (!newName) { + newName = '[no name] ' + resource.attributes.id; + } + else if (newName.length>45) { + newName = description.substring(0,45)+'...'; + } + } // Need to structurally modify the DOM to force a re-render of text $link = $li.find('.js-resource-edit-name'); $link.html(''+newName+''); @@ -755,15 +765,17 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ } var nameBox = $table.find('input.js-resource-edit-name'); - CKAN.Utils.bindInputChanges(nameBox,function(e) { setName($(e.target).val()); }); + var descriptionBox = $table.find('textarea.js-resource-edit-description'); var formatBox = $table.find('input.js-resource-edit-format'); - CKAN.Utils.bindInputChanges(formatBox,function(e) { setFormat($(e.target).val()); }); + CKAN.Utils.bindInputChanges(nameBox, function() { setName(nameBox.val(),descriptionBox.val()); }); + CKAN.Utils.bindInputChanges(descriptionBox, function() { setName(nameBox.val(),descriptionBox.val()); }); + CKAN.Utils.bindInputChanges(formatBox, function() { setFormat(formatBox.val()); }); $li.find('.js-resource-edit-open').click(openTable); // TODO $li.find('.js-resource-edit-delete').click(deleteResource); // Initialise name - setName(resource.attributes.name); + setName(resource.attributes.name,resource.attributes.description); setFormat(resource.attributes.format); }, diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 94e2a6f56a6..128d6b1c020 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -91,7 +91,7 @@ CKAN.Templates.resourceDetails = ' \
    \ \ \ \ \ From 7e61fc045cd80a35cd82ff4cfd7bbd41530f7002 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 24 Feb 2012 13:33:08 +0000 Subject: [PATCH 32/84] [#1506][editresources][m]: Refactored add resource process into new ui --- ckan/public/css/style.css | 72 +++++++++++--------- ckan/public/scripts/application.js | 22 ++++-- ckan/templates/package/new_package_form.html | 24 ++++--- 3 files changed, 69 insertions(+), 49 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 22b5d433273..b9d2f291dc6 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1046,14 +1046,6 @@ body.editresources #sidebar { display: none; } -.dataset-editresources-form .resource-add { - background: #eee; - padding-top: 10px; - padding-bottom: 5px; - border: 1px solid #e0e0e0; - border-left: none; - border-right: none; -} .dataset-editresources-form .resource-add li h4 { display: inline; padding-right: 20px; @@ -1520,6 +1512,12 @@ body.authz form button { /* Dev */ +fieldset#resources { + min-height: 380px; + margin-bottom: 40px; + position: relative; + padding: 0; +} .resource-list { list-style-type: none; padding: 0; @@ -1531,6 +1529,8 @@ body.authz form button { border-radius: 4px; border: 1px solid #eee; position: relative; + margin-right: 20px; + z-index: 1; } .resource-list li a { display: block; @@ -1539,43 +1539,46 @@ body.authz form button { .resource-list li:hover { border-color: #ccc; } +.resource-list li.active { + border-color: #888; + border-right: 0; + background: #f9f9f9; + margin-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +hr.resource-list-divide { + margin: 10px 0; + width: 380px; + background: #eee; +} -fieldset#resources { - position: relative; - padding: 0; + +/* While dragging.... */ +.resource-list-edit li.ui-sortable-helper { + box-shadow: 2px 2px 2px rgba(0,0,0,0.1); +} +.resource-list-edit li.ui-sortable-helper.active { + margin-right: 20px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-right: 1px solid #888; } + .dataset-editresources-form .resource-list { width: 400px; - min-height: 300px; - margin-bottom: 40px; -} -li.resource-edit { - margin-right: 20px; - position: relative; - z-index: 1; - background: #fff; } -li.resource-edit:hover { - margin-right: 1px; - border-right: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 0; +.resource-list-add li a { + padding-left: 43px; } -li.resource-edit a { +.resource-list-edit li a { padding-left: 0; } -li.resource-edit:hover .drag-bars { +.resource-list-edit li:hover .drag-bars { color: #999; } -li.resource-edit.active { - border-color: #888; - border-right: 0; - background: #f9f9f9; - margin-right: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} + .resource-details-container { background: #f9f9f9; @@ -1586,6 +1589,7 @@ li.resource-edit.active { left: 399px; top: 0px; padding: 10px; + min-height: 300px; } .resource-details-container .resource-details-close { position: absolute; diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index f305700d576..57d042cc4c4 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -620,12 +620,24 @@ CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ resources.bind('remove', flashWarning); // Table for editing resources - var $el = this.el.find('.js-resource-list'); + var $el = this.el.find('.resource-list-edit'); this.resourceList=new CKAN.View.ResourceEditList({ collection: resources, el: $el }); + // Trigger the Add Resource pane + var $a = this.el.find('.js-resource-add'); + $a.click(function(e) { + e.preventDefault(); + $('.resource-list li').removeClass('active'); + $('.resource-list-add li').addClass('active'); + $('.js-resource-details').hide(); + $('.js-resource-details.resource-add').show(); + $('.js-resource-details-container').show(); + return false; + }); + // Tabbed view for adding resources var $el = this.el.find('.resource-add'); this.addView=new CKAN.View.ResourceAddTabs({ @@ -636,7 +648,7 @@ CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ // Close details button $('.resource-details-close').click(function(e) { e.preventDefault(); - $('.js-resource-list li').removeClass('active'); + $('.resource-list li').removeClass('active'); $('.resource-details-container').hide(); return false; }); @@ -715,9 +727,9 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ var container = $('.js-resource-details-container'); container.find('.js-resource-details').hide(); container.show(); - self.el.find('li.active').removeClass('active'); - - $li.data('table').show(); + $('.resource-list li').removeClass('active'); + $table.show(); + $table.find('.js-resource-edit-name').focus(); $li.addClass('active'); }; diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index a177df980e9..881e5ac59e2 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -82,18 +82,22 @@

    Errors in form

    -
      +
      -
      +
      + + -
      -
        -
      • Add a resource:

      • -
      • -
      • - -
      +
      +
        +
      • Add a resource:

      • +
      • +
      • + +
      +
    From 50cce99c85faa99a26e493191f72b692fe9e687f Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 24 Feb 2012 14:26:43 +0000 Subject: [PATCH 33/84] [#1506][m]: Bugfix. Saving the resource-free Settings form would wipe all resources. Simple fix. --- ckan/public/css/style.css | 4 ++++ ckan/public/scripts/application.js | 25 +++++++++++++++++--- ckan/public/scripts/templates.js | 7 ++---- ckan/templates/package/new_package_form.html | 9 +++++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index b9d2f291dc6..6784d737500 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1577,6 +1577,10 @@ hr.resource-list-divide { .resource-list-edit li:hover .drag-bars { color: #999; } +.resource-edit-delete { + float: right; + +} diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 57d042cc4c4..689101c387d 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -612,6 +612,9 @@ CKAN.Utils = function($, my) { CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ initialize: function() { + // Delete the barebones editor. We will populate our own form. + $('.js-resource-edit-barebones').remove(); + var resources = this.model.get('resources'); var $form = this.el; var flashWarning = CKAN.Utils.warnOnFormChanges($form); @@ -653,6 +656,7 @@ CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ return false; }); + this.resourceList.openFirst(); this.addView.render(); this.resourceList.render(); }, @@ -661,12 +665,22 @@ CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ CKAN.View.ResourceEditList = Backbone.View.extend({ initialize: function() { - _.bindAll(this, 'addResource', 'removeResource', 'sortStop'); + _.bindAll(this, 'addResource', 'removeResource', 'sortStop', 'openFirst'); this.collection.bind('add', this.addResource); this.collection.bind('remove', this.removeResource); this.collection.each(this.addResource); this.el.bind("sortstop", this.sortStop); - $(this.el.find('.resource-edit:first-child .js-resource-edit-open')).click(); + }, + + openFirst: function() { + var firstRow = $(this.el.find('.resource-edit:first-child')); + if (firstRow.length) { + firstRow.find('.js-resource-edit-open').click(); + } + else { + // Open the 'add resource' box + $('.js-resource-add').click(); + } }, sortStop: function(event,ui) { @@ -735,6 +749,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ // == Inner Function: Delete the row == // var deleteResource = function(triggerEvent) { + console.log('it aint over til its over'); if (triggerEvent) triggerEvent.preventDefault(); confirmMessage = CKAN.Strings.deleteThisResourceQuestion; resourceName = resource.attributes.name || CKAN.Strings.noNameBrackets; @@ -742,6 +757,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ if (confirm(confirmMessage)) { self.collection.remove(resource); } + return false; }; // == Inner Functions: Update the name as you type == // @@ -785,7 +801,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ $li.find('.js-resource-edit-open').click(openTable); // TODO - $li.find('.js-resource-edit-delete').click(deleteResource); + $table.find('.js-resource-edit-delete').click(deleteResource); // Initialise name setName(resource.attributes.name,resource.attributes.description); setFormat(resource.attributes.format); @@ -793,9 +809,12 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ removeResource: function(resource) { if (resource.view_row) { + var table = resource.view_row.data('table'); resource.view_row.remove(); + table.remove(); delete resource.view_row; } + this.openFirst(); }, }); diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 128d6b1c020..38ad5413ec6 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -71,11 +71,6 @@ CKAN.Templates.resourceEntry = ' \ \ ${resource.name}\ \ - \ '; CKAN.Templates.resourceDetails = ' \ @@ -164,6 +159,8 @@ CKAN.Templates.resourceDetails = ' \
    \ \
    '+CKAN.Strings.description+' \ - \ + \
    \ + \ + \
  • \ '; diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index 881e5ac59e2..1feca8e7c42 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -82,6 +82,15 @@

    Errors in form

    +
    + + + + + + +

    From 3c24bd720232277f6052b2070901ff4e484b69a3 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 24 Feb 2012 16:30:47 +0000 Subject: [PATCH 34/84] [#1506][m]: Refactoring js. --- ckan/public/scripts/application.js | 172 +++++++++++++++-------------- 1 file changed, 89 insertions(+), 83 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 689101c387d..f4f3a0493bf 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -87,8 +87,8 @@ // Backbone model/view var _dataset = new CKAN.Model.Dataset(preload_dataset); var $el=$('form#dataset-edit'); - var view=new CKAN.View.DatasetEditResourcesForm({ - model: _dataset, + var view=new CKAN.View.ResourceEditor({ + collection: _dataset.get('resources'), el: $el }); view.render(); @@ -610,27 +610,27 @@ CKAN.Utils = function($, my) { }(jQuery, CKAN.Utils || {}); -CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ +CKAN.View.ResourceEditor = Backbone.View.extend({ initialize: function() { + var $form = this.el; + + // Init bindings + _.bindAll(this, 'addResource', 'removeResource', 'sortStop', 'openFirst'); + this.collection.bind('add', this.addResource); + this.collection.bind('remove', this.removeResource); + this.collection.each(this.addResource); + $form.find('.resource-list-edit').bind("sortstop", this.sortStop); + // Delete the barebones editor. We will populate our own form. $('.js-resource-edit-barebones').remove(); - var resources = this.model.get('resources'); - var $form = this.el; + // Warn on form changes var flashWarning = CKAN.Utils.warnOnFormChanges($form); - - resources.bind('add', flashWarning); - resources.bind('remove', flashWarning); - - // Table for editing resources - var $el = this.el.find('.resource-list-edit'); - this.resourceList=new CKAN.View.ResourceEditList({ - collection: resources, - el: $el - }); + this.collection.bind('add', flashWarning); + this.collection.bind('remove', flashWarning); // Trigger the Add Resource pane - var $a = this.el.find('.js-resource-add'); + var $a = $form.find('.js-resource-add'); $a.click(function(e) { e.preventDefault(); $('.resource-list li').removeClass('active'); @@ -642,10 +642,10 @@ CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ }); // Tabbed view for adding resources - var $el = this.el.find('.resource-add'); + var $resourceAdd = $form.find('.resource-add'); this.addView=new CKAN.View.ResourceAddTabs({ - collection: resources, - el: $el + collection: this.collection, + el: $resourceAdd }); // Close details button @@ -656,24 +656,12 @@ CKAN.View.DatasetEditResourcesForm = Backbone.View.extend({ return false; }); - this.resourceList.openFirst(); - this.addView.render(); - this.resourceList.render(); + this.openFirst(); }, -}); -CKAN.View.ResourceEditList = Backbone.View.extend({ - initialize: function() { - _.bindAll(this, 'addResource', 'removeResource', 'sortStop', 'openFirst'); - this.collection.bind('add', this.addResource); - this.collection.bind('remove', this.removeResource); - this.collection.each(this.addResource); - this.el.bind("sortstop", this.sortStop); - }, - openFirst: function() { - var firstRow = $(this.el.find('.resource-edit:first-child')); + var firstRow = $(this.el.find('.resource-list-edit li:first-child')); if (firstRow.length) { firstRow.find('.js-resource-edit-open').click(); } @@ -683,8 +671,25 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ } }, + openTable: function(triggerEvent) { + triggerEvent.preventDefault(); + var $li = $(triggerEvent.target).parents('li'); + // Close all tables + var container = $('.js-resource-details-container'); + container.find('.js-resource-details').hide(); + $('.resource-list li').removeClass('active'); + container.show(); + var $table = $li.data('table'); + $table.show(); + $table.find('.js-resource-edit-name').focus(); + $li.addClass('active'); + return false; + }, + + + /* Update the resource__N__field names to match new sort order. */ sortStop: function(event,ui) { - $.each(this.el.find('li'), function(li_idx, li) { + $.each(this.el.find('.resource-list-edit li'), function(li_idx, li) { var $li = $(li); $table = $li.data('table'); $.each($table.find('input,textarea,select'), function(input_idx, input) { @@ -695,6 +700,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ }); }, + /* Id of the next resource to create */ nextIndex: function() { var maxId=-1; var root = $('.js-resource-details-container'); @@ -708,6 +714,7 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ return maxId+1; }, + /* Create DOM elements for new resource. */ addResource: function(resource) { var self = this; var position = this.nextIndex(); @@ -726,47 +733,47 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ , ['example', 'Example'] ] }; + // Generate DOM editor var $li = $($.tmpl(CKAN.Templates.resourceEntry, resource_object)); var $table = $($.tmpl(CKAN.Templates.resourceDetails, resource_object)); - this.el.append($li); - $('.js-resource-details-container').append($table); + this.el.find('.resource-list-edit').append($li); + this.el.find('.js-resource-details-container').append($table); $li.data('table', $table); // Associate the resource with the DOM object resource.view_row = $li; - var openTable = function(triggerEvent) { - if (triggerEvent) triggerEvent.preventDefault(); - // Close all tables - var container = $('.js-resource-details-container'); - container.find('.js-resource-details').hide(); - container.show(); - $('.resource-list li').removeClass('active'); - $table.show(); - $table.find('.js-resource-edit-name').focus(); - $li.addClass('active'); - }; + // Hook to changes in name/format + var setName = self.setName($li,resource.attributes.id); + var setFormat = self.setFormat($li); + var nameBox = $table.find('input.js-resource-edit-name'); + var descriptionBox = $table.find('textarea.js-resource-edit-description'); + var formatBox = $table.find('input.js-resource-edit-format'); + CKAN.Utils.bindInputChanges(nameBox, function() { setName(nameBox.val(),descriptionBox.val()); }); + CKAN.Utils.bindInputChanges(descriptionBox, function() { setName(nameBox.val(),descriptionBox.val()); }); + CKAN.Utils.bindInputChanges(formatBox, function() { setFormat(formatBox.val()); }); - // == Inner Function: Delete the row == // - var deleteResource = function(triggerEvent) { - console.log('it aint over til its over'); - if (triggerEvent) triggerEvent.preventDefault(); - confirmMessage = CKAN.Strings.deleteThisResourceQuestion; - resourceName = resource.attributes.name || CKAN.Strings.noNameBrackets; - confirmMessage = confirmMessage.replace('%name%', resourceName); - if (confirm(confirmMessage)) { - self.collection.remove(resource); - } - return false; - }; + // Hook to open link + var openLink = $li.find('.js-resource-edit-open') + openLink.click(this.openTable); + if (resource.isNew()) { + openLink.click(); + } + // Hook to delete button + $table.find('.js-resource-edit-delete').click(this.askToDeleteResource($li,resource)); + // Initialise name + setName(resource.attributes.name,resource.attributes.description); + setFormat(resource.attributes.format); + }, - // == Inner Functions: Update the name as you type == // - var setName = function(name,description) { + + setName: function($li,id) { + return function(name,description) { var newName = name; if (!newName) { newName = description; if (!newName) { - newName = '[no name] ' + resource.attributes.id; + newName = '[no name] ' + id; } else if (newName.length>45) { newName = description.substring(0,45)+'...'; @@ -776,35 +783,34 @@ CKAN.View.ResourceEditList = Backbone.View.extend({ $link = $li.find('.js-resource-edit-name'); $link.html(''+newName+''); }; - // == Inner function: Updates the icon as you type == // - var setFormat = function(newFormat) { + }, + + setFormat: function($li) { + // Capture parent $li. + return function(newFormat) { // Ask the server which icon to use $.getJSON('/api/2/util/format_icon?format='+newFormat, function(data) { if (data.icon) { + var $table = $li.data('table'); $li.find('.js-resource-icon').attr('src',data.icon); $table.find('.js-resource-icon').attr('src',data.icon); } }); - } - - // Trigger animation - if (resource.isNew()) { - openTable(); - } - - var nameBox = $table.find('input.js-resource-edit-name'); - var descriptionBox = $table.find('textarea.js-resource-edit-description'); - var formatBox = $table.find('input.js-resource-edit-format'); - CKAN.Utils.bindInputChanges(nameBox, function() { setName(nameBox.val(),descriptionBox.val()); }); - CKAN.Utils.bindInputChanges(descriptionBox, function() { setName(nameBox.val(),descriptionBox.val()); }); - CKAN.Utils.bindInputChanges(formatBox, function() { setFormat(formatBox.val()); }); + }; + }, - $li.find('.js-resource-edit-open').click(openTable); - // TODO - $table.find('.js-resource-edit-delete').click(deleteResource); - // Initialise name - setName(resource.attributes.name,resource.attributes.description); - setFormat(resource.attributes.format); + askToDeleteResource: function($li,resource) { + var self = this; + // Capture parent $li and resource object + return function(triggerEvent) { + triggerEvent.preventDefault(); + var resourceName = $li.find('.js-resource-edit-name span').html(); + var confirmMessage = CKAN.Strings.deleteThisResourceQuestion.replace('%name%', resourceName); + if (confirm(confirmMessage)) { + self.collection.remove(resource); + } + return false; + }; }, removeResource: function(resource) { From a0d6c29dbe6a1be0f041bad267a85c39619ab51d Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 24 Feb 2012 18:57:45 +0000 Subject: [PATCH 35/84] [#1506][editresources][m]: Refactored backbone views. --- ckan/public/css/style.css | 12 +- ckan/public/scripts/application.js | 321 ++++++++++--------- ckan/public/scripts/templates.js | 10 +- ckan/templates/package/new_package_form.html | 6 +- 4 files changed, 189 insertions(+), 160 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 6784d737500..55838bc0d49 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1564,8 +1564,8 @@ hr.resource-list-divide { border-bottom-right-radius: 4px; border-right: 1px solid #888; } - -.dataset-editresources-form .resource-list { +.resource-list-add, +.resource-list-edit { width: 400px; } .resource-list-add li a { @@ -1577,14 +1577,14 @@ hr.resource-list-divide { .resource-list-edit li:hover .drag-bars { color: #999; } -.resource-edit-delete { - float: right; +button.resource-edit-delete { + float: right; } -.resource-details-container { +.resource-panel { background: #f9f9f9; border: 1px solid #888; box-shadow: 2px 2px 4px #888; @@ -1595,7 +1595,7 @@ hr.resource-list-divide { padding: 10px; min-height: 300px; } -.resource-details-container .resource-details-close { +.resource-panel .resource-panel-close { position: absolute; right: -8px; top: -12px; diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index f4f3a0493bf..a8e79a7692f 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -612,98 +612,90 @@ CKAN.Utils = function($, my) { CKAN.View.ResourceEditor = Backbone.View.extend({ initialize: function() { - var $form = this.el; - // Init bindings - _.bindAll(this, 'addResource', 'removeResource', 'sortStop', 'openFirst'); - this.collection.bind('add', this.addResource); - this.collection.bind('remove', this.removeResource); - this.collection.each(this.addResource); - $form.find('.resource-list-edit').bind("sortstop", this.sortStop); + _.bindAll(this, 'resourceAdded', 'resourceRemoved', 'sortStop', 'openFirstPanel', 'closePanel', 'openAddPanel'); + this.collection.bind('add', this.resourceAdded); + this.collection.bind('remove', this.resourceRemoved); + this.collection.each(this.resourceAdded); + this.el.find('.resource-list-edit').bind("sortstop", this.sortStop); // Delete the barebones editor. We will populate our own form. - $('.js-resource-edit-barebones').remove(); + this.el.find('.js-resource-edit-barebones').remove(); // Warn on form changes - var flashWarning = CKAN.Utils.warnOnFormChanges($form); + var flashWarning = CKAN.Utils.warnOnFormChanges(this.el); this.collection.bind('add', flashWarning); this.collection.bind('remove', flashWarning); // Trigger the Add Resource pane - var $a = $form.find('.js-resource-add'); - $a.click(function(e) { - e.preventDefault(); - $('.resource-list li').removeClass('active'); - $('.resource-list-add li').addClass('active'); - $('.js-resource-details').hide(); - $('.js-resource-details.resource-add').show(); - $('.js-resource-details-container').show(); - return false; - }); + this.el.find('.js-resource-add').click(this.openAddPanel); // Tabbed view for adding resources - var $resourceAdd = $form.find('.resource-add'); + var $resourceAdd = this.el.find('.resource-add'); this.addView=new CKAN.View.ResourceAddTabs({ collection: this.collection, el: $resourceAdd }); // Close details button - $('.resource-details-close').click(function(e) { - e.preventDefault(); - $('.resource-list li').removeClass('active'); - $('.resource-details-container').hide(); - return false; - }); + this.el.find('.resource-panel-close').click(this.closePanel); - this.openFirst(); + this.openFirstPanel(); }, - - - openFirst: function() { - var firstRow = $(this.el.find('.resource-list-edit li:first-child')); - if (firstRow.length) { - firstRow.find('.js-resource-edit-open').click(); + /* + * Called when the page loads or the current resource is deleted. + * Reset page state to the first available edit panel. + */ + openFirstPanel: function() { + if (this.collection.length>0) { + this.collection.at(0).view.openMyPanel(); } else { - // Open the 'add resource' box - $('.js-resource-add').click(); + this.openAddPanel(); } }, - - openTable: function(triggerEvent) { - triggerEvent.preventDefault(); - var $li = $(triggerEvent.target).parents('li'); - // Close all tables - var container = $('.js-resource-details-container'); - container.find('.js-resource-details').hide(); - $('.resource-list li').removeClass('active'); - container.show(); - var $table = $li.data('table'); - $table.show(); - $table.find('.js-resource-edit-name').focus(); - $li.addClass('active'); - return false; + /* + * Open the 'Add New Resource' special-case panel on the right. + */ + openAddPanel: function(e) { + if (e) e.preventDefault(); + this.el.find('.resource-list li').removeClass('active'); + this.el.find('.resource-list-add li').addClass('active'); + this.el.find('.resource-details').hide(); + this.el.find('.resource-details.resource-add').show(); + this.el.find('.resource-panel').show(); }, - - - /* Update the resource__N__field names to match new sort order. */ - sortStop: function(event,ui) { - $.each(this.el.find('.resource-list-edit li'), function(li_idx, li) { - var $li = $(li); - $table = $li.data('table'); - $.each($table.find('input,textarea,select'), function(input_idx, input) { + /* + * Close the panel on the right. + */ + closePanel: function(e) { + if (e) e.preventDefault(); + this.el.find('.resource-list li').removeClass('active'); + this.el.find('.resource-panel').hide(); + }, + /* + * Update the resource__N__field names to match + * new sort order. + */ + sortStop: function(e,ui) { + this.collection.each(function(resource) { + // Ask the DOM for the new sort order + var index = resource.view.li.index(); + // Update the form element names + var table = resource.view.table; + $.each(table.find('input,textarea,select'), function(input_index, input) { var name = $(input).attr('name'); - name = name.replace(/(resources__)\d+(.*)/, '$1'+li_idx+'$2') + name = name.replace(/(resources__)\d+(.*)/, '$1'+index+'$2') $(input).attr('name',name); }); }); }, - - /* Id of the next resource to create */ + /* + * Calculate id of the next resource to create + */ nextIndex: function() { var maxId=-1; - var root = $('.js-resource-details-container'); + var root = this.el.find('.resource-panel'); root.find('input').each(function(idx,input) { var splitName=$(input).attr('name').split('__'); if (splitName.length>1) { @@ -713,15 +705,44 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ }); return maxId+1; }, - - /* Create DOM elements for new resource. */ - addResource: function(resource) { + /* + * Create DOM elements for new resource. + */ + resourceAdded: function(resource) { var self = this; - var position = this.nextIndex(); - // Create a row from the template + resource.view = new CKAN.View.Resource({ + position: this.nextIndex(), + model: resource, + callback_deleteMe: function() { self.collection.remove(resource); } + }); + this.el.find('.resource-list-edit').append(resource.view.li); + this.el.find('.resource-panel').append(resource.view.table); + if (resource.isNew()) { + resource.view.openMyPanel(); + } + }, + /* + * Destroy DOM elements for deleted resource. + */ + resourceRemoved: function(resource) { + resource.view.removeFromDom(); + delete resource.view; + this.openFirstPanel(); + }, +}); + + +/* + * Backbone view of a resource. + * Creates two DOM elements: The draggable li (left) and the tabular editor (right). + */ +CKAN.View.Resource = Backbone.View.extend({ + initialize: function() { + _.bindAll(this,'updateName','updateIcon','name','askToDelete','openMyPanel'); + // Generate DOM elements var resource_object = { - resource: resource.toTemplateJSON(), - num: position, + resource: this.model.toTemplateJSON(), + num: this.options.position, resource_icon: '/images/icons/page_white.png', resourceTypeOptions: [ ['file', 'Data File'] @@ -732,96 +753,104 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ , ['code', 'Code'] , ['example', 'Example'] ] - }; - // Generate DOM editor - var $li = $($.tmpl(CKAN.Templates.resourceEntry, resource_object)); - var $table = $($.tmpl(CKAN.Templates.resourceDetails, resource_object)); - this.el.find('.resource-list-edit').append($li); - this.el.find('.js-resource-details-container').append($table); - $li.data('table', $table); - - // Associate the resource with the DOM object - resource.view_row = $li; - - // Hook to changes in name/format - var setName = self.setName($li,resource.attributes.id); - var setFormat = self.setFormat($li); - var nameBox = $table.find('input.js-resource-edit-name'); - var descriptionBox = $table.find('textarea.js-resource-edit-description'); - var formatBox = $table.find('input.js-resource-edit-format'); - CKAN.Utils.bindInputChanges(nameBox, function() { setName(nameBox.val(),descriptionBox.val()); }); - CKAN.Utils.bindInputChanges(descriptionBox, function() { setName(nameBox.val(),descriptionBox.val()); }); - CKAN.Utils.bindInputChanges(formatBox, function() { setFormat(formatBox.val()); }); - - // Hook to open link - var openLink = $li.find('.js-resource-edit-open') - openLink.click(this.openTable); - if (resource.isNew()) { - openLink.click(); - } + }; + this.li = $($.tmpl(CKAN.Templates.resourceEntry, resource_object)); + this.table = $($.tmpl(CKAN.Templates.resourceDetails, resource_object)); + + // Hook to changes in name + this.nameBox = this.table.find('input.js-resource-edit-name'); + this.descriptionBox = this.table.find('textarea.js-resource-edit-description'); + CKAN.Utils.bindInputChanges(this.nameBox,this.updateName); + CKAN.Utils.bindInputChanges(this.descriptionBox,this.updateName); + // Hook to changes in format + this.formatBox = this.table.find('input.js-resource-edit-format'); + CKAN.Utils.bindInputChanges(this.formatBox,this.updateIcon); // Hook to delete button - $table.find('.js-resource-edit-delete').click(this.askToDeleteResource($li,resource)); - // Initialise name - setName(resource.attributes.name,resource.attributes.description); - setFormat(resource.attributes.format); - }, - + this.table.find('.js-resource-edit-delete').click(this.askToDelete); + // Hook to open panel link + this.li.find('.resource-open-my-panel').click(this.openMyPanel); - setName: function($li,id) { - return function(name,description) { - var newName = name; - if (!newName) { - newName = description; - if (!newName) { - newName = '[no name] ' + id; + // Set initial state + this.updateName(); + this.updateIcon(); + }, + /* + * Work out what I should be called. Rough-match + * of helpers.py:resource_display_name. + */ + name: function() { + var name = this.nameBox.val(); + if (!name) { + name = this.descriptionBox.val(); + if (!name) { + if (this.model.isNew()) { + name = '[new resource]'; } - else if (newName.length>45) { - newName = description.substring(0,45)+'...'; + else { + name = '[no name] ' + this.model.id; } } - // Need to structurally modify the DOM to force a re-render of text - $link = $li.find('.js-resource-edit-name'); - $link.html(''+newName+''); - }; + } + if (name.length>45) { + name = name.substring(0,45)+'...'; + } + return name; }, - - setFormat: function($li) { - // Capture parent $li. - return function(newFormat) { - // Ask the server which icon to use - $.getJSON('/api/2/util/format_icon?format='+newFormat, function(data) { - if (data.icon) { - var $table = $li.data('table'); - $li.find('.js-resource-icon').attr('src',data.icon); - $table.find('.js-resource-icon').attr('src',data.icon); - } - }); - }; + /* + * Called when the user types to update the name in + * my
  • to match the values. + */ + updateName: function() { + // Need to structurally modify the DOM to force a re-render of text + var $link = this.li.find('.js-resource-edit-name'); + $link.html(''+this.name()+''); }, - - askToDeleteResource: function($li,resource) { + /* + * Called when the user types to update the icon + * tags. Uses server API to select icon. + */ + updateIcon: function() { var self = this; - // Capture parent $li and resource object - return function(triggerEvent) { - triggerEvent.preventDefault(); - var resourceName = $li.find('.js-resource-edit-name span').html(); - var confirmMessage = CKAN.Strings.deleteThisResourceQuestion.replace('%name%', resourceName); - if (confirm(confirmMessage)) { - self.collection.remove(resource); + // AJAX to server API + $.getJSON('/api/2/util/format_icon?format='+this.formatBox.val(), function(data) { + if (data.icon) { + self.li.find('.js-resource-icon').attr('src',data.icon); + self.table.find('.js-resource-icon').attr('src',data.icon); } - return false; - }; + }); }, - - removeResource: function(resource) { - if (resource.view_row) { - var table = resource.view_row.data('table'); - resource.view_row.remove(); - table.remove(); - delete resource.view_row; + /* + * Closes all other panels on the right and opens my editor panel. + */ + openMyPanel: function(e) { + if (e) e.preventDefault(); + // Close all tables + var panel = this.table.parents('.resource-panel'); + panel.find('.resource-details').hide(); + this.li.parents('fieldset#resources').find('li').removeClass('active'); + panel.show(); + this.table.show(); + this.table.find('.js-resource-edit-name').focus(); + this.li.addClass('active'); + }, + /* + * Called when my delete button is clicked. Calls back to the parent + * resource editor. + */ + askToDelete: function(e) { + e.preventDefault(); + var confirmMessage = CKAN.Strings.deleteThisResourceQuestion.replace('%name%', this.name()); + if (confirm(confirmMessage)) { + this.options.callback_deleteMe(); } - this.openFirst(); }, + /* + * Called when my model is destroyed. Remove me from the page. + */ + removeFromDom: function() { + this.li.remove(); + this.table.remove(); + } }); diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 38ad5413ec6..d52e0b25ec7 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -66,15 +66,15 @@ CKAN.Templates.resourceUpload = ' \ CKAN.Templates.resourceEntry = ' \
  • \ - \ -
    |||
    \ - \ - ${resource.name}\ +
    \ +
    |||
    \ + \ + ${resource.name}\
    \
  • '; CKAN.Templates.resourceDetails = ' \ - +
    + Groups +
    +
    +
    + + + +
    + + +
    +
    +
    + +
    Add to:
    +
    + +
    +
    Cannot add any groups.
    +
    +
    +
    + Tags +
    +
    + +
    +
    Comma-separated terms that may link this dataset to similar ones. For more information on conventions, see this wiki page.
    +
    e.g. pollution, rivers, water quality
    +
    ${errors.get('tag_string', '')}
    @@ -113,43 +153,6 @@

    Add resources:

    -

    Groups

    -
    - - - -
    - - -
    -
    -
    - -
    Group
    -
    - -
    -
    Cannot add any groups.
    -
    -

    Tags

    -
    -
    -
    - -
    -
    Comma-separated terms that may link this dataset to similar ones. For more information on conventions, see this wiki page.
    -
    e.g. pollution, rivers, water quality
    -
    ${errors.get('tag_string', '')}
    -
    From db865bec1a6a8d6ed2d7722f847a5851ee3f6999 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 1 Mar 2012 13:26:45 +0000 Subject: [PATCH 54/84] [#1506][xl]: Refactoring js. --- ckan/public/scripts/application.js | 1087 +++++++++--------- ckan/templates/package/new_package_form.html | 2 +- 2 files changed, 561 insertions(+), 528 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 69a8beedb14..915976197c8 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -1,3 +1,8 @@ +var CKAN = CKAN || {}; + +/* ================================= */ +/* == Initialise CKAN Application == */ +/* ================================= */ (function ($) { $(document).ready(function () { CKAN.Utils.setupUserAutocomplete($('input.autocomplete-user')); @@ -54,14 +59,18 @@ var isDatasetNew = $('body.package.new').length > 0; if (isDatasetNew) { // Set up magic URL slug editor - CKAN.Utils.setupUrlEditor('package'); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'package' + }); $('#save').val(CKAN.Strings.addDataset); $("#title").focus(); } var isGroupNew = $('body.group.new').length > 0; if (isGroupNew) { // Set up magic URL slug editor - CKAN.Utils.setupUrlEditor('group'); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'group' + }); $('#save').val(CKAN.Strings.addGroup); $("#title").focus(); } @@ -69,7 +78,10 @@ var isDatasetEdit = $('body.package.edit').length > 0; if (isDatasetEdit) { CKAN.Utils.warnOnFormChanges($('form#dataset-edit')); - CKAN.Utils.setupUrlEditor('package',readOnly=true); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'package', + editMode: true + }); // Set up hashtag nagivigation CKAN.Utils.setupDatasetEditNavigation(); @@ -101,54 +113,32 @@ var isGroupEdit = $('body.group.edit').length > 0; if (isGroupEdit) { - CKAN.Utils.setupUrlEditor('group',readOnly=true); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'group', + editMode: true + }); } }); }(jQuery)); -var CKAN = CKAN || {}; - -CKAN.Utils = function($, my) { - // Animate the appearance of an element by expanding its height - my.animateHeight = function(element, animTime) { - if (!animTime) animTime = 350; - element.show(); - var finalHeight = element.height(); - element.height(0); - element.animate({height:finalHeight}, animTime); - } - - my.bindInputChanges = function(input, callback) { - input.keyup(callback); - input.keydown(callback); - input.keypress(callback); - input.change(callback); - }; - - my.setupWelcomeBanner = function(banner) { - var cookieName = 'ckan_killtopbar'; - var isKilled = ($.cookie(cookieName)!=null); - if (!isKilled) { - banner.show(); - // Bind to the close button - banner.find('.js-kill-button').live('click', function() { - $.cookie(cookieName, 'true', { expires: 365 }); - banner.hide(); - }); - } - }; - my.setupUrlEditor = function(slugType,readOnly) { +/* ============================== */ +/* == Backbone View: UrlEditor == */ +/* ============================== */ +CKAN.View.UrlEditor = Backbone.View.extend({ + initialize: function() { + var slugType = this.options.slugType; + var editMode = this.options.editMode; + + if (!editMode) var editMode = false; // Page elements to hook onto var titleInput = $('.js-title'); - var urlText = $('.js-url-text'); var urlSuffix = $('.js-url-suffix'); var urlInput = $('.js-url-input'); var validMsg = $('.js-url-is-valid'); if (titleInput.length==0) throw "No titleInput found."; - if (urlText.length==0) throw "No urlText found."; if (urlSuffix.length==0) throw "No urlSuffix found."; if (urlInput.length==0) throw "No urlInput found."; if (validMsg.length==0) throw "No validMsg found."; @@ -225,418 +215,63 @@ CKAN.Utils = function($, my) { }; }(); - if (readOnly) { - slug = urlInput.val(); - urlSuffix.html(''+slug+''); - } - else { - var editLink = $('.js-url-editlink'); - editLink.show(); - // Hook title changes to the input box - my.bindInputChanges(titleInput, titleChanged); - my.bindInputChanges(urlInput, urlChanged); - // Set up the form - urlChanged(); - - editLink.live('click',function(e) { - e.preventDefault(); - $('.js-url-viewmode').hide(); - $('.js-url-editmode').show(); - urlInput.select(); - urlInput.focus(); - }); - } - }; - - my.setupDatasetDeleteButton = function() { - var select = $('select.dataset-delete'); - select.attr('disabled','disabled'); - select.css({opacity: 0.3}); - $('button.dataset-delete').click(function(e) { - select.removeAttr('disabled'); - select.fadeTo('fast',1.0); - $(e.target).css({opacity:0}); - $(e.target).attr('disabled','disabled'); - return false; - }); - }; - - // Attach dataset autocompletion to provided elements - // - // Requires: jquery-ui autocomplete - my.setupPackageAutocomplete = function(elements) { - elements.autocomplete({ - minLength: 0, - source: function(request, callback) { - var url = '/dataset/autocomplete?q=' + request.term; - $.ajax({ - url: url, - success: function(data) { - // atm is a string with items broken by \n and item = title (name)|name - var out = []; - var items = data.split('\n'); - $.each(items, function(idx, value) { - var _tmp = value.split('|'); - var _newItem = { - label: _tmp[0], - value: _tmp[1] - }; - out.push(_newItem); - }); - callback(out); - } - }); - } - , select: function(event, ui) { - var input_box = $(this); - input_box.val(''); - var parent_dd = input_box.parent('dd'); - var old_name = input_box.attr('name'); - var field_name_regex = /^(\S+)__(\d+)__(\S+)$/; - var split = old_name.match(field_name_regex); - - var new_name = split[1] + '__' + (parseInt(split[2]) + 1) + '__' + split[3] - - input_box.attr('name', new_name) - input_box.attr('id', new_name) + var editLink = $('.js-url-editlink'); + editLink.show(); + // Hook title changes to the input box + CKAN.Utils.bindInputChanges(titleInput, titleChanged); + CKAN.Utils.bindInputChanges(urlInput, urlChanged); + // Set up the form + urlChanged(); - parent_dd.before( - '' + - '
    ' + ui.item.label + '
    ' - ); - } + editLink.live('click',function(e) { + e.preventDefault(); + $('.js-url-viewmode').hide(); + $('.js-url-editmode').show(); + urlInput.select(); + urlInput.focus(); }); - }; + }, +}); - // Attach tag autocompletion to provided elements - // - // Requires: jquery-ui autocomplete - my.setupTagAutocomplete = function(elements) { - elements - // don't navigate away from the field on tab when selecting an item - .bind( "keydown", function( event ) { - if ( event.keyCode === $.ui.keyCode.TAB && - $( this ).data( "autocomplete" ).menu.active ) { - event.preventDefault(); - } - }) - .autocomplete({ - minLength: 1, - source: function(request, callback) { - // here request.term is whole list of tags so need to get last - var _realTerm = request.term.split(',').pop().trim(); - var url = CKAN.SITE_URL + '/api/2/util/tag/autocomplete?incomplete=' + _realTerm; - $.getJSON(url, function(data) { - // data = { ResultSet: { Result: [ {Name: tag} ] } } (Why oh why?) - var tags = $.map(data.ResultSet.Result, function(value, idx) { - return value.Name; - }); - callback( - $.ui.autocomplete.filter(tags, _realTerm) - ); - }); - }, - focus: function() { - // prevent value inserted on focus - return false; - }, - select: function( event, ui ) { - var terms = this.value.split(','); - // remove the current input - terms.pop(); - // add the selected item - terms.push( " "+ui.item.value ); - // add placeholder to get the comma-and-space at the end - terms.push( " " ); - this.value = terms.join( "," ); - return false; - } - }); - }; - // Attach tag autocompletion to provided elements - // - // Requires: jquery-ui autocomplete - my.setupFormatAutocomplete = function(elements) { - elements.autocomplete({ - minLength: 1, - source: function(request, callback) { - var url = CKAN.SITE_URL + '/api/2/util/resource/format_autocomplete?incomplete=' + request.term; - $.getJSON(url, function(data) { - // data = { ResultSet: { Result: [ {Name: tag} ] } } (Why oh why?) - var formats = $.map(data.ResultSet.Result, function(value, idx) { - return value.Format; - }); - callback(formats); - }); - } - }); - }; +/* =================================== */ +/* == Backbone View: ResourceEditor == */ +/* =================================== */ +CKAN.View.ResourceEditor = Backbone.View.extend({ + initialize: function() { + // Init bindings + _.bindAll(this, 'resourceAdded', 'resourceRemoved', 'sortStop', 'openFirstPanel', 'closePanel', 'openAddPanel'); + this.collection.bind('add', this.resourceAdded); + this.collection.bind('remove', this.resourceRemoved); + this.collection.each(this.resourceAdded); + this.el.find('.resource-list-edit').bind("sortstop", this.sortStop); - my.setupPublisherUserAutocomplete = function(elements) { - elements.autocomplete({ - minLength: 2, - source: function(request, callback) { - var url = '/api/2/util/user/autocomplete?q=' + request.term; - $.getJSON(url, function(data) { - $.each(data, function(idx, userobj) { - var label = userobj.name; - if (userobj.fullname) { - label += ' [' + userobj.fullname + ']'; - } - userobj.label = label; - userobj.value = userobj.name; - }); - callback(data); - }); - }, - select: function(event, ui) { - var input_box = $(this); - input_box.val(''); - var parent_dd = input_box.parent('dd'); - var old_name = input_box.attr('name'); - var field_name_regex = /^(\S+)__(\d+)__(\S+)$/; - var split = old_name.match(field_name_regex); + // Delete the barebones editor. We will populate our own form. + this.el.find('.js-resource-edit-barebones').remove(); - var new_name = split[1] + '__' + (parseInt(split[2]) + 1) + '__' + split[3] - input_box.attr('name', new_name) - input_box.attr('id', new_name) + // Warn on form changes + var flashWarning = CKAN.Utils.warnOnFormChanges(this.el); + this.collection.bind('add', flashWarning); + this.collection.bind('remove', flashWarning); - var capacity = $("input:radio[name=add-user-capacity]:checked").val(); - parent_dd.before( - '' + - '' + - '
    ' + ui.item.label + '
    ' - ); + // Trigger the Add Resource pane + this.el.find('.js-resource-add').click(this.openAddPanel); - return false; // to cancel the event ;) - } + // Tabbed view for adding resources + var $resourceAdd = this.el.find('.resource-add'); + this.addView=new CKAN.View.ResourceAddTabs({ + collection: this.collection, + el: $resourceAdd }); - }; + // Close details button + this.el.find('.resource-panel-close').click(this.closePanel); - // Attach user autocompletion to provided elements - // - // Requires: jquery-ui autocomplete - my.setupUserAutocomplete = function(elements) { - elements.autocomplete({ - minLength: 2, - source: function(request, callback) { - var url = CKAN.SITE_URL + '/api/2/util/user/autocomplete?q=' + request.term; - $.getJSON(url, function(data) { - $.each(data, function(idx, userobj) { - var label = userobj.name; - if (userobj.fullname) { - label += ' [' + userobj.fullname + ']'; - } - userobj.label = label; - userobj.value = userobj.name; - }); - callback(data); - }); - } - }); - }; - - // Attach authz group autocompletion to provided elements - // - // Requires: jquery-ui autocomplete - my.setupAuthzGroupAutocomplete = function(elements) { - elements.autocomplete({ - minLength: 2, - source: function(request, callback) { - var url = CKAN.SITE_URL + '/api/2/util/authorizationgroup/autocomplete?q=' + request.term; - $.getJSON(url, function(data) { - $.each(data, function(idx, userobj) { - var label = userobj.name; - userobj.label = label; - userobj.value = userobj.name; - }); - callback(data); - }); - } - }); - }; - - my.setupGroupAutocomplete = function(elements) { - elements.autocomplete({ - minLength: 2, - source: function(request, callback) { - var url = CKAN.SITE_URL + '/api/2/util/group/autocomplete?q=' + request.term; - $.getJSON(url, function(data) { - $.each(data, function(idx, userobj) { - var label = userobj.name; - userobj.label = label; - userobj.value = userobj.name; - }); - callback(data); - }); - } - }); - }; - - my.setupMarkdownEditor = function(markdownEditor) { - // Markdown editor hooks - markdownEditor.find('button, div.markdown-preview').live('click', function(e) { - e.preventDefault(); - var $target = $(e.target); - // Extract neighbouring elements - var markdownEditor=$target.closest('.markdown-editor') - markdownEditor.find('button').removeClass('depressed'); - var textarea = markdownEditor.find('.markdown-input'); - var preview = markdownEditor.find('.markdown-preview'); - // Toggle the preview - if ($target.is('.js-markdown-preview')) { - $target.addClass('depressed'); - raw_markdown=textarea.val(); - preview.html(""+CKAN.Strings.loading+""); - $.post(CKAN.SITE_URL + "/api/util/markdown", { q: raw_markdown }, - function(data) { preview.html(data); } - ); - preview.width(textarea.width()) - preview.height(textarea.height()) - textarea.hide(); - preview.show(); - } else { - markdownEditor.find('.js-markdown-edit').addClass('depressed'); - textarea.show(); - preview.hide(); - textarea.focus(); - } - return false; - }); - }; - - // If notes field is more than 1 paragraph, just show the - // first paragraph with a 'Read more' link that will expand - // the div if clicked - my.setupNotesExtract = function() { - var notes = $('#content div.notes'); - var paragraphs = notes.find('#notes-extract > *'); - if (paragraphs.length==0) { - notes.hide(); - } - else if (paragraphs.length > 1) { - var remainder = notes.find('#notes-remainder'); - $.each(paragraphs,function(i,para) { - if (i > 0) remainder.append($(para).remove()); - }); - notes.find('#notes-toggle').show(); - notes.find('#notes-toggle button').click( - function(event){ - notes.find('#notes-toggle button').toggle(); - if ($(event.target).hasClass('more')) - remainder.slideDown(); - else - remainder.slideUp(); - return false; - } - ); - } - }; - - my.warnOnFormChanges = function() { - var boundToUnload = false; - return function($form) { - var flashWarning = function() { - if (boundToUnload) return; - boundToUnload = true; - // Bind to the window departure event - window.onbeforeunload = function () { - return CKAN.Strings.youHaveUnsavedChanges; - }; - } - // Hook form modifications to flashWarning - $form.find('input,select').live('change', function(e) { - $target = $(e.target); - // Entering text in the 'add' box does not represent a change - if ($target.closest('.resource-add').length==0) { - flashWarning(); - } - }); - // Don't stop us leaving - $form.submit(function() { - window.onbeforeunload = null; - }); - // Calling functions might hook to flashWarning - return flashWarning; - }; - }(); - - // Show/hide fieldset sections from the edit dataset form. - my.setupDatasetEditNavigation = function() { - - function showSection(sectionToShowId) { - $('.dataset-edit-form fieldset').hide(); - $('.dataset-edit-form fieldset#'+sectionToShowId).show(); - $('.dataset-edit-nav li a').removeClass('active'); - $('.dataset-edit-nav li a[href=#section-'+sectionToShowId+']').addClass('active'); - window.location.hash = 'section-'+sectionToShowId; - } - - // Set up initial form state - // Prefix="#section-" - var initialSection = window.location.hash.slice(9) || 'basic-information'; - showSection(initialSection); - - // Adjust form state on click - $('.dataset-edit-nav li a').live('click', function(e) { - var $el = $(e.target); - // Prefix="#section-" - var showMe = $el.attr('href').slice(9); - showSection(showMe); - return false; - }); - }; - - my.countObject = function(obj) { - var count=0; - $.each(obj, function() { - count++; - }); - return count; - }; - - return my; - -}(jQuery, CKAN.Utils || {}); - - -CKAN.View.ResourceEditor = Backbone.View.extend({ - initialize: function() { - // Init bindings - _.bindAll(this, 'resourceAdded', 'resourceRemoved', 'sortStop', 'openFirstPanel', 'closePanel', 'openAddPanel'); - this.collection.bind('add', this.resourceAdded); - this.collection.bind('remove', this.resourceRemoved); - this.collection.each(this.resourceAdded); - this.el.find('.resource-list-edit').bind("sortstop", this.sortStop); - - // Delete the barebones editor. We will populate our own form. - this.el.find('.js-resource-edit-barebones').remove(); - - // Warn on form changes - var flashWarning = CKAN.Utils.warnOnFormChanges(this.el); - this.collection.bind('add', flashWarning); - this.collection.bind('remove', flashWarning); - - // Trigger the Add Resource pane - this.el.find('.js-resource-add').click(this.openAddPanel); - - // Tabbed view for adding resources - var $resourceAdd = this.el.find('.resource-add'); - this.addView=new CKAN.View.ResourceAddTabs({ - collection: this.collection, - el: $resourceAdd - }); - - // Close details button - this.el.find('.resource-panel-close').click(this.closePanel); - - // Did we embed some form errors? - if ((typeof global_form_errors == 'object') && global_form_errors.resources) { - for (i in global_form_errors.resources) { - var resource_errors = global_form_errors.resources[i]; - this.collection.at(i).view.setErrors(resource_errors); + // Did we embed some form errors? + if ((typeof global_form_errors == 'object') && global_form_errors.resources) { + for (i in global_form_errors.resources) { + var resource_errors = global_form_errors.resources[i]; + this.collection.at(i).view.setErrors(resource_errors); } } // Initial state @@ -750,10 +385,9 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ }); -/* - * Backbone view of a resource. - * Creates two DOM elements: The draggable li (left) and the tabular editor (right). - */ +/* ============================== */ +/* == Backbone View: Resource == */ +/* ============================== */ CKAN.View.Resource = Backbone.View.extend({ initialize: function() { _.bindAll(this,'updateName','updateIcon','name','askToDelete','openMyPanel','setErrors','setupDynamicExtras','addDynamicExtra'); @@ -987,107 +621,505 @@ CKAN.View.Resource = Backbone.View.extend({ } }); +/* ===================================== */ +/* == Backbone View: ResourceAdd Tabs == */ +/* ===================================== */ +CKAN.View.ResourceAddTabs = Backbone.View.extend({ + initialize: function() { + _.bindAll(this, 'render', 'addNewResource', 'reset'); + }, + + render: function() { + }, + + events: { + 'click button': 'clickButton', + 'click input[name=reset]': 'reset' + }, + + reset: function() { + this.el.find('button').removeClass('depressed'); + if (this.subView != null) { + this.subView.remove(); + this.subView = null; + } + return false; + }, + + clickButton: function(e) { + e.preventDefault(); + var $target = $(e.target); + + if ($target.is('.depressed')) { + this.reset(); + } + else { + this.reset(); + $target.addClass('depressed'); + + var $subPane = $('
    ').addClass('subpane'); + this.el.append($subPane); + + var tempResource = new CKAN.Model.Resource({}); + + tempResource.bind('change', this.addNewResource); + // Open sub-pane + if ($target.is('.js-upload-file')) { + this.subView = new CKAN.View.ResourceUpload({ + el: $subPane, + model: tempResource, + // TODO: horrible reverse depedency ... + client: CKAN.UI.workspace.client + }); + } + else if ($target.is('.js-link-file') || $target.is('.js-link-api')) { + this.subView = new CKAN.View.ResourceAddLink({ + el: $subPane, + model: tempResource, + mode: ($target.is('.js-link-file'))? 'file' : 'api', + // TODO: horrible reverse depedency ... + client: CKAN.UI.workspace.client + }); + } + this.subView.render(); + } + }, + + addNewResource: function(tempResource) { + // Deep-copy the tempResource we had bound to + var resource=new CKAN.Model.Resource(tempResource.toJSON()); + + this.collection.add(resource); + this.reset(); + } +}); + +/* ================================================= */ +/* == Backbone View: ResourceAdd Link-To-Resource == */ +/* ================================================= */ +CKAN.View.ResourceAddLink = Backbone.View.extend({ + initialize: function(options) { + _.bindAll(this, 'render'); + this.mode = options.mode; + }, + + render: function() { + if (this.mode=='file') { + var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkFile); + } + else if (this.mode=='api') { + var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkApi); + } + $(this.el).html(tmpl); + return this; + }, + + events: { + 'submit form': 'setResourceInfo', + }, + + setResourceInfo: function(e) { + e.preventDefault(); + var urlVal=this.el.find('input[name=url]').val(); + this.model.set({url: urlVal, resource_type: this.mode}) + } +}); + + + +/* ================ */ +/* == CKAN.Utils == */ +/* ================ */ +CKAN.Utils = function($, my) { + // Animate the appearance of an element by expanding its height + my.animateHeight = function(element, animTime) { + if (!animTime) animTime = 350; + element.show(); + var finalHeight = element.height(); + element.height(0); + element.animate({height:finalHeight}, animTime); + } + + my.bindInputChanges = function(input, callback) { + input.keyup(callback); + input.keydown(callback); + input.keypress(callback); + input.change(callback); + }; + + my.setupWelcomeBanner = function(banner) { + + var cookieName = 'ckan_killtopbar'; + var isKilled = ($.cookie(cookieName)!=null); + if (!isKilled) { + banner.show(); + // Bind to the close button + banner.find('.js-kill-button').live('click', function() { + $.cookie(cookieName, 'true', { expires: 365 }); + banner.hide(); + }); + } + }; + + my.setupDatasetDeleteButton = function() { + var select = $('select.dataset-delete'); + select.attr('disabled','disabled'); + select.css({opacity: 0.3}); + $('button.dataset-delete').click(function(e) { + select.removeAttr('disabled'); + select.fadeTo('fast',1.0); + $(e.target).css({opacity:0}); + $(e.target).attr('disabled','disabled'); + return false; + }); + }; + + // Attach dataset autocompletion to provided elements + // + // Requires: jquery-ui autocomplete + my.setupPackageAutocomplete = function(elements) { + elements.autocomplete({ + minLength: 0, + source: function(request, callback) { + var url = '/dataset/autocomplete?q=' + request.term; + $.ajax({ + url: url, + success: function(data) { + // atm is a string with items broken by \n and item = title (name)|name + var out = []; + var items = data.split('\n'); + $.each(items, function(idx, value) { + var _tmp = value.split('|'); + var _newItem = { + label: _tmp[0], + value: _tmp[1] + }; + out.push(_newItem); + }); + callback(out); + } + }); + } + , select: function(event, ui) { + var input_box = $(this); + input_box.val(''); + var parent_dd = input_box.parent('dd'); + var old_name = input_box.attr('name'); + var field_name_regex = /^(\S+)__(\d+)__(\S+)$/; + var split = old_name.match(field_name_regex); + + var new_name = split[1] + '__' + (parseInt(split[2]) + 1) + '__' + split[3] + + input_box.attr('name', new_name) + input_box.attr('id', new_name) + + parent_dd.before( + '' + + '
    ' + ui.item.label + '
    ' + ); + } + }); + }; + + // Attach tag autocompletion to provided elements + // + // Requires: jquery-ui autocomplete + my.setupTagAutocomplete = function(elements) { + elements + // don't navigate away from the field on tab when selecting an item + .bind( "keydown", function( event ) { + if ( event.keyCode === $.ui.keyCode.TAB && + $( this ).data( "autocomplete" ).menu.active ) { + event.preventDefault(); + } + }) + .autocomplete({ + minLength: 1, + source: function(request, callback) { + // here request.term is whole list of tags so need to get last + var _realTerm = request.term.split(',').pop().trim(); + var url = CKAN.SITE_URL + '/api/2/util/tag/autocomplete?incomplete=' + _realTerm; + $.getJSON(url, function(data) { + // data = { ResultSet: { Result: [ {Name: tag} ] } } (Why oh why?) + var tags = $.map(data.ResultSet.Result, function(value, idx) { + return value.Name; + }); + callback( + $.ui.autocomplete.filter(tags, _realTerm) + ); + }); + }, + focus: function() { + // prevent value inserted on focus + return false; + }, + select: function( event, ui ) { + var terms = this.value.split(','); + // remove the current input + terms.pop(); + // add the selected item + terms.push( " "+ui.item.value ); + // add placeholder to get the comma-and-space at the end + terms.push( " " ); + this.value = terms.join( "," ); + return false; + } + }); + }; + + // Attach tag autocompletion to provided elements + // + // Requires: jquery-ui autocomplete + my.setupFormatAutocomplete = function(elements) { + elements.autocomplete({ + minLength: 1, + source: function(request, callback) { + var url = CKAN.SITE_URL + '/api/2/util/resource/format_autocomplete?incomplete=' + request.term; + $.getJSON(url, function(data) { + // data = { ResultSet: { Result: [ {Name: tag} ] } } (Why oh why?) + var formats = $.map(data.ResultSet.Result, function(value, idx) { + return value.Format; + }); + callback(formats); + }); + } + }); + }; + + my.setupPublisherUserAutocomplete = function(elements) { + elements.autocomplete({ + minLength: 2, + source: function(request, callback) { + var url = '/api/2/util/user/autocomplete?q=' + request.term; + $.getJSON(url, function(data) { + $.each(data, function(idx, userobj) { + var label = userobj.name; + if (userobj.fullname) { + label += ' [' + userobj.fullname + ']'; + } + userobj.label = label; + userobj.value = userobj.name; + }); + callback(data); + }); + }, + select: function(event, ui) { + var input_box = $(this); + input_box.val(''); + var parent_dd = input_box.parent('dd'); + var old_name = input_box.attr('name'); + var field_name_regex = /^(\S+)__(\d+)__(\S+)$/; + var split = old_name.match(field_name_regex); + + var new_name = split[1] + '__' + (parseInt(split[2]) + 1) + '__' + split[3] + input_box.attr('name', new_name) + input_box.attr('id', new_name) + + var capacity = $("input:radio[name=add-user-capacity]:checked").val(); + parent_dd.before( + '' + + '' + + '
    ' + ui.item.label + '
    ' + ); + + return false; // to cancel the event ;) + } + }); + }; -CKAN.View.ResourceAddTabs = Backbone.View.extend({ - initialize: function() { - _.bindAll(this, 'render', 'addNewResource', 'reset'); - }, - render: function() { - }, + // Attach user autocompletion to provided elements + // + // Requires: jquery-ui autocomplete + my.setupUserAutocomplete = function(elements) { + elements.autocomplete({ + minLength: 2, + source: function(request, callback) { + var url = CKAN.SITE_URL + '/api/2/util/user/autocomplete?q=' + request.term; + $.getJSON(url, function(data) { + $.each(data, function(idx, userobj) { + var label = userobj.name; + if (userobj.fullname) { + label += ' [' + userobj.fullname + ']'; + } + userobj.label = label; + userobj.value = userobj.name; + }); + callback(data); + }); + } + }); + }; - events: { - 'click button': 'clickButton', - 'click input[name=reset]': 'reset' - }, + // Attach authz group autocompletion to provided elements + // + // Requires: jquery-ui autocomplete + my.setupAuthzGroupAutocomplete = function(elements) { + elements.autocomplete({ + minLength: 2, + source: function(request, callback) { + var url = CKAN.SITE_URL + '/api/2/util/authorizationgroup/autocomplete?q=' + request.term; + $.getJSON(url, function(data) { + $.each(data, function(idx, userobj) { + var label = userobj.name; + userobj.label = label; + userobj.value = userobj.name; + }); + callback(data); + }); + } + }); + }; - reset: function() { - this.el.find('button').removeClass('depressed'); - if (this.subView != null) { - this.subView.remove(); - this.subView = null; - } - return false; - }, + my.setupGroupAutocomplete = function(elements) { + elements.autocomplete({ + minLength: 2, + source: function(request, callback) { + var url = CKAN.SITE_URL + '/api/2/util/group/autocomplete?q=' + request.term; + $.getJSON(url, function(data) { + $.each(data, function(idx, userobj) { + var label = userobj.name; + userobj.label = label; + userobj.value = userobj.name; + }); + callback(data); + }); + } + }); + }; - clickButton: function(e) { - e.preventDefault(); - var $target = $(e.target); + my.setupMarkdownEditor = function(markdownEditor) { + // Markdown editor hooks + markdownEditor.find('button, div.markdown-preview').live('click', function(e) { + e.preventDefault(); + var $target = $(e.target); + // Extract neighbouring elements + var markdownEditor=$target.closest('.markdown-editor') + markdownEditor.find('button').removeClass('depressed'); + var textarea = markdownEditor.find('.markdown-input'); + var preview = markdownEditor.find('.markdown-preview'); + // Toggle the preview + if ($target.is('.js-markdown-preview')) { + $target.addClass('depressed'); + raw_markdown=textarea.val(); + preview.html(""+CKAN.Strings.loading+""); + $.post(CKAN.SITE_URL + "/api/util/markdown", { q: raw_markdown }, + function(data) { preview.html(data); } + ); + preview.width(textarea.width()) + preview.height(textarea.height()) + textarea.hide(); + preview.show(); + } else { + markdownEditor.find('.js-markdown-edit').addClass('depressed'); + textarea.show(); + preview.hide(); + textarea.focus(); + } + return false; + }); + }; - if ($target.is('.depressed')) { - this.reset(); + // If notes field is more than 1 paragraph, just show the + // first paragraph with a 'Read more' link that will expand + // the div if clicked + my.setupNotesExtract = function() { + var notes = $('#content div.notes'); + var paragraphs = notes.find('#notes-extract > *'); + if (paragraphs.length==0) { + notes.hide(); } - else { - this.reset(); - $target.addClass('depressed'); + else if (paragraphs.length > 1) { + var remainder = notes.find('#notes-remainder'); + $.each(paragraphs,function(i,para) { + if (i > 0) remainder.append($(para).remove()); + }); + notes.find('#notes-toggle').show(); + notes.find('#notes-toggle button').click( + function(event){ + notes.find('#notes-toggle button').toggle(); + if ($(event.target).hasClass('more')) + remainder.slideDown(); + else + remainder.slideUp(); + return false; + } + ); + } + }; - var $subPane = $('
    ').addClass('subpane'); - this.el.append($subPane); + my.warnOnFormChanges = function() { + var boundToUnload = false; + return function($form) { + var flashWarning = function() { + if (boundToUnload) return; + boundToUnload = true; + // Bind to the window departure event + window.onbeforeunload = function () { + return CKAN.Strings.youHaveUnsavedChanges; + }; + } + // Hook form modifications to flashWarning + $form.find('input,select').live('change', function(e) { + $target = $(e.target); + // Entering text in the 'add' box does not represent a change + if ($target.closest('.resource-add').length==0) { + flashWarning(); + } + }); + // Don't stop us leaving + $form.submit(function() { + window.onbeforeunload = null; + }); + // Calling functions might hook to flashWarning + return flashWarning; + }; + }(); - var tempResource = new CKAN.Model.Resource({}); + // Show/hide fieldset sections from the edit dataset form. + my.setupDatasetEditNavigation = function() { - tempResource.bind('change', this.addNewResource); - // Open sub-pane - if ($target.is('.js-upload-file')) { - this.subView = new CKAN.View.ResourceUpload({ - el: $subPane, - model: tempResource, - // TODO: horrible reverse depedency ... - client: CKAN.UI.workspace.client - }); - } - else if ($target.is('.js-link-file') || $target.is('.js-link-api')) { - this.subView = new CKAN.View.ResourceAddLink({ - el: $subPane, - model: tempResource, - mode: ($target.is('.js-link-file'))? 'file' : 'api', - // TODO: horrible reverse depedency ... - client: CKAN.UI.workspace.client - }); - } - this.subView.render(); + function showSection(sectionToShowId) { + $('.dataset-edit-form fieldset').hide(); + $('.dataset-edit-form fieldset#'+sectionToShowId).show(); + $('.dataset-edit-nav li a').removeClass('active'); + $('.dataset-edit-nav li a[href=#section-'+sectionToShowId+']').addClass('active'); + window.location.hash = 'section-'+sectionToShowId; } - }, - addNewResource: function(tempResource) { - // Deep-copy the tempResource we had bound to - var resource=new CKAN.Model.Resource(tempResource.toJSON()); + // Set up initial form state + // Prefix="#section-" + var initialSection = window.location.hash.slice(9) || 'basic-information'; + showSection(initialSection); - this.collection.add(resource); - this.reset(); - } -}); + // Adjust form state on click + $('.dataset-edit-nav li a').live('click', function(e) { + var $el = $(e.target); + // Prefix="#section-" + var showMe = $el.attr('href').slice(9); + showSection(showMe); + return false; + }); + }; -CKAN.View.ResourceAddLink = Backbone.View.extend({ - initialize: function(options) { - _.bindAll(this, 'render'); - this.mode = options.mode; - }, + my.countObject = function(obj) { + var count=0; + $.each(obj, function() { + count++; + }); + return count; + }; - render: function() { - if (this.mode=='file') { - var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkFile); - } - else if (this.mode=='api') { - var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkApi); - } - $(this.el).html(tmpl); - return this; - }, + return my; - events: { - 'submit form': 'setResourceInfo', - }, +}(jQuery, CKAN.Utils || {}); - setResourceInfo: function(e) { - e.preventDefault(); - var urlVal=this.el.find('input[name=url]').val(); - this.model.set({url: urlVal, resource_type: this.mode}) - } -}); -(function ($) { - var my = {}; + +/* ==================== */ +/* == Data Previewer == */ +/* ==================== */ +CKAN.DataPreview = function ($, my) { my.jsonpdataproxyUrl = 'http://jsonpdataproxy.appspot.com/'; my.dialogId = 'ckanext-datapreview'; my.$dialog = $('#' + my.dialogId); @@ -1305,5 +1337,6 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ // Export the CKANEXT object onto the window. $.extend(true, window, {CKANEXT: {}}); CKANEXT.DATAPREVIEW = my; -}(jQuery)); + return my; +}(jQuery, CKAN.DataPreview || {}); diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index 69f57ce3b90..b6a2f3ffda1 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -42,7 +42,7 @@

    Errors in form

    - ${h.url(controller='package', action='index')+'/'}  + ${h.url(controller='package', action='index')+'/'} 

     

    From fd77374065102bfd20bfcf9dd6b5d90c32c74fcf Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 1 Mar 2012 14:06:15 +0000 Subject: [PATCH 55/84] [#1506][js][l]: UrlEditor is a backbone model; various ux tweaks --- ckan/public/scripts/application.js | 204 ++++++++++--------- ckan/templates/group/new_group_form.html | 4 +- ckan/templates/js_strings.html | 1 + ckan/templates/package/new_package_form.html | 4 +- 4 files changed, 115 insertions(+), 98 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 915976197c8..c92b1270e9e 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -128,109 +128,125 @@ var CKAN = CKAN || {}; /* ============================== */ CKAN.View.UrlEditor = Backbone.View.extend({ initialize: function() { - var slugType = this.options.slugType; - var editMode = this.options.editMode; - - if (!editMode) var editMode = false; - // Page elements to hook onto - var titleInput = $('.js-title'); - var urlSuffix = $('.js-url-suffix'); - var urlInput = $('.js-url-input'); - var validMsg = $('.js-url-is-valid'); - - if (titleInput.length==0) throw "No titleInput found."; - if (urlSuffix.length==0) throw "No urlSuffix found."; - if (urlInput.length==0) throw "No urlInput found."; - if (validMsg.length==0) throw "No validMsg found."; - - var api_url = CKAN.SITE_URL + '/api/2/util/is_slug_valid'; - // (make length less than max, in case we need a few for '_' chars to de-clash slugs.) - var MAX_SLUG_LENGTH = 90; - - var titleChanged = function() { - var lastTitle = ""; - var regexToHyphen = [ new RegExp('[ .:/_]', 'g'), - new RegExp('[^a-zA-Z0-9-_]', 'g'), - new RegExp('-+', 'g')]; - var regexToDelete = [ new RegExp('^-*', 'g'), - new RegExp('-*$', 'g')]; - - var titleToSlug = function(title) { - var slug = title; - $.each(regexToHyphen, function(idx,regex) { slug = slug.replace(regex, '-'); }); - $.each(regexToDelete, function(idx,regex) { slug = slug.replace(regex, ''); }); - slug = slug.toLowerCase(); - - if (slug.length'+CKAN.Strings.urlIsAvailable+''); - } else { - validMsg.html(''+CKAN.Strings.urlIsNotAvailable+''); - } - } - }); - } + toEditMode: function(event) { + $('.js-url-viewmode').hide(); + $('.js-url-editmode').show(); + if (event) { + // If we clicked a link, highlight the input + event.preventDefault(); + this.urlInput.select(); + this.urlInput.focus(); + } + // Disable automatic slug generation + this.disableTitleChanged = true; + }, - return function() { - slug = urlInput.val(); - urlSuffix.html(''+slug+''); - if (timer) clearTimeout(timer); - if (slug.length<2) { - validMsg.html(''+CKAN.Strings.urlIsTooShort+''); - } - else { - validMsg.html(''+CKAN.Strings.checking+''); - timer = setTimeout(function () { - checkSlugValid(slug); - }, 200); - } - }; - }(); + titleToSlug: function(title) { + var slug = title; + $.each(this.regexToHyphen, function(idx,regex) { slug = slug.replace(regex, '-'); }); + $.each(this.regexToDelete, function(idx,regex) { slug = slug.replace(regex, ''); }); + slug = slug.toLowerCase(); - var editLink = $('.js-url-editlink'); - editLink.show(); - // Hook title changes to the input box - CKAN.Utils.bindInputChanges(titleInput, titleChanged); - CKAN.Utils.bindInputChanges(urlInput, urlChanged); - // Set up the form - urlChanged(); + if (slug.length'+slug+''); + if (this.updateTimer) clearTimeout(this.updateTimer); + if (slug.length<2) { + this.validMsg.html(''+CKAN.Strings.urlIsTooShort+''); + } + else if (this.options.editMode && slug==this.originalUrl) { + this.validMsg.html(''+CKAN.Strings.urlIsUnchanged+''); + } + else { + this.validMsg.html(''+CKAN.Strings.checking+''); + var self = this; + this.updateTimer = setTimeout(function () { + self.checkSlugIsValid(slug); + }, 200); + } + }, + + checkSlugIsValid: function(slug) { + $.ajax({ + url: this.options.apiUrl, + data: 'type='+this.options.slugType+'&slug=' + slug, + dataType: 'jsonp', + type: 'get', + jsonpCallback: 'callback', + success: this.apiCallback }); }, + + /* Called when the slug-validator gets back to us */ + apiCallback: function(data) { + if (data.valid) { + this.validMsg.html(''+CKAN.Strings.urlIsAvailable+''); + } else { + this.validMsg.html(''+CKAN.Strings.urlIsNotAvailable+''); + } + }, }); diff --git a/ckan/templates/group/new_group_form.html b/ckan/templates/group/new_group_form.html index 8a3b58a78a2..599cf6aa7a0 100644 --- a/ckan/templates/group/new_group_form.html +++ b/ckan/templates/group/new_group_form.html @@ -8,7 +8,7 @@

    Errors in form

    The form contains invalid entries:

      -
    • ${"%s: %s" % (key, error)}
    • +
    • ${"%s: %s" % (key if not key=='Name' else 'URL', error)}
    @@ -19,7 +19,7 @@

    Errors in form

    - ${h.url(controller='group', action='index')+'/'}  + ${h.url(controller='group', action='index')+'/'} (edit)

     

    diff --git a/ckan/templates/js_strings.html b/ckan/templates/js_strings.html index 6a00beee9fb..104f9f360ae 100644 --- a/ckan/templates/js_strings.html +++ b/ckan/templates/js_strings.html @@ -16,6 +16,7 @@ CKAN.Strings.helloWorld = "${_('Hello there, world!')}"; 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.bracketsNone = "${_('(none)')}"; diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index b6a2f3ffda1..7f653d079d8 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -9,7 +9,7 @@

    Errors in form

    The form contains invalid entries:

      -
    • ${"%s: %s" % (key, error)} +
    • ${"%s: %s" % (key if not key=='Name' else 'URL', error)}
        @@ -42,7 +42,7 @@

        Errors in form

        - ${h.url(controller='package', action='index')+'/'}  + ${h.url(controller='package', action='index')+'/'} (edit)

         

        From 4a74dbfd6637a08c9416b3cbbe784847e63841e3 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 1 Mar 2012 14:42:04 +0000 Subject: [PATCH 56/84] [#1506][js][s]: Warn on very long URL slugs. --- ckan/public/css/style.css | 6 ++++++ ckan/public/scripts/application.js | 7 +++++++ ckan/templates/group/new_group_form.html | 1 + ckan/templates/package/new_package_form.html | 1 + 4 files changed, 15 insertions(+) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index ba13d656e94..5c1293adbde 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1115,6 +1115,12 @@ a.url-edit { font-weight: normal; margin-left: 10px; } +p.url-is-long { + color: #600; + display: none; + font-size: 11px; + font-weight: bold; +} /* ===================== */ diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index c92b1270e9e..7390287194c 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -136,6 +136,7 @@ CKAN.View.UrlEditor = Backbone.View.extend({ this.urlSuffix = $('.js-url-suffix'); this.urlInput = $('.js-url-input'); this.validMsg = $('.js-url-is-valid'); + this.lengthMsg = $('.url-is-long'); this.lastTitle = ""; this.disableTitleChanged = false; @@ -226,6 +227,12 @@ CKAN.View.UrlEditor = Backbone.View.extend({ self.checkSlugIsValid(slug); }, 200); } + if (slug.length>20) { + this.lengthMsg.show(); + } + else { + this.lengthMsg.hide(); + } }, checkSlugIsValid: function(slug) { diff --git a/ckan/templates/group/new_group_form.html b/ckan/templates/group/new_group_form.html index 599cf6aa7a0..867b76abc6f 100644 --- a/ckan/templates/group/new_group_form.html +++ b/ckan/templates/group/new_group_form.html @@ -22,6 +22,7 @@

        Errors in form

        ${h.url(controller='group', action='index')+'/'} (edit)

         

        +

        Warning: URL is very long. Consider changing it to something shorter.

        ${errors.get('name', '')}
        diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index 7f653d079d8..a2d7740854a 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -45,6 +45,7 @@

        Errors in form

        ${h.url(controller='package', action='index')+'/'} (edit)

         

        +

        Warning: URL is very long. Consider changing it to something shorter.

        ${errors.get('name', '')}
        From 385ffcd85c1f95c855ec73db94eb40c10404cbf0 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 1 Mar 2012 14:52:21 +0000 Subject: [PATCH 57/84] [#1648 in #1506][settings][s]: Clarified terminology on dataset extra fields. --- ckan/templates/package/edit.html | 2 +- ckan/templates/package/new_package_form.html | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ckan/templates/package/edit.html b/ckan/templates/package/edit.html index 2b2da7d4751..931a14d737a 100644 --- a/ckan/templates/package/edit.html +++ b/ckan/templates/package/edit.html @@ -14,7 +14,7 @@
      • Basic Information
      • Further Information
      • -
      • Extras
      • +
      • Extra Fields
      • Delete
    • diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index a2d7740854a..e514db052bf 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -153,8 +153,6 @@

      Add resources:

    -
    -
    @@ -180,6 +178,7 @@

    Add resources:

    +

    Adding custom fields to the dataset such as "location:uk" can help users find it in the search engine. This data will also appear under Additional Information when viewing the dataset.

    From f3fbd7075fb208c143867f8ab1be86c00ea206b0 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 1 Mar 2012 15:17:10 +0000 Subject: [PATCH 58/84] [#1506][js][s]: Fixed animation glitch. --- ckan/public/css/style.css | 9 +++++++++ ckan/public/scripts/application.js | 6 ++++-- ckan/templates/group/read.html | 6 +++--- ckan/templates/package/read_core.html | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 5c1293adbde..9c7822a19a3 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -261,6 +261,11 @@ img.gravatar { cursor: move; } +ul.no-break li { + white-space: nowrap; + overflow: hidden; +} + /* =============== */ /* MinorNavigation */ /* =============== */ @@ -1265,6 +1270,10 @@ body.package.read #sidebar li.widget-container { #notes-extract p { margin-bottom: 0; } +#notes-remainder { + overflow: hidden; + padding: 0 8px; +} #notes-remainder p:last-child { margin-bottom: 0; } diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 7390287194c..eed23922658 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -1057,14 +1057,16 @@ CKAN.Utils = function($, my) { $.each(paragraphs,function(i,para) { if (i > 0) remainder.append($(para).remove()); }); + var finalHeight = remainder.height(); + remainder.height(0); notes.find('#notes-toggle').show(); notes.find('#notes-toggle button').click( function(event){ notes.find('#notes-toggle button').toggle(); if ($(event.target).hasClass('more')) - remainder.slideDown(); + remainder.animate({'height':finalHeight}); else - remainder.slideUp(); + remainder.animate({'height':0}); return false; } ); diff --git a/ckan/templates/group/read.html b/ckan/templates/group/read.html index 7915b5fdcf2..fa1d458392f 100644 --- a/ckan/templates/group/read.html +++ b/ckan/templates/group/read.html @@ -14,8 +14,8 @@
  • Administrators

    -
  • -

    Add resources:

    +
    +

    Add resources:

    +

    Upload or link data files, APIs and other materials related to your dataset.

    +
    @@ -216,13 +219,13 @@

    Add resources:

    - - + + -