From 59d3f758c63cc3a5f8da29e8549c6388b48264ff Mon Sep 17 00:00:00 2001 From: John Glover Date: Mon, 21 Nov 2011 17:09:19 +0000 Subject: [PATCH 01/70] [ux, dataset-view] Change main resource link on dataset view page to point to resource view page. Remove info from dataset view page that will be on the 'more info' tab Restyle format tag and move to after resource name --- ckan/public/css/style.css | 19 ++- ckan/templates/package/read.html | 194 +++++++++++--------------- ckan/templates/package/read_core.html | 64 ++++----- 3 files changed, 132 insertions(+), 145 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 6d4d7835f76..74c39da8567 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -982,27 +982,35 @@ body.package.read #sidebar ul.groups { margin-bottom: 10px; } body.package.read #dataset-resources { + margin-top: 2em; margin-bottom: 2em; } body.package.read .dataset-resource { margin-bottom: 1em; } -body.package.read .resource-url { +body.package.read .resource-view-url { font-size: 1.3em; } +body.package.read .resource-url { + color: #808080; +} +body.package.read .resource-url a { + color: #808080; +} body.package.read .resource-information { padding-left: 0; + color: #808080; } body.package.read .resource-information li { display: inline; margin-right: 4px; - padding: 2px; border: none; font-weight: normal; - color: #808080; } body.package.read .resource-format { - background:#ececec; + border: 1px solid #ececec; + padding: 2px; + color: #808080; } .flash-messages .success .new-dataset { font-size: 150%; @@ -1010,6 +1018,9 @@ body.package.read .resource-format { .flash-messages.success .new-dataset a { font-weight: bold; } +body.package.read #sidebar li.widget-container { + border: 0 +} /* ====================== */ diff --git a/ckan/templates/package/read.html b/ckan/templates/package/read.html index cdffe4a84ef..a7400c6a7e7 100644 --- a/ckan/templates/package/read.html +++ b/ckan/templates/package/read.html @@ -20,101 +20,101 @@
  • -
  • - -

    - This Dataset is Open -

    -

    - License: - - ${c.pkg.license.title.split('::')[-1]} - ${c.pkg.license.title} - ${c.pkg.license_id} - -

    -

    - - [Open Data] -

    -
    + + + + + + + + + + + + + + + + + + + + + - -

    This dataset is Not Open

    + + -

    Either because it is not openly licensed or is missing - downloadable resources.

    -

    - Start an enquiry on IsItOpenData » -

    -
    -
  • + + + + + + +
    @@ -126,8 +126,6 @@

    This dataset is Not Open

    - -
    @@ -139,28 +137,6 @@

    This dataset is Not Open

    - - - - - - -
    - - - - - - - - - - - diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 431b33e61c8..81c95a89d77 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -14,21 +14,21 @@

    Resources

    -
    -
    +
    + + ${res.get('name')} + (no name) + + ${res.get('format', '(unknown format)')} +
    ${h.markdown_extract(res.get('description'))}
    + -
    ${h.markdown_extract(res.get('description'))}
      -
    • ${res.get('format', '(unknown format)')}
    • Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago
    • @@ -51,27 +51,27 @@

      Resources

      -
      -

      Additional Information

      - - - - - - - - - - - - - - - - -
      FieldValue
      ${_(key)}${value}
      (none)
      -
      + + + + + + + + + + + + + + + + + + + + +
    From c56766583558441032ab55d5df5115dc0054ff4c Mon Sep 17 00:00:00 2001 From: John Glover Date: Tue, 22 Nov 2011 11:00:35 +0000 Subject: [PATCH 02/70] [ux, resource-view] add initial dataset view tabs --- ckan/public/css/style.css | 39 ++++++- ckan/templates/package/read_core.html | 146 +++++++++++++++----------- 2 files changed, 120 insertions(+), 65 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 74c39da8567..8a121ea04a7 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -977,10 +977,19 @@ td.resource-edit-value { /* ===================== */ /* = Dataset View Page = */ /* ===================== */ +body.package.read #content { + border: 0; +} body.package.read #sidebar ul.tags, body.package.read #sidebar ul.groups { margin-bottom: 10px; } +body.package.read #dataset { + border-left: 1px solid #ececec; + border-right: 1px solid #ececec; + border-bottom: 1px solid #ececec; + padding: 12px; +} body.package.read #dataset-resources { margin-top: 2em; margin-bottom: 2em; @@ -1021,6 +1030,34 @@ body.package.read .resource-format { body.package.read #sidebar li.widget-container { border: 0 } +body.package.read #dataset-tab-bar { + margin-top: 1em; + border-bottom: 1px solid #ececec; +} +body.package.read #dataset-tabs { + padding: 0; + margin: 0; +} +body.package.read #dataset-tabs li { + display: inline-block; + cursor: pointer; + font-size: 14px; +} +body.package.read #dataset-tabs a { + padding: 8px 12px 8px 12px; + color: #808080; + /* border: 1px transparent solid #ffffff; */ + /* border-right: none; */ + display: inline-block; + margin-bottom: -1px; +} +body.package.read #dataset-tabs a.selected { + color: #000000; + border-left: 1px solid #e0e0e0; + border-right: 1px solid #e0e0e0; + border-top: 1px solid #e0e0e0; + border-bottom: 1px solid #ffffff; +} /* ====================== */ @@ -1040,7 +1077,7 @@ body.package.resource_read #resource-description { margin-bottom: 2em; } body.package.resource_read #dataset-description { margin-bottom: 2em; } body.package.resource_read #resource-explore { margin-bottom: 2em; } body.package.resource_read #resource-information-list { - border: 1px solid #E0E0E0; + border: 1px solid #e0e0e0; padding: 10px; } body.package.resource_read #resource-information dt diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 81c95a89d77..5bf3178f7d6 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -4,74 +4,92 @@ py:strip="" > + + + +
    - -
    - ${c.pkg_notes_formatted} -
    - - -
    -

    Resources

    - -
    - - ${res.get('name')} - (no name) - - ${res.get('format', '(unknown format)')} -
    ${h.markdown_extract(res.get('description'))}
    -
    - - ${res.get('url', '')} - + + +
    + + +
    + ${c.pkg_notes_formatted} +
    + + + + + +
    +

    Resources

    + +
    + + ${res.get('name')} + (no name) + + ${res.get('format', '(unknown format)')} +
    ${h.markdown_extract(res.get('description'))}
    + +
      +
    • + Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago +
    • +
    -
      -
    • - Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago -
    • -
    -
    - - - (none) - -
    + + + (none) + +
    + +
    - - - - ${label} - - - ${value} - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + +
    From 9f3dd2526744ab92622e837bb8f1c907298d2e65 Mon Sep 17 00:00:00 2001 From: John Glover Date: Tue, 22 Nov 2011 11:49:47 +0000 Subject: [PATCH 03/70] [ux, dataset-view] enable dataset view tab functionality via javascript --- ckan/public/css/style.css | 2 +- ckan/public/scripts/application.js | 33 +++++++++++++++++++++++++-- ckan/templates/package/read_core.html | 8 +++---- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 8a121ea04a7..b0af72bc591 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1051,7 +1051,7 @@ body.package.read #dataset-tabs a { display: inline-block; margin-bottom: -1px; } -body.package.read #dataset-tabs a.selected { +body.package.read #dataset-tabs a.active { color: #000000; border-left: 1px solid #e0e0e0; border-right: 1px solid #e0e0e0; diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index e8439942e70..a38bee5f1c9 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -25,8 +25,11 @@ var isDatasetView = $('body.package.read').length > 0; if (isDatasetView) { - var _dataset = new CKAN.Model.Dataset(preload_dataset); - CKANEXT.DATAPREVIEW.setupDataPreview(_dataset); + // var _dataset = new CKAN.Model.Dataset(preload_dataset); + // CKANEXT.DATAPREVIEW.setupDataPreview(_dataset); + + // Set up hashtag nagivigation + CKAN.Utils.setupDatasetViewNavigation(); } var isDatasetNew = $('body.package.new').length > 0; @@ -405,6 +408,32 @@ CKAN.Utils = function($, my) { }); }; + // Show/hide fieldset sections from the dataset view form. + my.setupDatasetViewNavigation = function() { + + function showSection(sectionToShowId) { + $('#dataset-overview').hide(); + $('#dataset-more-information').hide(); + $('#dataset-'+sectionToShowId).show(); + $('#dataset-tabs li a').removeClass('active'); + $('#dataset-tabs 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) || 'overview'; + showSection(initialSection); + + // Adjust page state on click + $('#dataset-tabs li a').live('click', function(e) { + var $el = $(e.target); + // Prefix="#section-" + var showMe = $el.attr('href').slice(9); + showSection(showMe); + return false; + }); + }; // Show/hide fieldset sections from the edit dataset form. my.setupDatasetEditNavigation = function() { diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 5bf3178f7d6..3324810eac9 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -8,14 +8,14 @@
    - +
    @@ -56,7 +56,7 @@

    Resources

    -
    +
    From 1ab15b00240653bc624727253db194ccb691ef2c Mon Sep 17 00:00:00 2001 From: John Glover Date: Tue, 22 Nov 2011 15:07:26 +0000 Subject: [PATCH 04/70] [ux, dataset-view] Add some details from sidebar to 'additional information' section. Disable overview/more info tabs for now and show all sections. --- ckan/public/css/style.css | 20 +++--- ckan/public/scripts/application.js | 2 +- ckan/templates/package/read.html | 80 ++++++++++------------- ckan/templates/package/read_core.html | 91 ++++++++++++++++----------- 4 files changed, 97 insertions(+), 96 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index b0af72bc591..234b97ebcd1 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -977,19 +977,19 @@ td.resource-edit-value { /* ===================== */ /* = Dataset View Page = */ /* ===================== */ -body.package.read #content { - border: 0; -} +/* body.package.read #content { */ +/* border: 0; */ +/* } */ body.package.read #sidebar ul.tags, body.package.read #sidebar ul.groups { margin-bottom: 10px; } -body.package.read #dataset { - border-left: 1px solid #ececec; - border-right: 1px solid #ececec; - border-bottom: 1px solid #ececec; - padding: 12px; -} +/* body.package.read #dataset { */ +/* border-left: 1px solid #ececec; */ +/* border-right: 1px solid #ececec; */ +/* border-bottom: 1px solid #ececec; */ +/* padding: 24px 12px 12px 12px; */ +/* } */ body.package.read #dataset-resources { margin-top: 2em; margin-bottom: 2em; @@ -1046,8 +1046,6 @@ body.package.read #dataset-tabs li { body.package.read #dataset-tabs a { padding: 8px 12px 8px 12px; color: #808080; - /* border: 1px transparent solid #ffffff; */ - /* border-right: none; */ display: inline-block; margin-bottom: -1px; } diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index a38bee5f1c9..45fdf518101 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -29,7 +29,7 @@ // CKANEXT.DATAPREVIEW.setupDataPreview(_dataset); // Set up hashtag nagivigation - CKAN.Utils.setupDatasetViewNavigation(); + // CKAN.Utils.setupDatasetViewNavigation(); } var isDatasetNew = $('body.package.new').length > 0; diff --git a/ckan/templates/package/read.html b/ckan/templates/package/read.html index a7400c6a7e7..af892e330ca 100644 --- a/ckan/templates/package/read.html +++ b/ckan/templates/package/read.html @@ -14,58 +14,48 @@ ${c.pkg_dict.get('title', c.pkg_dict['name'])} - Datasets - ${c.pkg_dict['title']} + + ${c.pkg_dict['title']} + + + + [Open Data] + + + + + ${h.icon('lock')} + + + +
    • - - - - - - - - - - - - - - - - - - -
    • Tags

      ${tag_list(c.pkg_dict.get('tags', ''))}
    • - - - - - - - - - - - - - - - - - - - - - - - +
    • +

      Groups

      + + + +

      + + Groups are collections of dataset maintained by users of ${g.site_title}. This dataset has not been added to any groups yet. + +

      +
    • +
    +
  • + @@ -79,10 +69,8 @@

    Tags

    - - - + diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 3324810eac9..32dd16ff962 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -6,12 +6,12 @@ - + + + + + +
    @@ -58,39 +58,54 @@

    Resources

    - - - - - - - - - - + +
    +

    Additional Information

    +
    + +
    Source
    +
    ${c.pkg_url_link}
    +
    + + +
    Author
    +
    ${c.pkg_author_link}
    +
    + + +
    Maintainer
    +
    ${c.pkg_maintainer_link}
    +
    + + +
    Version
    +
    ${c.pkg.version}
    +
    + + +
    Country
    +
    ${h.code_to_country(c.eu_country)}
    +
    + + +
    State
    +
    ${c.pkg.state}
    +
    + + +
    Harvest Source
    +
    Dataset page on + ${c.harvest_catalogue_name}
    +
    + + + +
    ${_(key)}
    +
    ${value}
    +
    +
    +
    - - - - - - - - - - - - - - - - - - - - - -
    From 732337206ba326b0db5247a404df3b322267c919 Mon Sep 17 00:00:00 2001 From: John Glover Date: Tue, 22 Nov 2011 16:41:55 +0000 Subject: [PATCH 05/70] [ux, dataset-view] change dataset info section to be a table rather than definition list, was causing formatting problems on more complicated pages --- ckan/public/css/style.css | 80 +++++++++++++++++-------- ckan/templates/package/read.html | 24 ++++---- ckan/templates/package/read_core.html | 85 ++++++++++++++------------- 3 files changed, 112 insertions(+), 77 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 234b97ebcd1..66405351fb6 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1030,31 +1030,63 @@ body.package.read .resource-format { body.package.read #sidebar li.widget-container { border: 0 } -body.package.read #dataset-tab-bar { - margin-top: 1em; - border-bottom: 1px solid #ececec; -} -body.package.read #dataset-tabs { - padding: 0; +/* body.package.read #dataset-tab-bar { */ +/* margin-top: 1em; */ +/* border-bottom: 1px solid #ececec; */ +/* } */ +/* body.package.read #dataset-tabs { */ +/* padding: 0; */ +/* margin: 0; */ +/* } */ +/* body.package.read #dataset-tabs li { */ +/* display: inline-block; */ +/* cursor: pointer; */ +/* font-size: 14px; */ +/* } */ +/* body.package.read #dataset-tabs a { */ +/* padding: 8px 12px 8px 12px; */ +/* color: #808080; */ +/* display: inline-block; */ +/* margin-bottom: -1px; */ +/* } */ +/* body.package.read #dataset-tabs a.active { */ +/* color: #000000; */ +/* border-left: 1px solid #e0e0e0; */ +/* border-right: 1px solid #e0e0e0; */ +/* border-top: 1px solid #e0e0e0; */ +/* border-bottom: 1px solid #ffffff; */ +/* } */ +body.package.read table { margin: 0; } -body.package.read #dataset-tabs li { - display: inline-block; - cursor: pointer; - font-size: 14px; +body.package.read tbody tr:nth-child(odd) td, tbody tr.odd td { + background-color: #ffffff; } -body.package.read #dataset-tabs a { - padding: 8px 12px 8px 12px; - color: #808080; - display: inline-block; - margin-bottom: -1px; +body.package.read tbody tr:nth-child(even) td, tbody tr.even td { + background-color: #f8f8f8; } -body.package.read #dataset-tabs a.active { - color: #000000; - border-left: 1px solid #e0e0e0; - border-right: 1px solid #e0e0e0; - border-top: 1px solid #e0e0e0; - border-bottom: 1px solid #ffffff; +body.package.read #dataset-information { + border: 1px solid #e0e0e0; + padding: 10px; +} +/* body.package.read #dataset-information dl { */ +/* margin: 0; */ +/* } */ +/* body.package.read #dataset-information dt { */ +/* width: 8em; */ +/* clear: left; */ +/* float: left; */ +/* font-weight: bold; */ +/* text-align: right; */ +/* } */ +/* body.package.read #dataset-information dd { */ +/* margin-left: 10em; */ +/* margin-top: 0.5em; */ +/* margin-bottom: 0.5em; */ +/* } */ +body.package.read #dataset-information .dataset-label { + font-weight: bold; + min-width: 10em; } @@ -1078,15 +1110,13 @@ body.package.resource_read #resource-information-list { border: 1px solid #e0e0e0; padding: 10px; } -body.package.resource_read #resource-information dt -{ +body.package.resource_read #resource-information dt { width: 10em; float: left; font-weight: bold; text-align: right; } -body.package.resource_read #resource-information dd -{ +body.package.resource_read #resource-information dd { margin-left: 14em; margin-bottom: 1em; } diff --git a/ckan/templates/package/read.html b/ckan/templates/package/read.html index af892e330ca..5bcb83d3761 100644 --- a/ckan/templates/package/read.html +++ b/ckan/templates/package/read.html @@ -16,18 +16,18 @@ ${c.pkg_dict['title']} - - - - [Open Data] - - - - - ${h.icon('lock')} - - - + + + + + + + + + + + + diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 32dd16ff962..2112faf62dc 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -58,52 +58,57 @@

    Resources

    - -
    -

    Additional Information

    -
    - -
    Source
    -
    ${c.pkg_url_link}
    -
    + +

    Dataset Information

    +
    + + + + + + - -
    Author
    -
    ${c.pkg_author_link}
    -
    - - -
    Maintainer
    -
    ${c.pkg_maintainer_link}
    -
    + + + + - -
    Version
    -
    ${c.pkg.version}
    -
    + + + + - -
    Country
    -
    ${h.code_to_country(c.eu_country)}
    -
    + + + + - -
    State
    -
    ${c.pkg.state}
    -
    + + + + - -
    Harvest Source
    -
    Dataset page on - ${c.harvest_catalogue_name}
    -
    + + + + + + + + + + + + + + + +
    Source${c.pkg_url_link}
    Author${c.pkg_author_link}
    Maintainer${c.pkg_maintainer_link}
    Version${c.pkg.version}
    Country${h.code_to_country(c.eu_country)}
    State${c.pkg.state}
    Harvest Source + Dataset page on + ${c.harvest_catalogue_name} +
    ${_(key)}${value}
    - - -
    ${_(key)}
    -
    ${value}
    -
    -
    From c202b237c600eb97d4fb978fc0f1c637bcd89334 Mon Sep 17 00:00:00 2001 From: John Glover Date: Tue, 22 Nov 2011 18:28:27 +0000 Subject: [PATCH 06/70] [ux] set sidebar headers to normal font-weight, consistent with headers in the content section --- ckan/public/css/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 66405351fb6..ecfcc4500d6 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -508,7 +508,6 @@ form.simple-form input[type=password] { } .property-list li h3 { font-size: 1.1em; - font-weight: bold; margin-bottom: 0.5em; margin-left: -2em; } From d69e9600aa0f8ca81d47ba3aea8ae43e89bd1e10 Mon Sep 17 00:00:00 2001 From: John Glover Date: Tue, 22 Nov 2011 18:29:28 +0000 Subject: [PATCH 07/70] [ux, dataset-view] fix bug in showing unknown resource formats --- ckan/templates/package/read_core.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 2112faf62dc..0a6083b5131 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -23,9 +23,6 @@ ${c.pkg_notes_formatted} - - -

    Resources

    @@ -36,7 +33,8 @@

    Resources

    ${res.get('name')} (no name) - ${res.get('format', '(unknown format)')} + ${res.get('format')} + (unknown format)
    ${h.markdown_extract(res.get('description'))}
    From 7f5f8a680564d1063148e3f2ff4d475fc6e6aab5 Mon Sep 17 00:00:00 2001 From: John Glover Date: Tue, 22 Nov 2011 18:29:51 +0000 Subject: [PATCH 08/70] [ux, dataset-view] add skeleton code for collapsable notes field --- ckan/public/scripts/application.js | 17 ++++++++++++++++- ckan/public/scripts/templates.js | 12 ++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 45fdf518101..19830db7b02 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -27,9 +27,10 @@ if (isDatasetView) { // var _dataset = new CKAN.Model.Dataset(preload_dataset); // CKANEXT.DATAPREVIEW.setupDataPreview(_dataset); - // Set up hashtag nagivigation // CKAN.Utils.setupDatasetViewNavigation(); + // Show extract of notes field + CKAN.Utils.setupDatasetViewNotesExtract(); } var isDatasetNew = $('body.package.new').length > 0; @@ -435,6 +436,20 @@ CKAN.Utils = function($, my) { }); }; + // 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.setupDatasetViewNotesExtract = function() { + var notes = $('#dataset div.notes'); + if(notes.find('p').length > 1){ + var first = notes.find('p:first'); + var remaining = notes.find('p:not(:first)'); + $('#dataset div.notes').html($.tmpl( + CKAN.Templates.datasetNotesField, {} + )); + } + }; + // Show/hide fieldset sections from the edit dataset form. my.setupDatasetEditNavigation = function() { diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 936929f8534..83158f516c9 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -152,3 +152,15 @@ CKAN.Templates.resourceEntry = ' \ \ \ '; + +CKAN.Templates.datasetNotesField = ' \ +
    \ +
    \ + extract \ +
    \ + \ +
    \ +
    \ +
    \ +'; + From fd118821e16957574cfe0c3dfe17d07b0a4fb38e Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 10:35:33 +0000 Subject: [PATCH 09/70] [ux, dataset-view] tidy up JS slightly --- ckan/public/scripts/application.js | 56 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 81d1b17e5e9..d27d10b3abb 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -25,10 +25,6 @@ var isDatasetView = $('body.package.read').length > 0; if (isDatasetView) { - // var _dataset = new CKAN.Model.Dataset(preload_dataset); - // CKANEXT.DATAPREVIEW.setupDataPreview(_dataset); - // Set up hashtag nagivigation - // CKAN.Utils.setupDatasetViewNavigation(); // Show extract of notes field CKAN.Utils.setupDatasetViewNotesExtract(); } @@ -416,31 +412,35 @@ CKAN.Utils = function($, my) { }; // Show/hide fieldset sections from the dataset view form. - my.setupDatasetViewNavigation = function() { - - function showSection(sectionToShowId) { - $('#dataset-overview').hide(); - $('#dataset-more-information').hide(); - $('#dataset-'+sectionToShowId).show(); - $('#dataset-tabs li a').removeClass('active'); - $('#dataset-tabs 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) || 'overview'; - showSection(initialSection); + // + // TODO: Currently not used, reenable if we decide to go back to tabs on this page. + // If not, remove. + // + // my.setupDatasetViewNavigation = function() { + + // function showSection(sectionToShowId) { + // $('#dataset-overview').hide(); + // $('#dataset-more-information').hide(); + // $('#dataset-'+sectionToShowId).show(); + // $('#dataset-tabs li a').removeClass('active'); + // $('#dataset-tabs 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) || 'overview'; + // showSection(initialSection); - // Adjust page state on click - $('#dataset-tabs li a').live('click', function(e) { - var $el = $(e.target); - // Prefix="#section-" - var showMe = $el.attr('href').slice(9); - showSection(showMe); - return false; - }); - }; + // // Adjust page state on click + // $('#dataset-tabs li a').live('click', function(e) { + // var $el = $(e.target); + // // Prefix="#section-" + // var showMe = $el.attr('href').slice(9); + // showSection(showMe); + // return false; + // }); + // }; // If notes field is more than 1 paragraph, just show the // first paragraph with a 'Read more' link that will expand From b62437c075d9ea9a49288315684bc5ad7799615c Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 11:58:59 +0000 Subject: [PATCH 10/70] [ux, dataset-view] fix expandable notes field --- ckan/public/css/style.css | 9 +++++++++ ckan/public/scripts/application.js | 15 ++++++++++----- ckan/public/scripts/templates.js | 11 ++++------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index ecfcc4500d6..af3d641d64a 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1055,6 +1055,15 @@ body.package.read #sidebar li.widget-container { /* border-top: 1px solid #e0e0e0; */ /* border-bottom: 1px solid #ffffff; */ /* } */ +body.package.read .notes a { + cursor: pointer; +} +body.package.read #notes-extract p { + margin-bottom: 0; +} +body.package.read #notes-remainder { + margin-top: 1em; +} body.package.read table { margin: 0; } diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index d27d10b3abb..d977ca89562 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -448,11 +448,16 @@ CKAN.Utils = function($, my) { my.setupDatasetViewNotesExtract = function() { var notes = $('#dataset div.notes'); if(notes.find('p').length > 1){ - var first = notes.find('p:first'); - var remaining = notes.find('p:not(:first)'); - $('#dataset div.notes').html($.tmpl( - CKAN.Templates.datasetNotesField, {} - )); + var extract = notes.children(':eq(0)'); + var remainder = notes.children(':gt(0)'); + notes.html($.tmpl(CKAN.Templates.datasetNotesField)); + notes.find('#notes-extract').html(extract); + notes.find('#notes-remainder').html(remainder); + notes.find('#notes-remainder').hide(); + notes.find('#dataset-notes-read-more a').click(function(){ + notes.find('#dataset-notes-read-more').hide(); + notes.find('#notes-remainder').slideDown(); + }) } }; diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 83158f516c9..2b883dfb15b 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -154,13 +154,10 @@ CKAN.Templates.resourceEntry = ' \ '; CKAN.Templates.datasetNotesField = ' \ -
    \ -
    \ - extract \ -
    \ - \ -
    \ -
    \ +
    \ +
    \ + \ +
    \
    \ '; From 65c1cf2a981bab086b9b1b56f55e58fac51e5bc6 Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 12:04:26 +0000 Subject: [PATCH 11/70] [ux, dataset-view] remove dead code related to dataset view tabs --- ckan/public/css/style.css | 50 --------------------------- ckan/public/scripts/application.js | 33 +----------------- ckan/templates/package/read_core.html | 8 ----- 3 files changed, 1 insertion(+), 90 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index af3d641d64a..29a79868691 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -976,19 +976,10 @@ td.resource-edit-value { /* ===================== */ /* = Dataset View Page = */ /* ===================== */ -/* body.package.read #content { */ -/* border: 0; */ -/* } */ body.package.read #sidebar ul.tags, body.package.read #sidebar ul.groups { margin-bottom: 10px; } -/* body.package.read #dataset { */ -/* border-left: 1px solid #ececec; */ -/* border-right: 1px solid #ececec; */ -/* border-bottom: 1px solid #ececec; */ -/* padding: 24px 12px 12px 12px; */ -/* } */ body.package.read #dataset-resources { margin-top: 2em; margin-bottom: 2em; @@ -1029,32 +1020,6 @@ body.package.read .resource-format { body.package.read #sidebar li.widget-container { border: 0 } -/* body.package.read #dataset-tab-bar { */ -/* margin-top: 1em; */ -/* border-bottom: 1px solid #ececec; */ -/* } */ -/* body.package.read #dataset-tabs { */ -/* padding: 0; */ -/* margin: 0; */ -/* } */ -/* body.package.read #dataset-tabs li { */ -/* display: inline-block; */ -/* cursor: pointer; */ -/* font-size: 14px; */ -/* } */ -/* body.package.read #dataset-tabs a { */ -/* padding: 8px 12px 8px 12px; */ -/* color: #808080; */ -/* display: inline-block; */ -/* margin-bottom: -1px; */ -/* } */ -/* body.package.read #dataset-tabs a.active { */ -/* color: #000000; */ -/* border-left: 1px solid #e0e0e0; */ -/* border-right: 1px solid #e0e0e0; */ -/* border-top: 1px solid #e0e0e0; */ -/* border-bottom: 1px solid #ffffff; */ -/* } */ body.package.read .notes a { cursor: pointer; } @@ -1077,21 +1042,6 @@ body.package.read #dataset-information { border: 1px solid #e0e0e0; padding: 10px; } -/* body.package.read #dataset-information dl { */ -/* margin: 0; */ -/* } */ -/* body.package.read #dataset-information dt { */ -/* width: 8em; */ -/* clear: left; */ -/* float: left; */ -/* font-weight: bold; */ -/* text-align: right; */ -/* } */ -/* body.package.read #dataset-information dd { */ -/* margin-left: 10em; */ -/* margin-top: 0.5em; */ -/* margin-bottom: 0.5em; */ -/* } */ body.package.read #dataset-information .dataset-label { font-weight: bold; min-width: 10em; diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index d977ca89562..8959fbc9b56 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -411,37 +411,6 @@ CKAN.Utils = function($, my) { }); }; - // Show/hide fieldset sections from the dataset view form. - // - // TODO: Currently not used, reenable if we decide to go back to tabs on this page. - // If not, remove. - // - // my.setupDatasetViewNavigation = function() { - - // function showSection(sectionToShowId) { - // $('#dataset-overview').hide(); - // $('#dataset-more-information').hide(); - // $('#dataset-'+sectionToShowId).show(); - // $('#dataset-tabs li a').removeClass('active'); - // $('#dataset-tabs 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) || 'overview'; - // showSection(initialSection); - - // // Adjust page state on click - // $('#dataset-tabs li a').live('click', function(e) { - // var $el = $(e.target); - // // Prefix="#section-" - // var showMe = $el.attr('href').slice(9); - // showSection(showMe); - // 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 @@ -456,7 +425,7 @@ CKAN.Utils = function($, my) { notes.find('#notes-remainder').hide(); notes.find('#dataset-notes-read-more a').click(function(){ notes.find('#dataset-notes-read-more').hide(); - notes.find('#notes-remainder').slideDown(); + notes.fin('#notes-remainder').slideDown(); }) } }; diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 0a6083b5131..909a08874dc 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -5,14 +5,6 @@ > - - - - - - - -
    From 331529c8c1208157eaa7f7b6f5bfbfb93baddac1 Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 12:28:50 +0000 Subject: [PATCH 12/70] [ux, dataset-view] bug fix: typo in notes field expander --- ckan/public/scripts/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 8959fbc9b56..d385c642e22 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -425,7 +425,7 @@ CKAN.Utils = function($, my) { notes.find('#notes-remainder').hide(); notes.find('#dataset-notes-read-more a').click(function(){ notes.find('#dataset-notes-read-more').hide(); - notes.fin('#notes-remainder').slideDown(); + notes.find('#notes-remainder').slideDown(); }) } }; From 5ea26ec3c6aa3c513c1c8c57cdc5012cf29f5908 Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 14:17:40 +0000 Subject: [PATCH 13/70] [ux, dataset-view] Add tags and groups to sidebar --- ckan/public/css/style.css | 28 +++++++- ckan/templates/package/read.html | 92 ++++++++++----------------- ckan/templates/package/read_core.html | 2 +- 3 files changed, 58 insertions(+), 64 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 29a79868691..81834594bc7 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -976,10 +976,32 @@ td.resource-edit-value { /* ===================== */ /* = Dataset View Page = */ /* ===================== */ -body.package.read #sidebar ul.tags, -body.package.read #sidebar ul.groups { - margin-bottom: 10px; +/* light yellow (from tables): fefef2 */ +/* dark yellow (from tables): fcf6cf */ +body.package.read .sidebar-section { + margin-bottom: 2em; +} +body.package.read .tags, body.package.read .groups { + padding: 0; +} +body.package.read .tags li, body.package.read .groups li { + list-style-type: none; + display: inline-block; + padding: 3px 3px 3px 0px; +} +body.package.read .tags a, body.package.read .groups a { + display: inline-block; + color: #222; + padding: 5px; + border: 1px solid #e0e0e0; + -webkit-border-radius: 4px !important; + border-radius: 4px !important; +} +body.package.read .tags a:hover, +body.package.read .groups a:hover { + border: 1px solid #aaa; } +body.package.read #dataset-license img.open-data { vertical-align: top; } body.package.read #dataset-resources { margin-top: 2em; margin-bottom: 2em; diff --git a/ckan/templates/package/read.html b/ckan/templates/package/read.html index 5bcb83d3761..22546c2a905 100644 --- a/ckan/templates/package/read.html +++ b/ckan/templates/package/read.html @@ -16,42 +16,41 @@ ${c.pkg_dict['title']} - - - - - - - - - - - - -
  • -
      -
    • -

      Tags

      - ${tag_list(c.pkg_dict.get('tags', ''))} -
    • -
    • -

      Groups

      - - - -

      - - Groups are collections of dataset maintained by users of ${g.site_title}. This dataset has not been added to any groups yet. - -

      +
    • + + + + @@ -70,32 +69,8 @@

      Groups

      - - - - - - - - - - - - - - - - - - - - - - - - @@ -103,6 +78,7 @@

      Groups

      +
      @@ -121,10 +97,6 @@

      Groups

      - - diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 909a08874dc..85f14cf6743 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -49,7 +49,7 @@

      Resources

      -

      Dataset Information

      +

      Information

      From 0299e83002a0e3cf43438ed0ba0621bfbb0bb04e Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 14:46:22 +0000 Subject: [PATCH 14/70] [ux, dataset-view] add related datasets to sidebar --- ckan/public/css/style.css | 8 ++++++-- ckan/templates/package/read.html | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 81834594bc7..2efe26da42d 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -976,8 +976,6 @@ td.resource-edit-value { /* ===================== */ /* = Dataset View Page = */ /* ===================== */ -/* light yellow (from tables): fefef2 */ -/* dark yellow (from tables): fcf6cf */ body.package.read .sidebar-section { margin-bottom: 2em; } @@ -1001,6 +999,12 @@ body.package.read .tags a:hover, body.package.read .groups a:hover { border: 1px solid #aaa; } +body.package.read .related-datasets { + padding: 0; +} +body.package.read .related-datasets li { + list-style-type: none; +} body.package.read #dataset-license img.open-data { vertical-align: top; } body.package.read #dataset-resources { margin-top: 2em; diff --git a/ckan/templates/package/read.html b/ckan/templates/package/read.html index 22546c2a905..a9c59982e3f 100644 --- a/ckan/templates/package/read.html +++ b/ckan/templates/package/read.html @@ -55,19 +55,19 @@

      Groups

      - - - - - - - - - - - - - + From e0b34ca07c743b7119922efa03189b20e098283f Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 15:14:28 +0000 Subject: [PATCH 15/70] [ux, resource-view] fix resource name bug, format information table to be consistent with info table on dataset view page --- ckan/public/css/style.css | 24 +++--- ckan/templates/package/resource_read.html | 99 +++++++++++++++-------- 2 files changed, 79 insertions(+), 44 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 2efe26da42d..b4bbc36279a 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1077,6 +1077,7 @@ body.package.read #dataset-information .dataset-label { /* ====================== */ /* = Resource View Page = */ /* ====================== */ +body.package.resource_read .page_heading { margin-bottom: 0.2em; } body.package.resource_read #sidebar { display: none; } body.package.resource_read #minornavigation { display: none; } body.package.resource_read #content { @@ -1090,19 +1091,22 @@ body.package.resource_read #resource-actions a { color: #222; } body.package.resource_read #resource-description { margin-bottom: 2em; } body.package.resource_read #dataset-description { margin-bottom: 2em; } body.package.resource_read #resource-explore { margin-bottom: 2em; } -body.package.resource_read #resource-information-list { +body.package.resource_read table { + margin: 0; +} +body.package.resource_read tbody tr:nth-child(odd) td, tbody tr.odd td { + background-color: #ffffff; +} +body.package.resource_read tbody tr:nth-child(even) td, tbody tr.even td { + background-color: #f8f8f8; +} +body.package.resource_read #resource-information table { border: 1px solid #e0e0e0; padding: 10px; } -body.package.resource_read #resource-information dt { - width: 10em; - float: left; - font-weight: bold; - text-align: right; -} -body.package.resource_read #resource-information dd { - margin-left: 14em; - margin-bottom: 1em; +body.package.resource_read #resource-information .label { + font-weight: bold; + width: 14em; } diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index ef9106f2086..77f438cb026 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -20,12 +20,25 @@ - ${c.package.get('title', c.package['name'])} > - ${c.resource.get('name', c.resource['id'])} - Datasets + + ${c.package.get('title', c.package['name'])} > + ${c.resource.get('name')} - Datasets + + + ${c.package.get('title', c.package['name'])} > + ${c.resource.get('id')} - Datasets + + + + + + ${c.resource.get('name')} + + + ${c.resource.get('id')} + - ${c.resource.get('name', c.resource['id'])} -
      @@ -86,38 +99,56 @@
      -

      Resource Information

      -
      -
      Url
      -
      - ${c.resource.get('url')} -
      - -
      Format
      -
      ${c.resource.get('format', "(none)")}
      - -
      Resource Type
      -
      - ${c.resource.get('resource_type', "(none)")} -
      - -
      Size (Bytes)
      -
      ${c.resource.get('size')}
      - -
      Mimetype
      -
      ${c.resource.get('mimetype')}
      - -
      Mimetype (Inner)
      -
      ${c.resource.get('mimetype_inner')}
      - -
      Hash
      -
      ${c.resource.get('hash')}
      +

      Information

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Url + + ${c.resource.get('url')} + +
      Format${c.resource.get('format', "(none)")}
      Resource Type${c.resource.get('resource_type', "(none)")}
      Size (Bytes)${c.resource.get('size')}
      Mimetype${c.resource.get('mimetype')}
      Mimetype (Inner)${c.resource.get('mimetype_inner')}
      Hash${c.resource.get('hash')}
      ID${c.resource.get('id')}
      -
      ID
      -
      ${c.resource.get('id')}
      -
      -
  • From a48cbf6e45b7150e1b300074f46bbd0c2e5479ca Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 16:33:41 +0000 Subject: [PATCH 16/70] [ux, dataset-view] remove box around dataset information for now --- ckan/public/css/style.css | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index b4bbc36279a..0279a2f7a95 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1062,11 +1062,15 @@ body.package.read tbody tr:nth-child(odd) td, tbody tr.odd td { background-color: #ffffff; } body.package.read tbody tr:nth-child(even) td, tbody tr.even td { - background-color: #f8f8f8; + /* background-color: #f8f8f8; */ + background-color: #ffffff; } body.package.read #dataset-information { - border: 1px solid #e0e0e0; - padding: 10px; + /* border: 1px solid #e0e0e0; */ + /* padding: 10px; */ +} +body.package.read #dataset-information td { + padding-left: 0; } body.package.read #dataset-information .dataset-label { font-weight: bold; From a72084ad49857b4d3b59f9fb7725c54d3491da4e Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 16:37:01 +0000 Subject: [PATCH 17/70] [ux, resource-view] also remove box around resource info for now --- ckan/public/css/style.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 0279a2f7a95..1e37f8f6bfd 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1102,11 +1102,12 @@ body.package.resource_read tbody tr:nth-child(odd) td, tbody tr.odd td { background-color: #ffffff; } body.package.resource_read tbody tr:nth-child(even) td, tbody tr.even td { - background-color: #f8f8f8; + /* background-color: #f8f8f8; */ + background-color: #ffffff; } body.package.resource_read #resource-information table { - border: 1px solid #e0e0e0; - padding: 10px; + /* border: 1px solid #e0e0e0; */ + /* padding: 10px; */ } body.package.resource_read #resource-information .label { font-weight: bold; From 12e3c867b72056d6f7dceee87c0641c656e7166f Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 16:39:03 +0000 Subject: [PATCH 18/70] [ux, resource-view] fix resource view table alignment --- ckan/public/css/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 1e37f8f6bfd..46f0b36d9ca 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1109,6 +1109,9 @@ body.package.resource_read #resource-information table { /* border: 1px solid #e0e0e0; */ /* padding: 10px; */ } +body.package.resource_read #resource-information td { + padding-left: 0; +} body.package.resource_read #resource-information .label { font-weight: bold; width: 14em; From b25de120a93a10d56ce983c1c56079bfc570a8bb Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 23 Nov 2011 17:35:27 +0000 Subject: [PATCH 19/70] [ux, dataset-view] update test for reading datasets, 'This dataset is open' no longer displayed --- ckan/tests/functional/test_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/tests/functional/test_package.py b/ckan/tests/functional/test_package.py index 49a336b4f3f..a28a7bcc073 100644 --- a/ckan/tests/functional/test_package.py +++ b/ckan/tests/functional/test_package.py @@ -286,7 +286,7 @@ def test_read(self): assert 'romantic novel' in res, res assert 'original media' in res, res assert 'book' in res, res - assert 'This Dataset is Open' in res, res + assert 'This dataset satisfies the Open Definition' in res, res def test_read_war(self): name = u'warandpeace' From 2213616bbb05b79c238a4e2b9e2966678a1dbade Mon Sep 17 00:00:00 2001 From: kindly Date: Wed, 23 Nov 2011 23:22:31 +0000 Subject: [PATCH 20/70] tests failing with migration done --- ckan/controllers/group_formalchemy.py | 4 +- ckan/forms/common.py | 23 +++++- ckan/forms/group.py | 2 +- ckan/lib/create_test_data.py | 9 ++- ckan/lib/dictization/model_dictize.py | 19 +++-- ckan/lib/dictization/model_save.py | 76 ++++++++++++------- ckan/lib/dumper.py | 2 +- ckan/lib/stats.py | 10 +-- ckan/logic/action/get.py | 4 +- ckan/logic/auth/create.py | 6 +- .../046_rename_package_group_member.py | 73 ++++++++++++++++++ ckan/model/__init__.py | 2 +- ckan/model/group.py | 61 ++++++--------- ckan/tests/functional/api/model/test_group.py | 16 +++- .../functional/api/model/test_package.py | 14 +++- ckan/tests/functional/test_group.py | 2 +- ckan/tests/functional/test_package.py | 2 +- ckan/tests/test_dumper.py | 2 +- 18 files changed, 223 insertions(+), 104 deletions(-) create mode 100644 ckan/migration/versions/046_rename_package_group_member.py diff --git a/ckan/controllers/group_formalchemy.py b/ckan/controllers/group_formalchemy.py index e7f1244cf5b..a8853bb559c 100644 --- a/ckan/controllers/group_formalchemy.py +++ b/ckan/controllers/group_formalchemy.py @@ -63,7 +63,7 @@ def new(self): group = model.Group.get(c.groupname) pkgs = [model.Package.by_name(name) for name in request.params.getall('Group-packages-current')] group.packages = pkgs - pkgnames = request.params.getall('PackageGroup--package_name') + pkgnames = request.params.getall('Member--package_name') for pkgname in pkgnames: if pkgname: package = model.Package.by_name(pkgname) @@ -126,7 +126,7 @@ def edit(self, id=None): # allow id=None to allow posting return render('group/edit.html') pkgs = [model.Package.by_name(name) for name in request.params.getall('Group-packages-current')] group.packages = pkgs - pkgnames = request.params.getall('PackageGroup--package_name') + pkgnames = request.params.getall('Member--package_name') for pkgname in pkgnames: if pkgname: package = model.Package.by_name(pkgname) diff --git a/ckan/forms/common.py b/ckan/forms/common.py index d5b8470bb89..712a2d7a11f 100644 --- a/ckan/forms/common.py +++ b/ckan/forms/common.py @@ -14,6 +14,7 @@ import ckan.lib.helpers as h import ckan.lib.field_types as field_types import ckan.misc +import ckan.lib.dictization.model_save as model_save log = logging.getLogger(__name__) @@ -693,9 +694,20 @@ def sync(self): def _update_groups(self): new_group_ids = self._deserialize() or [] + + group_dicts = [dict(id = group_id) for + group_id in new_group_ids] + + context = {'model': model, 'session': model.Session} + model_save.package_membership_list_save( + group_dicts, self.parent.model, context) + return # Get groups which have alread been associated. - old_groups = self.parent.model.groups + old_groups = model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + join(model.Package, model.Package.id == model.Member.table_id).\ + filter(model.Package.id == self.parent.model.id).all() # Calculate which to append and which to remove. editable_set = set([g.id for g in self.user_editable_groups]) @@ -707,7 +719,9 @@ def _update_groups(self): # Create dataset group associations. for id in append_set: - group = model.Session.query(model.Group).autoflush(False).get(id) + member = model.Member(table_id = self.parent.model.id, + table_name = 'package', + group = model.Session.query(model.Group).get(id)) if group: self.parent.model.groups.append(group) @@ -722,7 +736,10 @@ def requires_label(self): class GroupSelectEditRenderer(formalchemy.fields.FieldRenderer): def _get_value(self, **kwargs): - return self.field.parent.model.groups + return model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + join(model.Package, model.Package.id == model.Member.table_id).\ + filter(model.Package.id == self.field.parent.model.id).all() def _get_user_editable_groups(self): return self.field.user_editable_groups diff --git a/ckan/forms/group.py b/ckan/forms/group.py index 7565b53ee00..8b081435fd9 100644 --- a/ckan/forms/group.py +++ b/ckan/forms/group.py @@ -81,7 +81,7 @@ def get_group_fieldset(combined=False, is_admin=False): def get_package_group_fieldset(): if not 'new_package_group_fs' in fieldsets: # new_package_group_fs is the packages for the WUI form - builder = FormBuilder(model.PackageGroup) + builder = FormBuilder(model.Member) builder.add_field(PackageNameField('package_name')) builder.set_field_option('package_name', 'with_renderer', PackagesRenderer) builder.set_field_text('package_name', _('Package')) diff --git a/ckan/lib/create_test_data.py b/ckan/lib/create_test_data.py index ef305fe771e..10998462890 100644 --- a/ckan/lib/create_test_data.py +++ b/ckan/lib/create_test_data.py @@ -385,9 +385,12 @@ def create(cls, commit_changesets=False): cls.group_names.add(u'david') cls.group_names.add(u'roger') - model.Session.add(model.PackageGroup(package=pkg1, group=david)) - model.Session.add(model.PackageGroup(package=pkg2, group=david)) - model.Session.add(model.PackageGroup(package=pkg1, group=roger)) + + model.Session.flush() + + model.Session.add(model.Member(table_id=pkg1.id, table_name='package', group=david)) + model.Session.add(model.Member(table_id=pkg2.id, table_name='package', group=david)) + model.Session.add(model.Member(table_id=pkg1.id, table_name='package', group=roger)) # authz model.Session.add_all([ model.User(name=u'tester', apikey=u'tester', password=u'tester'), diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index 3fc7e3ef8ff..576c7d259b5 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -139,12 +139,12 @@ def package_dictize(pkg, context): result = _execute_with_revision(q, extra_rev, context) result_dict["extras"] = extras_list_dictize(result, context) #groups - group_rev = model.package_group_revision_table + member_rev = model.member_revision_table group = model.group_table q = select([group], - from_obj=group_rev.join(group, group.c.id == group_rev.c.group_id) - ).where(group_rev.c.package_id == pkg.id) - result = _execute_with_revision(q, group_rev, context) + from_obj=member_rev.join(group, group.c.id == member_rev.c.group_id) + ).where(member_rev.c.table_id == pkg.id) + result = _execute_with_revision(q, member_rev, context) result_dict["groups"] = obj_list_dictize(result, context) #relations rel_rev = model.package_relationship_revision_table @@ -157,16 +157,21 @@ def package_dictize(pkg, context): return result_dict def group_dictize(group, context): - + model = context['model'] result_dict = table_dictize(group, context) - + result_dict['display_name'] = group.display_name result_dict['extras'] = extras_dict_dictize( group._extras, context) + packages = model.Session.query(model.Package).\ + join(model.Member, model.Member.table_id == model.Package.id).\ + filter(model.Member.group_id == group.id).\ + filter(model.Member.state == 'active').all() + result_dict['packages'] = obj_list_dictize( - group.packages, context) + packages, context) return result_dict diff --git a/ckan/lib/dictization/model_save.py b/ckan/lib/dictization/model_save.py index 05763515a79..ff5467787aa 100644 --- a/ckan/lib/dictization/model_save.py +++ b/ckan/lib/dictization/model_save.py @@ -179,7 +179,7 @@ def package_tag_list_save(tag_dicts, package, context): package.package_tag_all[:] = tag_package_tag.values() -def package_group_list_save(group_dicts, package, context): +def package_membership_list_save(group_dicts, package, context): allow_partial_update = context.get("allow_partial_update", False) if not group_dicts and allow_partial_update: @@ -189,9 +189,11 @@ def package_group_list_save(group_dicts, package, context): session = context["session"] pending = context.get('pending') - group_package_group = dict((package_group.group, package_group) - for package_group in - package.package_group_all) + members = session.query(model.Member).filter_by(table_id = package.id) + + group_member = dict((member.group, member) + for member in + members) groups = set() for group_dict in group_dicts: id = group_dict.get("id") @@ -202,25 +204,25 @@ def package_group_list_save(group_dicts, package, context): group = session.query(model.Group).filter_by(name=name).first() groups.add(group) - for group in groups - set(group_package_group.keys()): - package_group_obj = model.PackageGroup(package = package, - group = group, - state = 'active') - session.add(package_group_obj) - group_package_group[group] = package_group_obj - - for group in set(group_package_group.keys()) - groups: - group_package_group.pop(group) - continue - ### this is alternate behavioiur below which is correct - ### but not compatible with old behaviour - package_group = group_package_group[group] - if pending and package_group.state <> 'deleted': - package_group.state = 'pending-deleted' - else: - package_group.state = 'deleted' + ## need to flush so we can get out the package id + model.Session.flush() + for group in groups - set(group_member.keys()): + member_obj = model.Member(table_id = package.id, + table_name = 'package', + group = group, + state = 'active') + session.add(member_obj) + - package.package_group_all[:] = group_package_group.values() + for group in set(group_member.keys()) - groups: + member_obj = group_member[group] + member_obj.state = 'deleted' + session.add(member_obj) + + for group in set(group_member.keys()) & groups: + member_obj = group_member[group] + member_obj.state = 'active' + session.add(member_obj) def relationship_list_save(relationship_dicts, package, attr, context): @@ -263,7 +265,7 @@ def package_dict_save(pkg_dict, context): package_resource_list_save(pkg_dict.get("resources", []), pkg, context) package_tag_list_save(pkg_dict.get("tags", []), pkg, context) - package_group_list_save(pkg_dict.get("groups", []), pkg, context) + package_membership_list_save(pkg_dict.get("groups", []), pkg, context) subjects = pkg_dict.get('relationships_as_subject', []) relationship_list_save(subjects, pkg, 'relationships_as_subject', context) @@ -284,6 +286,7 @@ def group_dict_save(group_dict, context): Group = model.Group Package = model.Package + Member = model.Member if group: group_dict["id"] = group.id @@ -299,7 +302,7 @@ def group_dict_save(group_dict, context): package_dicts = group_dict.get("packages", []) - packages = [] + packages = {} for package in package_dicts: pkg = None @@ -308,14 +311,31 @@ def group_dict_save(group_dict, context): pkg = session.query(Package).get(id) if not pkg: pkg = session.query(Package).filter_by(name=package["name"]).first() - if pkg and pkg not in packages: - packages.append(pkg) + if pkg and pkg not in packages.values(): + packages[pkg.id] = pkg + + members = session.query(Member).filter_by( + table_name='package', + group_id=group.id, + ).all() - if packages or not allow_partial_update: - group.packages[:] = packages + package_member = dict((member.table_id, member) for member in members) + + for package_id in set(package_member.keys()) - set(packages.keys()): + package_member[package_id].state = 'deleted' + session.add(package_member[package_id]) + + for package_id in set(package_member.keys()) & set(packages.keys()): + package_member[package_id].state = 'active' + session.add(package_member[package_id]) + + for package_id in set(packages.keys()) - set(package_member.keys()): + member = Member(group=group, table_id=package_id, table_name='package') + session.add(member) return group + def user_dict_save(user_dict, context): model = context['model'] diff --git a/ckan/lib/dumper.py b/ckan/lib/dumper.py index 28ae105dab9..76e063820bd 100644 --- a/ckan/lib/dumper.py +++ b/ckan/lib/dumper.py @@ -61,7 +61,7 @@ class Dumper(object): ckan.model.PackageRevision, ckan.model.PackageTagRevision, ckan.model.Group, - ckan.model.PackageGroup, + ckan.model.Member, ckan.model.PackageExtra, ] # TODO Bring this list of classes up to date. In the meantime, diff --git a/ckan/lib/stats.py b/ckan/lib/stats.py index 5aca8458f45..cc547f9244e 100644 --- a/ckan/lib/stats.py +++ b/ckan/lib/stats.py @@ -50,11 +50,11 @@ def most_edited_packages(cls, limit=10): @classmethod def largest_groups(cls, limit=10): - package_group = table('package_group') - s = select([package_group.c.group_id, func.count(package_group.c.package_id)]).\ - group_by(package_group.c.group_id).\ - where(package_group.c.group_id!=None).\ - order_by(func.count(package_group.c.package_id).desc()).\ + member = table('member') + s = select([member.c.group_id, func.count(member.c.table_id)]).\ + group_by(member.c.group_id).\ + where(and_(member.c.group_id!=None, member.c.table_name=='package')).\ + order_by(func.count(member.c.table_id).desc()).\ limit(limit) res_ids = model.Session.execute(s).fetchall() res_groups = [(model.Session.query(model.Group).get(unicode(group_id)), val) for group_id, val in res_ids] diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index c2fa76d342c..2ff24d722ee 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -432,8 +432,8 @@ def group_package_show(context, data_dict): query = model.Session.query(model.PackageRevision)\ .filter(model.PackageRevision.state=='active')\ .filter(model.PackageRevision.current==True)\ - .join(model.PackageGroup, model.PackageGroup.package_id==model.PackageRevision.id)\ - .join(model.Group, model.Group.id==model.PackageGroup.group_id)\ + .join(model.Member, model.Member.table_id==model.PackageRevision.id)\ + .join(model.Group, model.Group.id==model.Member.group_id)\ .filter_by(id=group.id) query = query.order_by(model.package_revision_table.c.revision_timestamp.desc()) diff --git a/ckan/logic/auth/create.py b/ckan/logic/auth/create.py index 86965aa681a..88c3bde2f67 100644 --- a/ckan/logic/auth/create.py +++ b/ckan/logic/auth/create.py @@ -102,7 +102,11 @@ def check_group_auth(context, data_dict): groups.add(grp) if pkg: - groups = groups - set(pkg.groups) + pkg_groups = model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + filter(model.Member.table_id == pkg.id).all() + + groups = groups - set(pkg_groups) for group in groups: if not check_access_old(group, model.Action.EDIT, context): diff --git a/ckan/migration/versions/046_rename_package_group_member.py b/ckan/migration/versions/046_rename_package_group_member.py new file mode 100644 index 00000000000..ae8b302d6ab --- /dev/null +++ b/ckan/migration/versions/046_rename_package_group_member.py @@ -0,0 +1,73 @@ +from migrate import * + +def upgrade(migrate_engine): + migrate_engine.execute(''' +BEGIN; +alter table package_group RENAME to member; +alter table package_group_revision RENAME to member_revision; +alter table member RENAME column package_id to table_id; +alter table member_revision RENAME column package_id to table_id; + +alter table member ALTER column table_id set NOT NULL; +alter table member_revision ALTER column table_id set NOT NULL; + +ALTER TABLE member_revision + DROP CONSTRAINT package_group_revision_pkey; + +ALTER TABLE member_revision + DROP CONSTRAINT package_group_revision_continuity_id_fkey; + +ALTER TABLE member_revision + DROP CONSTRAINT package_group_revision_group_id_fkey; + +ALTER TABLE member_revision + DROP CONSTRAINT package_group_revision_package_id_fkey; + +ALTER TABLE member_revision + DROP CONSTRAINT package_group_revision_revision_id_fkey; + +ALTER TABLE "member" + DROP CONSTRAINT package_group_pkey; + +ALTER TABLE "member" + DROP CONSTRAINT package_group_group_id_fkey; + +ALTER TABLE "member" + DROP CONSTRAINT package_group_package_id_fkey; + +ALTER TABLE "member" + DROP CONSTRAINT package_group_revision_id_fkey; + + +ALTER TABLE "member" + ADD COLUMN table_name text not null; + +ALTER TABLE member_revision + ADD COLUMN table_name text not null; + +ALTER TABLE "member" + ADD CONSTRAINT member_pkey PRIMARY KEY (id); + +ALTER TABLE member_revision + ADD CONSTRAINT member_revision_pkey PRIMARY KEY (id, revision_id); + +ALTER TABLE "member" + ADD CONSTRAINT member_group_id_fkey FOREIGN KEY (group_id) REFERENCES "group"(id); + +ALTER TABLE "member" + ADD CONSTRAINT member_revision_id_fkey FOREIGN KEY (revision_id) REFERENCES revision(id); + +ALTER TABLE member_revision + ADD CONSTRAINT member_revision_continuity_id_fkey FOREIGN KEY (continuity_id) REFERENCES member(id); + +ALTER TABLE member_revision + ADD CONSTRAINT member_revision_group_id_fkey FOREIGN KEY (group_id) REFERENCES "group"(id); + +ALTER TABLE member_revision + ADD CONSTRAINT member_revision_revision_id_fkey FOREIGN KEY (revision_id) REFERENCES revision(id); + +update member set table_name = 'package'; +update member_revision set table_name = 'package'; +COMMIT; + ''' + ) diff --git a/ckan/model/__init__.py b/ckan/model/__init__.py index f4b952cf7d3..264a38a6047 100644 --- a/ckan/model/__init__.py +++ b/ckan/model/__init__.py @@ -247,7 +247,7 @@ def purge_revision(self, revision, leave_record=False): repo = Repository(metadata, Session, - versioned_objects=[Package, PackageTag, Resource, ResourceGroup, PackageExtra, PackageGroup, Group] + versioned_objects=[Package, PackageTag, Resource, ResourceGroup, PackageExtra, Member, Group] ) diff --git a/ckan/model/group.py b/ckan/model/group.py index 24aa84e21b6..d30456d8d2d 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -11,17 +11,18 @@ from sqlalchemy.ext.associationproxy import association_proxy __all__ = ['group_table', 'Group', 'package_revision_table', - 'PackageGroup', 'GroupRevision', 'PackageGroupRevision', - 'package_group_revision_table'] + 'Member', 'GroupRevision', 'MemberRevision', + 'member_revision_table'] -package_group_table = Table('package_group', metadata, +member_table = Table('member', metadata, Column('id', UnicodeText, primary_key=True, default=make_uuid), - Column('package_id', UnicodeText, ForeignKey('package.id')), + Column('table_name', UnicodeText, nullable=False), + Column('table_id', UnicodeText, nullable=False), Column('group_id', UnicodeText, ForeignKey('group.id')), ) -vdm.sqlalchemy.make_table_stateful(package_group_table) -package_group_revision_table = make_revisioned_table(package_group_table) +vdm.sqlalchemy.make_table_stateful(member_table) +member_revision_table = make_revisioned_table(member_table) group_table = Table('group', metadata, Column('id', UnicodeText, primary_key=True, default=make_uuid), @@ -35,11 +36,12 @@ group_revision_table = make_revisioned_table(group_table) -class PackageGroup(vdm.sqlalchemy.RevisionedObjectMixin, +class Member(vdm.sqlalchemy.RevisionedObjectMixin, vdm.sqlalchemy.StatefulObjectMixin, DomainObject): def related_packages(self): - return [self.package] + # TODO do we want to return all related packages or certain ones? + return Session.query(Package).filter_by(id=self.table_id).all() class Group(vdm.sqlalchemy.RevisionedObjectMixin, vdm.sqlalchemy.StatefulObjectMixin, @@ -69,10 +71,9 @@ def get(cls, reference): def active_packages(self, load_eager=True): query = Session.query(Package).\ filter_by(state=vdm.sqlalchemy.State.ACTIVE).\ - join('package_group_all', 'group').filter_by(id=self.id) - if load_eager: - query = query.options(eagerload_all('package_tags.tag')) - query = query.options(eagerload_all('resource_groups_all.resources_all')) + filter_by(id=self.id).\ + join(member_table, member_table.c.table_id == Package.id).\ + join(group_table, group_table.c.id == member_table.c.table_id) return query @classmethod @@ -105,7 +106,7 @@ def all_related_revisions(self): if not results.has_key(grp_rev.revision): results[grp_rev.revision] = [] results[grp_rev.revision].append(grp_rev) - for class_ in [PackageGroup, GroupExtra]: + for class_ in [Member, GroupExtra]: rev_class = class_.__revision_class__ obj_revisions = Session.query(rev_class).filter_by(group_id=self.id).all() for obj_rev in obj_revisions: @@ -125,42 +126,24 @@ def __repr__(self): extension=[vdm.sqlalchemy.Revisioner(group_revision_table),], ) - vdm.sqlalchemy.modify_base_object_mapper(Group, Revision, State) GroupRevision = vdm.sqlalchemy.create_object_version(mapper, Group, group_revision_table) -mapper(PackageGroup, package_group_table, properties={ +mapper(Member, member_table, properties={ 'group': relation(Group, - backref=backref('package_group_all', cascade='all, delete-orphan'), - ), - 'package': relation(Package, - backref=backref('package_group_all', cascade='all, delete-orphan'), + backref=backref('member_all', cascade='all, delete-orphan') ), }, - extension=[vdm.sqlalchemy.Revisioner(package_group_revision_table),], + extension=[vdm.sqlalchemy.Revisioner(member_revision_table),], ) -def _create_group(group): - return PackageGroup(group=group) - -def _create_package(package): - return PackageGroup(package=package) - -Package.groups = association_proxy('package_group_all', 'group', creator=_create_group) -Group.packages = association_proxy('package_group_all', 'package', creator=_create_package) - -vdm.sqlalchemy.modify_base_object_mapper(PackageGroup, Revision, State) -PackageGroupRevision = vdm.sqlalchemy.create_object_version(mapper, PackageGroup, - package_group_revision_table) +vdm.sqlalchemy.modify_base_object_mapper(Member, Revision, State) +MemberRevision = vdm.sqlalchemy.create_object_version(mapper, Member, + member_revision_table) -PackageGroupRevision.related_packages = lambda self: [self.continuity.package] +#TODO +MemberRevision.related_packages = lambda self: [self.continuity.package] -from vdm.sqlalchemy.base import add_stateful_versioned_m2m -#vdm.sqlalchemy.add_stateful_versioned_m2m(Package, PackageGroup, 'groups', 'group', -# 'package_group') -vdm.sqlalchemy.add_stateful_versioned_m2m_on_version(GroupRevision, 'groups') -vdm.sqlalchemy.add_stateful_versioned_m2m(Group, PackageGroup, 'groups', 'group', - 'package_group') diff --git a/ckan/tests/functional/api/model/test_group.py b/ckan/tests/functional/api/model/test_group.py index 50ff6e1e47b..55b6ba0fc57 100644 --- a/ckan/tests/functional/api/model/test_group.py +++ b/ckan/tests/functional/api/model/test_group.py @@ -33,7 +33,10 @@ def test_register_post_ok(self): assert group assert group.title == self.testgroupvalues['title'], group assert group.description == self.testgroupvalues['description'], group - pkg_names = [pkg.name for pkg in group.packages] + pkg_ids = [member.table_id for member in group.member_all] + pkgs = model.Session.query(model.Package).filter(model.Package.id.in_(pkg_ids)).all() + pkg_names = [pkg.name for pkg in pkgs] + assert set(pkg_names) == set(('annakarenina', 'warandpeace')), pkg_names # check register updated @@ -64,6 +67,7 @@ def test_register_post_ok(self): def test_entity_get_ok(self): offset = self.group_offset(self.roger.name) res = self.app.get(offset, status=self.STATUS_200_OK) + self.assert_msg_represents_roger(msg=res.body) assert self.package_ref_from_name('annakarenina') in res, res assert self.group_ref_from_name('roger') in res, res @@ -96,7 +100,7 @@ def test_10_edit_group(self): model.Session.remove() group = model.Group.by_name(self.testgroupvalues['name']) assert group - assert len(group.packages) == 2, group.packages + assert len(group.member_all) == 2, group.packages user = model.User.by_name(self.user_name) model.setup_default_user_roles(group, [user]) @@ -109,10 +113,14 @@ def test_10_edit_group(self): extra_environ=self.extra_environ) model.Session.remove() group = model.Session.query(model.Group).filter_by(name=group_vals['name']).one() + package = model.Session.query(model.Package).filter_by(name='annakarenina').one() assert group.name == group_vals['name'] assert group.title == group_vals['title'] - assert len(group.packages) == 1, group.packages - assert group.packages[0].name == group_vals['packages'][0] + assert len(group.member_all) == 2, group.member_all + assert len([mem for mem in group.member_all if mem.state == 'active']) == 1, group.member_all + for mem in group.member_all: + if mem.state == 'active': + assert mem.table_id == package.id def test_10_edit_group_name_duplicate(self): # create a group with testgroupvalues diff --git a/ckan/tests/functional/api/model/test_package.py b/ckan/tests/functional/api/model/test_package.py index ea8e05bdba8..7a2a46cdaaf 100644 --- a/ckan/tests/functional/api/model/test_package.py +++ b/ckan/tests/functional/api/model/test_package.py @@ -137,10 +137,13 @@ def test_register_post_with_group(self): self.remove() package = self.get_package_by_name(self.package_fixture_data['name']) assert package + pkg_groups = model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + filter(model.Member.table_id == package.id).all() if self.get_expected_api_version() == '1': - self.assert_equal([g.name for g in package.groups], groups) + self.assert_equal([g.name for g in pkg_groups], groups) else: - self.assert_equal([g.id for g in package.groups], groups) + self.assert_equal([g.id for g in pkg_groups], groups) del package_fixture_data['groups'] def test_register_post_with_group_not_authorized(self): @@ -187,10 +190,13 @@ def test_register_post_with_group_sysadmin(self): self.remove() package = self.get_package_by_name(self.package_fixture_data['name']) assert package + pkg_groups = model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + filter(model.Member.table_id == package.id).all() if self.get_expected_api_version() == '1': - self.assert_equal([g.name for g in package.groups], groups) + self.assert_equal([g.name for g in pkg_groups], groups) else: - self.assert_equal([g.id for g in package.groups], groups) + self.assert_equal([g.id for g in pkg_groups], groups) del package_fixture_data['groups'] diff --git a/ckan/tests/functional/test_group.py b/ckan/tests/functional/test_group.py index 69679e5227c..fe6d4380380 100644 --- a/ckan/tests/functional/test_group.py +++ b/ckan/tests/functional/test_group.py @@ -282,7 +282,7 @@ def test_2_new(self): assert fv[prefix+'name'].value == '', fv.fields assert fv[prefix+'title'].value == '' assert fv[prefix+'description'].value == '' - assert fv['packages__0__name'].value == '', fv['PackageGroup--package_name'].value + assert fv['packages__0__name'].value == '', fv['Member--package_name'].value # Edit form fv[prefix+'name'] = group_name diff --git a/ckan/tests/functional/test_package.py b/ckan/tests/functional/test_package.py index 49a336b4f3f..ae3cce807df 100644 --- a/ckan/tests/functional/test_package.py +++ b/ckan/tests/functional/test_package.py @@ -912,7 +912,7 @@ def test_edit_700_groups_remove(self): assert len(pkg.groups) == 0 grp = model.Group.by_name(u'david') model.repo.new_revision() - model.Session.add(model.PackageGroup(package=pkg, group=grp)) + model.Session.add(model.Member(table_id=pkg.id, table_name='package', group=grp)) model.repo.commit_and_remove() pkg = model.Package.by_name(u'editpkgtest') assert len(pkg.groups) == 1 diff --git a/ckan/tests/test_dumper.py b/ckan/tests/test_dumper.py index 6beda8fc4de..bc2fe1d1139 100644 --- a/ckan/tests/test_dumper.py +++ b/ckan/tests/test_dumper.py @@ -78,7 +78,7 @@ def test_dump(self): dumpeddata = json.load(open(self.outpath)) assert dumpeddata['version'] == ckan.__version__ tables = dumpeddata.keys() - for key in ['Package', 'Tag', 'Group', 'PackageGroup', 'PackageExtra']: + for key in ['Package', 'Tag', 'Group', 'Member', 'PackageExtra']: assert key in tables, '%r not in %s' % (key, tables) for key in ['User']: assert key not in tables, '%s should not be in %s' % (key, tables) From aa90b19588b60dc38be860483a8828cd6aba5b3d Mon Sep 17 00:00:00 2001 From: rgrp Date: Thu, 24 Nov 2011 18:17:15 +0000 Subject: [PATCH 21/70] [view/resource][s]: reorganize resource read/view page. * Add some relevant helper functions along the way. --- ckan/lib/helpers.py | 26 +++++- ckan/public/css/style.css | 35 +++----- ckan/templates/package/resource_read.html | 101 +++++++++------------- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index cbcb34796e5..92e392a4faf 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -283,9 +283,33 @@ def date_str_to_datetime(date_str): return datetime.datetime(*map(int, re.split('[^\d]', date_str))) def time_ago_in_words_from_str(date_str, granularity='month'): - return date.time_ago_in_words(date_str_to_datetime(date_str), granularity=granularity) + if date_str: + return date.time_ago_in_words(date_str_to_datetime(date_str), granularity=granularity) + else: + return 'Unknown' def button_attr(enable, type='primary'): if enable: return 'class="pretty-button %s"' % type return 'disabled class="pretty-button disabled"' + +def resource_display_name(resource_dict): + return resource_dict['name'] or resource_dict['id'] + +def dataset_display_name(package_or_package_dict): + if isinstance(package_or_package_dict, dict): + return package_or_package_dict.get('title', '') or package_or_package_dict.get('name', '') + else: + return package_or_package_dict.title or package_or_package_dictname + +def dataset_link(package_or_package_dict): + if isinstance(package_or_package_dict, dict): + name = package_or_package_dict['name'] + else: + name = package_or_package_dict.name + text = dataset_display_name(package_or_package_dict) + return link_to( + text, + url_for(controller='package', action='read', id=name) + ) + diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 6d4d7835f76..66b8b4567a0 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1015,36 +1015,27 @@ body.package.read .resource-format { /* ====================== */ /* = Resource View Page = */ /* ====================== */ + body.package.resource_read #sidebar { display: none; } -body.package.resource_read #minornavigation { display: none; } body.package.resource_read #content { border-right: 0; width: 950px; } -body.package.resource_read #dataset-license img { vertical-align: top; } -body.package.resource_read #resource-main-information { margin-bottom: 2em; } -body.package.resource_read #resource-actions { margin-bottom: 2em; } -body.package.resource_read #resource-actions a { color: #222; } -body.package.resource_read #resource-description { margin-bottom: 2em; } -body.package.resource_read #dataset-description { margin-bottom: 2em; } -body.package.resource_read #resource-explore { margin-bottom: 2em; } -body.package.resource_read #resource-information-list { - border: 1px solid #E0E0E0; - padding: 10px; -} -body.package.resource_read #resource-information dt -{ - width: 10em; - float: left; - font-weight: bold; - text-align: right; + +.resource_read .quick-info dt { + width: 115px; + float: left; + font-weight: normal; + color: gray; } -body.package.resource_read #resource-information dd -{ - margin-left: 14em; - margin-bottom: 1em; + +.resource_read .resource-actions { + float: right; } +body.package.resource_read #dataset-description { margin-bottom: 2em; } +body.package.resource_read #resource-explore { margin-bottom: 2em; } + /* ================== */ /* = Add Group Page = */ diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index ef9106f2086..bbb2902e275 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -24,18 +24,34 @@ ${c.resource.get('name', c.resource['id'])} - Datasets - ${c.resource.get('name', c.resource['id'])} + ${h.resource_display_name(c.resource)}
    -
    -
    - Dataset: - - - ${c.package.get('title', c.package['name'])} - - - + + +
    +
    +
    Part of dataset
    +
    + ${h.dataset_link(c.package)} +
    +
    Last updated
    +
    ${h.time_ago_in_words_from_str(c.resource['last_modified'], granularity='day')}
    +
    Format
    +
    + ${c.resource['format'] or 'Unknown'} +
    +
    Openness
    +
    [Open Data] @@ -46,22 +62,8 @@ ${h.icon('lock')} - -
    -
    - Last updated: ${h.time_ago_in_words_from_str(c.resource.get('last_modified'), granularity='day')} -
    -
    - -
    @@ -85,39 +87,22 @@ -
    -

    Resource Information

    -
    -
    Url
    -
    - ${c.resource.get('url')} -
    - -
    Format
    -
    ${c.resource.get('format', "(none)")}
    - -
    Resource Type
    -
    - ${c.resource.get('resource_type', "(none)")} -
    - -
    Size (Bytes)
    -
    ${c.resource.get('size')}
    - -
    Mimetype
    -
    ${c.resource.get('mimetype')}
    - -
    Mimetype (Inner)
    -
    ${c.resource.get('mimetype_inner')}
    - -
    Hash
    -
    ${c.resource.get('hash')}
    - -
    ID
    -
    ${c.resource.get('id')}
    -
    +
    + + + + + + + + + + + + + +
    FieldValue
    ${_(key)}${c.resource[key]}
    -
    From b4e395b3c19429fd233da50b79ea8b0e0daf6aaf Mon Sep 17 00:00:00 2001 From: rgrp Date: Thu, 24 Nov 2011 18:25:06 +0000 Subject: [PATCH 22/70] [view/resource][s]: reintroduce nav bar on resource view page and add tab on nav bar for resources. --- ckan/controllers/package.py | 11 +++-------- ckan/templates/package/layout.html | 3 +++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index 87a54b8e6ae..866a0268b8a 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -630,18 +630,13 @@ def resource_read(self, id, resource_id): try: c.resource = get_action('resource_show')(context, {'id': resource_id}) + c.package = get_action('package_show')(context, {'id': id}) + # required for nav menu + c.pkg = c.package except NotFound: abort(404, _('Resource not found')) except NotAuthorized: abort(401, _('Unauthorized to read resource %s') % id) - - try: - c.package = get_action('package_show')(context, {'id': id}) - except NotFound: - abort(404, _('Package not found')) - except NotAuthorized: - abort(401, _('Unauthorized to read package %s') % id) - # get package license info license_id = c.package.get('license_id') try: diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index b648d1c1d38..c1feb603782 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -9,6 +9,9 @@
    • ${h.subnav_link(c, h.icon('package') + _('View'), controller='package', action='read', id=c.pkg.name)}
    • +
    • ${h.subnav_link(c, h.icon('package') + _('Resource'), + controller='package', action='read', id=c.pkg.name, anchor='dataset-resources')}
    • ${h.subnav_link(c, h.icon('package_edit') + _('Edit'), controller='package', action='edit', id=c.pkg.name)}
    • From 4e5db5107ed645320b5bc2d4df7a34cc1b029169 Mon Sep 17 00:00:00 2001 From: kindly Date: Thu, 24 Nov 2011 21:20:33 +0000 Subject: [PATCH 23/70] [group] tests pass --- ckan/controllers/authorization_group.py | 2 -- ckan/forms/common.py | 33 +---------------------- ckan/lib/create_test_data.py | 7 +++-- ckan/lib/dictization/model_dictize.py | 2 +- ckan/lib/stats.py | 1 + ckan/logic/action/get.py | 4 +-- ckan/logic/auth/create.py | 4 +-- ckan/model/group.py | 5 ++-- ckan/model/package.py | 15 +++++++++-- ckan/templates/package/read.html | 6 ++--- ckan/tests/functional/test_group.py | 10 +++---- ckan/tests/functional/test_package.py | 16 +++++------ ckan/tests/lib/test_dictization_schema.py | 2 +- ckan/tests/misc/test_stats.py | 17 +++++++----- ckan/tests/models/test_group.py | 16 ++++++++--- 15 files changed, 66 insertions(+), 74 deletions(-) diff --git a/ckan/controllers/authorization_group.py b/ckan/controllers/authorization_group.py index 6cd753af68e..725659ba78a 100644 --- a/ckan/controllers/authorization_group.py +++ b/ckan/controllers/authorization_group.py @@ -143,8 +143,6 @@ def edit(self, id=None): # allow id=None to allow posting if usr and usr not in authorization_group.users: model.add_user_to_authorization_group(usr, authorization_group, model.Role.READER) model.repo.commit_and_remove() - from nose.tools import set_trace; set_trace() - h.redirect_to(controller='authorization_group', action='read', id=c.authorization_group_name) def authz(self, id): diff --git a/ckan/forms/common.py b/ckan/forms/common.py index 712a2d7a11f..2e8c981549c 100644 --- a/ckan/forms/common.py +++ b/ckan/forms/common.py @@ -701,34 +701,6 @@ def _update_groups(self): context = {'model': model, 'session': model.Session} model_save.package_membership_list_save( group_dicts, self.parent.model, context) - return - - # Get groups which have alread been associated. - old_groups = model.Session.query(model.Group).\ - join(model.Member, model.Member.group_id == model.Group.id).\ - join(model.Package, model.Package.id == model.Member.table_id).\ - filter(model.Package.id == self.parent.model.id).all() - - # Calculate which to append and which to remove. - editable_set = set([g.id for g in self.user_editable_groups]) - old_group_ids = [g.id for g in old_groups] - new_set = set(new_group_ids) - old_set = set(old_group_ids) - append_set = (new_set - old_set).intersection(editable_set) - remove_set = (old_set - new_set).intersection(editable_set) - - # Create dataset group associations. - for id in append_set: - member = model.Member(table_id = self.parent.model.id, - table_name = 'package', - group = model.Session.query(model.Group).get(id)) - if group: - self.parent.model.groups.append(group) - - # Delete dataset group associations. - for group in self.parent.model.groups: - if group.id in remove_set: - self.parent.model.groups.remove(group) def requires_label(self): return False @@ -736,10 +708,7 @@ def requires_label(self): class GroupSelectEditRenderer(formalchemy.fields.FieldRenderer): def _get_value(self, **kwargs): - return model.Session.query(model.Group).\ - join(model.Member, model.Member.group_id == model.Group.id).\ - join(model.Package, model.Package.id == model.Member.table_id).\ - filter(model.Package.id == self.field.parent.model.id).all() + return self.field.parent.model.get_groups() def _get_user_editable_groups(self): return self.field.user_editable_groups diff --git a/ckan/lib/create_test_data.py b/ckan/lib/create_test_data.py index 10998462890..a0128ff3ef0 100644 --- a/ckan/lib/create_test_data.py +++ b/ckan/lib/create_test_data.py @@ -163,6 +163,7 @@ def create_arbitrary(cls, package_dicts, relationships=[], pkg.tags.append(tag) model.Session.flush() elif attr == 'groups': + model.Session.flush() if isinstance(val, (str, unicode)): group_names = val.split() elif isinstance(val, list): @@ -175,7 +176,8 @@ def create_arbitrary(cls, package_dicts, relationships=[], group = model.Group(name=unicode(group_name)) model.Session.add(group) new_group_names.add(group_name) - pkg.groups.append(group) + member = model.Member(group=group, table_id=pkg.id, table_name='package') + model.Session.add(member) elif attr == 'license': pkg.license_id = val elif attr == 'license_id': @@ -291,7 +293,8 @@ def create_groups(cls, group_dicts, admin_user_name=None): for pkg_name in pkg_names: pkg = model.Package.by_name(unicode(pkg_name)) assert pkg, pkg_name - pkg.groups.append(group) + member = model.Member(group=group, table_id=pkg.id, table_name='package') + model.Session.add(member) model.Session.add(group) model.setup_default_user_roles(group, admin_users) cls.group_names.add(group_dict['name']) diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index 576c7d259b5..2e3baba7808 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -25,7 +25,7 @@ def group_list_dictize(obj_list, context, sort_key=lambda x:x['display_name']): group_dict['display_name'] = obj.display_name - group_dict['packages'] = len(obj.packages) + group_dict['packages'] = len(obj.active_packages().all()) result_list.append(group_dict) return sorted(result_list, key=sort_key) diff --git a/ckan/lib/stats.py b/ckan/lib/stats.py index cc547f9244e..71962250905 100644 --- a/ckan/lib/stats.py +++ b/ckan/lib/stats.py @@ -56,6 +56,7 @@ def largest_groups(cls, limit=10): where(and_(member.c.group_id!=None, member.c.table_name=='package')).\ order_by(func.count(member.c.table_id).desc()).\ limit(limit) + res_ids = model.Session.execute(s).fetchall() res_groups = [(model.Session.query(model.Group).get(unicode(group_id)), val) for group_id, val in res_ids] return res_groups diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 2ff24d722ee..8ba5344eb5a 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -146,7 +146,7 @@ def group_list(context, data_dict): if order_by == 'packages': groups = sorted(query.all(), - key=lambda g: len(g.packages), + key=lambda g: len(g.active_packages().all()), reverse=True) if not all_fields: @@ -175,7 +175,7 @@ def group_list_authz(context, data_dict): if available_only: package = context.get('package') if package: - groups = groups - set(package.groups) + groups = groups - set(package.get_groups()) return [{'id':group.id,'name':group.name} for group in groups] diff --git a/ckan/logic/auth/create.py b/ckan/logic/auth/create.py index 88c3bde2f67..80702bf176f 100644 --- a/ckan/logic/auth/create.py +++ b/ckan/logic/auth/create.py @@ -102,9 +102,7 @@ def check_group_auth(context, data_dict): groups.add(grp) if pkg: - pkg_groups = model.Session.query(model.Group).\ - join(model.Member, model.Member.group_id == model.Group.id).\ - filter(model.Member.table_id == pkg.id).all() + pkg_groups = pkg.get_groups() groups = groups - set(pkg_groups) diff --git a/ckan/model/group.py b/ckan/model/group.py index d30456d8d2d..0f4b44e515a 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -71,9 +71,10 @@ def get(cls, reference): def active_packages(self, load_eager=True): query = Session.query(Package).\ filter_by(state=vdm.sqlalchemy.State.ACTIVE).\ - filter_by(id=self.id).\ + filter(group_table.c.id == self.id).\ + filter(member_table.c.state == 'active').\ join(member_table, member_table.c.table_id == Package.id).\ - join(group_table, group_table.c.id == member_table.c.table_id) + join(group_table, group_table.c.id == member_table.c.group_id) return query @classmethod diff --git a/ckan/model/package.py b/ckan/model/package.py index 33b86de4a0b..46837e2fe5a 100644 --- a/ckan/model/package.py +++ b/ckan/model/package.py @@ -192,7 +192,7 @@ def as_dict(self, ref_package_by='name', ref_group_by='name'): tags = [tag.name for tag in self.tags] tags.sort() # so it is determinable _dict['tags'] = tags - groups = [getattr(group, ref_group_by) for group in self.groups] + groups = [getattr(group, ref_group_by) for group in self.get_groups()] groups.sort() _dict['groups'] = groups _dict['extras'] = dict([(key, value) for key, value in self.extras.items()]) @@ -498,7 +498,18 @@ def metadata_modified(self): import ckan.model as model epochtime = self.last_modified(model.package_table.c.id==self.id) return datetime.datetime.utcfromtimestamp(epochtime) - + + def get_groups(self): + import ckan.model as model + if '_groups' not in self.__dict__: + self._groups = model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + join(model.Package, model.Package.id == model.Member.table_id).\ + filter(model.Member.state == 'active').\ + filter(model.Package.id == self.id).all() + return self._groups + + @property def metadata_created(self): import ckan.model as model diff --git a/ckan/templates/package/read.html b/ckan/templates/package/read.html index cdffe4a84ef..89e6a199665 100644 --- a/ckan/templates/package/read.html +++ b/ckan/templates/package/read.html @@ -45,15 +45,15 @@

      Tags

    • Groups

      - +

      - + Groups are collections of dataset maintained by users of ${g.site_title}. This dataset has not been added to any groups yet.

      diff --git a/ckan/tests/functional/test_group.py b/ckan/tests/functional/test_group.py index fe6d4380380..0ed57c42924 100644 --- a/ckan/tests/functional/test_group.py +++ b/ckan/tests/functional/test_group.py @@ -66,7 +66,7 @@ def test_index(self): groupname = 'david' group = model.Group.by_name(unicode(groupname)) group_title = group.title - group_packages_count = len(group.packages) + group_packages_count = len(group.active_packages().all()) group_description = group.description self.check_named_element(res, 'tr', group_title, group_packages_count, group_description) res = res.click(group_title) @@ -178,7 +178,7 @@ def test_2_edit(self): assert group.description == newdesc, group # now look at datasets - assert len(group.packages) == 3 + assert len(group.active_packages().all()) == 3 def test_3_edit_form_has_new_package(self): # check for dataset in autocomplete @@ -222,7 +222,7 @@ def test_4_new_duplicate_package(self): # check package only added to the group once group = model.Group.by_name(group_name) - pkg_names = [pkg.name for pkg in group.packages] + pkg_names = [pkg.name for pkg in group.active_packages().all()] assert_equal(pkg_names, [self.packagename]) def test_edit_plugin_hook(self): @@ -298,9 +298,9 @@ def test_2_new(self): group = model.Group.by_name(group_name) assert group.title == group_title, group assert group.description == group_description, group - assert len(group.packages) == 1 + assert len(group.active_packages().all()) == 1 pkg = model.Package.by_name(self.packagename) - assert group.packages == [pkg] + assert group.active_packages().all() == [pkg] def test_3_new_duplicate_group(self): prefix = '' diff --git a/ckan/tests/functional/test_package.py b/ckan/tests/functional/test_package.py index ae3cce807df..9c19dc9eaf3 100644 --- a/ckan/tests/functional/test_package.py +++ b/ckan/tests/functional/test_package.py @@ -865,7 +865,7 @@ def test_edit_700_groups_unauthorized(self): try: pkg = model.Package.by_name(u'editpkgtest') grp = model.Group.by_name(u'david') - assert len(pkg.groups) == 0 + assert len(pkg.get_groups()) == 0 offset = url_for(controller='package', action='edit', id=pkg.name) res = self.app.get(offset) prefix = '' @@ -881,7 +881,7 @@ def test_edit_700_groups_unauthorized(self): res = fv.submit('save') res = res.follow() pkg = model.Package.by_name(u'editpkgtest') - assert len(pkg.groups) == 0 + assert len(pkg.get_groups()) == 0 finally: self._reset_data() @@ -889,7 +889,7 @@ def test_edit_700_groups_add(self): try: pkg = model.Package.by_name(u'editpkgtest') grp = model.Group.by_name(u'david') - assert len(pkg.groups) == 0 + assert len(pkg.get_groups()) == 0 offset = url_for(controller='package', action='edit', id=pkg.name) res = self.app.get(offset, extra_environ={'REMOTE_USER':'russianfan'}) @@ -901,7 +901,7 @@ def test_edit_700_groups_add(self): res = fv.submit('save', extra_environ={'REMOTE_USER':'russianfan'}) res = res.follow() pkg = model.Package.by_name(u'editpkgtest') - assert len(pkg.groups) == 1, pkg.groups + assert len(pkg.get_groups()) == 1, pkg.get_groups() assert 'david' in res, res finally: self._reset_data() @@ -909,13 +909,13 @@ def test_edit_700_groups_add(self): def test_edit_700_groups_remove(self): try: pkg = model.Package.by_name(u'editpkgtest') - assert len(pkg.groups) == 0 + assert len(pkg.get_groups()) == 0 grp = model.Group.by_name(u'david') model.repo.new_revision() model.Session.add(model.Member(table_id=pkg.id, table_name='package', group=grp)) model.repo.commit_and_remove() pkg = model.Package.by_name(u'editpkgtest') - assert len(pkg.groups) == 1 + assert len(pkg.get_groups()) == 1 offset = url_for(controller='package', action='edit', id=pkg.name) res = self.app.get(offset, extra_environ={'REMOTE_USER':'russianfan'}) prefix = '' @@ -926,7 +926,7 @@ def test_edit_700_groups_remove(self): res = fv.submit('save', extra_environ={'REMOTE_USER':'russianfan'}) model.repo.commit_and_remove() pkg = model.Package.by_name(u'editpkgtest') - assert len(pkg.groups) == 0 + assert len(pkg.get_groups()) == 0 finally: self._reset_data() @@ -1105,7 +1105,7 @@ def test_new_all_fields(self): expected_tagnames = [tag.lower() for tag in tags] expected_tagnames.sort() assert saved_tagnames == expected_tagnames, '%r != %r' % (saved_tagnames, expected_tagnames) - saved_groupnames = [str(group.name) for group in pkg.groups] + saved_groupnames = [str(group.name) for group in pkg.get_groups()] assert len(pkg.extras) == len(extras) for key, value in extras.items(): assert pkg.extras[key] == value diff --git a/ckan/tests/lib/test_dictization_schema.py b/ckan/tests/lib/test_dictization_schema.py index 5a9e882ab09..cea3cde8bbb 100644 --- a/ckan/tests/lib/test_dictization_schema.py +++ b/ckan/tests/lib/test_dictization_schema.py @@ -132,7 +132,7 @@ def test_2_group_schema(self): data = group_dictize(group, context) converted_data, errors = validate(data, default_group_schema(), context) - group_pack = sorted(group.packages, key=lambda x:x.id) + group_pack = sorted(group.active_packages().all(), key=lambda x:x.id) converted_data["packages"] = sorted(converted_data["packages"], key=lambda x:x["id"]) diff --git a/ckan/tests/misc/test_stats.py b/ckan/tests/misc/test_stats.py index 7325085f4b5..f1fc9bafbc6 100644 --- a/ckan/tests/misc/test_stats.py +++ b/ckan/tests/misc/test_stats.py @@ -62,13 +62,16 @@ def test_largest_groups(self): model.repo.new_revision() for group in model.Session.query(model.Group): - group.packages = [] - model.Group.by_name(u'tst1').packages = [model.Package.by_name(u'gils'), - model.Package.by_name(u'us-gov-images'), - model.Package.by_name(u'usa-courts-gov')] - model.Group.by_name(u'tst2').packages = [model.Package.by_name(u'us-gov-images'), - model.Package.by_name(u'usa-courts-gov')] - model.Group.by_name(u'tst3').packages = [model.Package.by_name(u'usa-courts-gov')] + group.member_all = [] + grp1 = model.Group.by_name(u'tst1') + grp2 = model.Group.by_name(u'tst2') + grp3 = model.Group.by_name(u'tst3') + model.Session.add(model.Member(group = grp1, table_name='package', table_id=model.Package.by_name(u'gils').id)) + model.Session.add(model.Member(group = grp1, table_name='package', table_id=model.Package.by_name(u'us-gov-images').id)) + model.Session.add(model.Member(group = grp1, table_name='package', table_id=model.Package.by_name(u'usa-courts-gov').id)) + model.Session.add(model.Member(group = grp2, table_name='package', table_id=model.Package.by_name(u'us-gov-images').id)) + model.Session.add(model.Member(group = grp2, table_name='package', table_id=model.Package.by_name(u'usa-courts-gov').id)) + model.Session.add(model.Member(group = grp3, table_name='package', table_id=model.Package.by_name(u'usa-courts-gov').id)) model.repo.commit_and_remove() res = Stats().largest_groups() diff --git a/ckan/tests/models/test_group.py b/ckan/tests/models/test_group.py index 7e85c4f12f8..92ccc5b4a48 100644 --- a/ckan/tests/models/test_group.py +++ b/ckan/tests/models/test_group.py @@ -23,25 +23,33 @@ def test_1_basic(self): grp = model.Group.by_name(u'group1') assert grp.title == u'Test Group' assert grp.description == u'This is a test group' - assert grp.packages == [] + assert grp.active_packages().all() == [] def test_2_add_packages(self): model.repo.new_revision() + self.russian_group = model.Group(name=u'russian', title=u'Russian Group', description=u'This is the russian group') model.Session.add(self.russian_group) anna = model.Package.by_name(u'annakarenina') war = model.Package.by_name(u'warandpeace') - self.russian_group.packages = [anna, war] + model.Session.add(model.Member(group=self.russian_group, + table_id=anna.id, + table_name='package') + ) + model.Session.add(model.Member(group=self.russian_group, + table_id=war.id, + table_name='package') + ) model.repo.commit_and_remove() grp = model.Group.by_name(u'russian') assert grp.title == u'Russian Group' anna = model.Package.by_name(u'annakarenina') war = model.Package.by_name(u'warandpeace') - assert set(grp.packages) == set((anna, war)), grp.packages - assert grp in anna.groups + assert set(grp.active_packages().all()) == set((anna, war)), grp.active_packages().all() + assert grp in anna.get_groups() class TestGroupRevisions: From 58b7a09328111b97da7d8ac65b4710b94dac54e3 Mon Sep 17 00:00:00 2001 From: kindly Date: Thu, 24 Nov 2011 22:24:37 +0000 Subject: [PATCH 24/70] fix migration so that not null gets added later --- .../versions/046_rename_package_group_member.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ckan/migration/versions/046_rename_package_group_member.py b/ckan/migration/versions/046_rename_package_group_member.py index ae8b302d6ab..442da705d0d 100644 --- a/ckan/migration/versions/046_rename_package_group_member.py +++ b/ckan/migration/versions/046_rename_package_group_member.py @@ -40,10 +40,10 @@ def upgrade(migrate_engine): ALTER TABLE "member" - ADD COLUMN table_name text not null; + ADD COLUMN table_name text; ALTER TABLE member_revision - ADD COLUMN table_name text not null; + ADD COLUMN table_name text; ALTER TABLE "member" ADD CONSTRAINT member_pkey PRIMARY KEY (id); @@ -68,6 +68,12 @@ def upgrade(migrate_engine): update member set table_name = 'package'; update member_revision set table_name = 'package'; + +ALTER TABLE "member" + ALTER COLUMN table_name set not null; + +ALTER TABLE member_revision + ALTER COLUMN table_name set not null; COMMIT; ''' ) From 61798168e01912804917826226a4bdcb3ffe8e92 Mon Sep 17 00:00:00 2001 From: rgrp Date: Fri, 25 Nov 2011 07:15:01 +0000 Subject: [PATCH 25/70] [js,resource/view][s]: include dataexplorer js locally and update references in resource view. * Have not updated package (dataset) view yet as about to remove the dataexplorer code from there. --- .../scripts/dataexplorer/icon-sprite.png | Bin 0 -> 1699 bytes ckan/public/scripts/dataexplorer/loading.gif | Bin 0 -> 1849 bytes .../dataexplorer/table-view-template.js | 49 + .../scripts/dataexplorer/table-view.css | 254 ++++ .../public/scripts/dataexplorer/table-view.js | 207 +++ .../scripts/dataexplorer/table-view.ui.js | 1154 +++++++++++++++++ ckan/templates/package/resource_read.html | 13 +- 7 files changed, 1668 insertions(+), 9 deletions(-) create mode 100644 ckan/public/scripts/dataexplorer/icon-sprite.png create mode 100644 ckan/public/scripts/dataexplorer/loading.gif create mode 100644 ckan/public/scripts/dataexplorer/table-view-template.js create mode 100644 ckan/public/scripts/dataexplorer/table-view.css create mode 100644 ckan/public/scripts/dataexplorer/table-view.js create mode 100644 ckan/public/scripts/dataexplorer/table-view.ui.js diff --git a/ckan/public/scripts/dataexplorer/icon-sprite.png b/ckan/public/scripts/dataexplorer/icon-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..c0799191dacfb116fdb4d4cd0de6637a73de9c4c GIT binary patch literal 1699 zcmaJ?Yfuwc7>!Wz1yrO~VH{i+hEfa3CLsw4K^w9Wq6Cu=OOQ4tBn!llY?>@2M9~qX zmZ4Oo)gs}gb)o_-h%zFgh_MR7OtIxr1+0jpJSwKuia4U(AYgw;cV_qA@6I{rd*0bx zVRVGsV(-NS0>O>fDwa2 zuhL;ER0}v1<%wt-76#&xzD_}-o0ZiXrptsEjAWAQNMs`9NNENr5d1$>qnSk;FfsBb z-nR-HBpEt{Bt{HqnqGl7F3H#7O2^{r5jlqHB`BIYQ$=A4ilK%SR0nWlXn;VjP-z|Z z6kZ@;@wEm_u2mp>I1I!Uh$@wm1@owkU5eE+u46y=ks;?pP>u za!GhFBu8Wa)99Hae0m()*|Eiq*}+G&__XWsv5r2s)fvAnEBG)+V!HKbVVl~Mv+5c{ zRWX0LxG2kK8~HEmSCRkRbN1udcbhza24!$BD1CB=V?Vl}(qvB)3$J7aL@r+)pI2_O zTY+^cFA1VX3AAqT!SN=yZ(FT5PxyHx-LWatpN2lR#HH_?&{jzU&0_tzPgivL5AChi z`CQHXqwMdhWwFhP6`?wh`^`@E^LmCy>v*S9>6W8A2?qIU_U?D8vSaKk>5yX0QvL(8 z)TwuPCC!C>EwH3B0$6gk-G1+~AN$GLHrg}SwuiLL-Ci9&V8JM_zmRf6Q~0ih3!gtz z3X!fCjoCg6x$a$1t6){8PL{hjMlpuQ$65_{@B3sD)xpK(?cH0`wT1bt_+`VV3!e3i z97WvrCCp!yeb~Op{ZdZBiLLUQc@-W*9h)i#W%=Qe$tT>m{r>&HmdkmcD1&|(n4jHP z?In}uZr>#=Zpkf9F^dAF(m?*pL)25TW8?#1+@M!u z&X49>R{6ft!2%I0Rn+uNLy+K#hn2c`A-AlD~a_H z$?pw(#}OT{?LvXIB{l4pmN?t#vrJ|00?E?$h6-zB4%hBXKGI6prp( zB^rKp+vM*#LaUwFQQz~iuuI($J4}0lu?l(;uIj2e5r=yc4!ydquK99EmTmFPaHCiB zseuBz>U9mgMf)Nv6ols)lC6W>6uYzp`D&4h0C-#1=J@Sg|>@sExoZCsq`cD}A; zucc;vi>qC!cM0!~m^Re)PqekwpYF@WD;j0Fbh6x)zJ|U1d3US#@?_>3 z>({z{-0g~5muu;~?nr1-YIl|IeV$idb?nui8)HB8ub}ox_tTEO)GG7$bfw4HaFqLd!!EF|6TH0~N!z zh~$9~mU)ewQ z_ve{&=6vS+JHI*EyRr=1tPp|7KM>^Evu9?r`SIh&V`F2>%gauub9i_-xZ?GC_wL<0 zKRtgNiMx;nq#kNiIs`;0Eg-2HNfu`n}fs}5TiJRAFONk)^+ddRk4 z>a-s^Ahp(7Tj~#5_iweoR&x-EeG2$gaF;rCO9+YJPN@_{`LW-OqGl6$)94@1iSgaP z6}Tk2GH;#0-ZQ%c@Nb-S?{uW`;VV!4g(KYR?__&C?kh!c{69P}xhm4n-KK9OUKBB6 z{NSDKNpi_yj02RsRh(=z(?RGPNMhO-IbpZ(IuFBSY9$TS5@n-8AXAHw>0!)#ht(4Wj)S*45GS&?oJ=sm?4fb?b z8r$^x^ZKy4)UUNj9LhyT-dx=jq;zhinVDGx)BMSx%K;+yWcv#R=@*#0zmM4M&3tqh z&P08%;rqr7P_jJo_etB^%(r_E<|S2?mi_ouPl@kpV(l+WfsH%VoTJq`Ex+jS)wIQ( z@SV~Ro{S3%XbS;8QU?KU8R=P-EU2hs>5QZJN<8YFh|}>#&R8x;=OkNhR1YaeqOieS zj*HoJ{ki#TqDYB0ao&!lz^;I0rsT532Qbsuvn4>tHXrfIstE$O7TC26QAsdLWv_-H zk$hYIQ9eO8*pDE9U>j`Z31LaU3XyifASoatUwo`zPz6S1X_>cAd0kw#LXp4uo};mr zE9-dQ_w?fkDdYVip1TYn?z~i{ltQng9#c^LovwSA`$)3!r)JgqD1s>7T+M@g!a@j@ zN(Dn;#Op_^a{ZudXH|uO*doYoNF#gX;WcXmy{17`>f=(yf@)UrvnjP)lqg;|CH-yL z)E{?Q55)4aPhYgPh~Jocl&kBqUDwYAxRTaA<2YDN`FPsgFx3$+7u^2McMdT_@hb>C z8^K!meD(;!C4g4IFsP7tA%P+iPipv?)>O1!8Q!v%jPmT#=-XS2XY5)r>GT46 zd{lRiww_7<^YObA>s~du_E}OMzBaOn4vd=iA?|&jw8YHf>dEyUnzqJB(y%MXNYciS*VkYD8xc{?A{L7re z8hga-k{u};1pOXkA?Mf#Gk9#g8DR@~5D1G>^b8)vu=Z*@3dw?6os;z{!;jQ|yR?;Q zoE@dq*(m)%E<~CXAI9YR4Gh_tv9hF!=
      ' + $.trim(error.message); + dp.$dialog.html(_html).dialog(dp.errorDialogOptions); + }; + + // Public: Displays the datapreview UI in a fullscreen dialog. + // + // This method also parses the data returned by the webstore for use in + // the data preview UI. + // + // data - An object of parsed CSV data returned by the webstore. + // + // Returns nothing. + // + dp.showData = function(data) { + dp.setupFullscreenDialog(); + + if(data.error) { + return dp.showError(data.error); + } + var tabular = dp.convertData(data); + + dp.loadTableView(tabular.columns, tabular.data); + }; + + // **Public: parse data from webstore or other source into form for data + // preview UI** + // + // :param data: An object of parsed CSV data returned by the webstore. + // + // :return: parsed data. + dp.convertData = function(data) { + var tabular = { + columns: [], + data: [] + }; + isNumericRegex = (/^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/); + + // two types of data: that from webstore and that from jsonpdataproxy + // if fields then from dataproxy + if (data.fields) { + tabular.columns = $.map(data.fields || [], function (column, i) { + return {id: 'header-' + i, name: column, field: 'column-' + i, sortable: true}; + }); + + tabular.data = $.map(data.data || [], function (row, id) { + var cells = {id: id}; + for (var i = 0, c = row.length; i < c; i++) { + var isNumeric = isNumericRegex.test(row[i]); + cells['column-' + i] = isNumeric ? parseFloat(row[i]) : row[i]; + } + return cells; + }); + } else { + tabular.columns = $.map(data.keys, function(key, idx) { + return {id: 'header-' + key, name: key, field: 'column-' + key, sortable: true}; + }); + tabular.data = $.map(data.data, function(row, id) { + var cells = {id: id}; + for(i in row) { + var val = row[i]; + var isNumeric = isNumericRegex.test(val); + cells['column-' + tabular.columns[i].name] = isNumeric ? parseFloat(val) : val; + } + return cells; + }); + } + return tabular; + }; + + // Public: Kickstarts the plugin. + // + // dialogId - The id of the dialog Element in the page. + // options - An object containing aditional options. + // timeout: Time in seconds to wait for a JSONP timeout. + // + // Returns nothing. + // + dp.initialize = function(dialogId, options) { + dp.$dialog = $('#' + dialogId); + options = options || {}; + + dp.timeout = options.timeout || dp.timeout; + + var _height = Math.round($(window).height() * 0.6); + + // Large stylable dialog for displaying data. + dp.dialogOptions = { + autoOpen: false, + // does not seem to work for width ... + position: ['center', 'center'], + buttons: [], + width: $(window).width() - 20, + height: $(window).height() - 20, + resize: 'auto', + modal: false, + draggable: true, + resizable: true + }; + + // Smaller alert style dialog for error messages. + dp.errorDialogOptions = { + title: 'Unable to Preview - Had an error from dataproxy', + position: ['center', 'center'], + buttons: [{ + text: "OK", + click: function () { $(this).dialog("close"); } + }], + width: 360, + height: 180, + resizable: false, + draggable: false, + modal: true, + position: 'fixed' + }; + }; + + // Export the DATAEXPLORER object onto the window. + $.extend(true, window, {DATAEXPLORER: {}}); + DATAEXPLORER.TABLEVIEW = dp; + +})(jQuery); diff --git a/ckan/public/scripts/dataexplorer/table-view.ui.js b/ckan/public/scripts/dataexplorer/table-view.ui.js new file mode 100644 index 00000000000..1d0c540dff9 --- /dev/null +++ b/ckan/public/scripts/dataexplorer/table-view.ui.js @@ -0,0 +1,1154 @@ +// This file contains all of the UI elements aside from the containing element +// (ie. lightbox) used to build the datapreview widget. The MainView should +// be initiated with an element containing the elements required by the +// subviews. +// +// Use TABLEVIEW.createTableView() to create new instances of MainView. +// +// Examples +// +// var $element = $(templateString); +// var datapreview = TABLEVIEW.createTableView($element); +// +// +(function ($, undefined) { + + var ui = {}; + + // Binds methods on an object to always be called with the object as the + // method context. + // + // context - An object with methods to bind. + // arguments* - Following arguments should be method names to bind. + // + // Examples + // + // var object = { + // method1: function () { + // return this; + // }, + // method2: function () {} + // }; + // + // bindAll(object, 'method1', 'method2'); + // + // object.method1.call(window) === object //=> true; + // + // Returns the context argument. + // + function bindAll(context) { + var args = [].slice.call(arguments, 0), i = 0, count = args.length; + for (; i < count; i += 1) { + context[args[i]] = $.proxy(context[args[i]], context); + } + return context; + } + + // Creates a new object that inherits from the proto argument. + // + // Source: http://github.com/aron/inheritance.js + // + // This function will use Object.create() if it exists otherwise falls back + // to using a dummy constructor function to create a new object instance. + // Unlike Object.create() this function will always return a new object even + // if a non object is provided as an argument. + // + // proto - An object to use for the new objects internal prototype. + // + // Examples + // + // var appleObject = {color: 'green'} + // var appleInstance = create(appleObject); + // + // appleInstance.hasOwnProperty('color'); //=> false + // appleInstance.color === appleObject.color; //=> true + // + // Returns a newly created object. + // + function create(proto) { + if (typeof proto !== 'object') { + return {}; + } + else if (Object.create) { + return Object.create(proto); + } + function DummyObject() {} + DummyObject.prototype = proto; + return new DummyObject(); + } + + // Public: Creates a new constructor function that inherits from a parent. + // + // Source: http://github.com/aron/inheritance.js + // + // Instance and static methods can also be provided as additional arguments. + // if the methods argument has a property called "constructor" this will be + // used as the constructor function. + // + // Static methods will also be copied over from the parent object. However + // these will not be inheritied prototypally as with the instance methods. + // + // parent - A constructor Function to inherit from. + // methods - An Object literal of instance methods that are added to the + // constructors prototype. + // properties - An Object literal of static/class methods to add to the + // constructor itself. + // + // Examples + // + // function MyObject() {}; + // + // var SubClass = inherit(MyObject, {method: function () {}}); + // var instance = new SubClass(); + // + // instance instanceof MyObject //=> true + // + // Returns the new constructor Function. + // + function inherit(parent, methods, properties) { + methods = methods || {}; + + var Child = methods.hasOwnProperty('constructor') ? + methods.constructor : inherit.constructor(parent); + + Child.prototype = create(parent.prototype); + Child.prototype.constructor = Child; + + delete methods.constructor; + $.extend(Child.prototype, methods, {__super__: parent.prototype}); + + return $.extend(Child, parent, properties); + } + + // Public: Base view object that other views should inherit from. + // + // A wrapper around a dom element (itself wrapped in jQuery). Provides useful + // features such as pub/sub methods, and show/hide toggling of the element. + // + // Implements Ben Allman's Tiny PubSub, https://gist.github.com/661855 + // + // element - A jQuery wrapper, DOM Element or selector String. + // + // Examples + // + // var myView = new View('my-element'); + // + // Returns a new View instance. + // + ui.View = inherit({}, { + constructor: function View(element) { + this.el = element instanceof $ ? element : $(element); + + // Use a custom empty jQuery wrapper rather than this.el to prevent + // browser events being triggered. + this.events = $({}); + }, + + // Public: Performs a jQuery lookup within the views element. + // + // selector - A selector String to query. + // + // Examples + // + // this.$('.some-child-class'); + // + // Returns a jQuery collection. + // + $: function (selector) { + return this.el.find(selector); + }, + + // Public: Registers a listener for a topic that will be called when the + // event is triggered. Optionally an Object of topic/callback pairs can + // be passed to the method. Built on top of the jQuery .bind() method + // so other features like namespaces can also be used. + // + // topic - Topic string to subscribe to. + // fn - Callback function to be called when the topic is triggered. + // + // Examples + // + // view.bind('my-event', onMyEvent); + // view.bind({ + // 'my-event', onMyEvent, + // 'my-other-events': onMyOtherEvent + // }); + // + // Returns itself for chaining. + // + bind: function (topic, fn) { + if (arguments.length === 1) { + for (var key in topic) { + if (topic.hasOwnProperty(key)) { + this.bind(key, topic[key]); + } + } + return this; + } + + function wrapper() { + return fn.apply(this, Array.prototype.slice.call(arguments, 1)); + } + wrapper.guid = fn.guid = fn.guid || ($.guid ? $.guid++ : $.event.guid++); + this.events.bind(topic, wrapper); + return this; + }, + + // Public: Unbinds a callback for a topic. + // + // Accepts the same arguments as jQuery's .unbind(). + // + // topic - The topic to unbind. + // fn - A specific function to unbind from the topic. + // + // Examples + // + // view.unbind('my-event'); + // + // Returns itself for chaining. + // + unbind: function () { + this.events.unbind.apply(this.events, arguments); + return this; + }, + + // Public: Triggers a topic providing an array of arguments to all listeners. + // + // topic - A topic to publish. + // args - An Array of arguments to pass into registered listeners. + // + // Examples + // + // view.trigger('my-event', [anArg, anotherArg]); + // + // Returns itself. + // + trigger: function () { + this.events.triggerHandler.apply(this.events, arguments); + return this; + }, + + // Public: Shows the element if hidden. + // + // Returns itself. + // + show: function () { + this.el.show(); + return this.trigger('show'); + }, + + // Public: Hides the element if shown. + // + // Returns itself. + // + hide: function () { + this.el.hide(); + return this.trigger('hide'); + } + }); + + // Public: Main view object for the data preview plugin. + // + // Contains the main interface elements and acts as a controller binding + // them together. + // + // element - The main DOM Element used for the plugin. + // columns - The columns array for the data rows formatted for SlickGrid. + // data - A data object formatted for use in SlickGrid. + // chart - A chart object to load. + // + // Examples + // + // new MainView($('.datapraview-wrapper'), columns, data); + // + // Returns a new instance of MainView. + // + ui.MainView = inherit(ui.View, { + constructor: function MainView(element, columns, data, chart) { + this.__super__.constructor.apply(this, arguments); + + bindAll(this, 'redraw', 'onNavChange', 'onNavToggleEditor', 'onEditorSubmit'); + + var view = this; + this.nav = new ui.NavigationView(this.$('.dataexplorer-tableview-nav')); + this.grid = new ui.GridView(this.$('.dataexplorer-tableview-grid'), columns, data); + this.chart = new ui.ChartView(this.$('.dataexplorer-tableview-graph'), columns, data); + this.editor = new ui.EditorView(this.$('.dataexplorer-tableview-editor'), columns, chart); + + this.nav.bind({ + 'change': this.onNavChange, + 'toggle-editor': this.onNavToggleEditor + }); + this.editor.bind({ + 'show hide': this.redraw, + 'submit': this.onEditorSubmit + }); + + this.$('.dataexplorer-tableview-editor-info h1').click(function () { + $(this).parent().toggleClass('dataexplorer-tableview-editor-hide-info'); + }); + + this.chart.hide(); + }, + + // Public: Redraws the both the grid and chart views. + // + // Useful if the viewport changes or is resized. + // + // Examples + // + // view.resize(); + // + // Returns itself. + // + redraw: function () { + this.chart.redraw(); + this.grid.redraw(); + return this; + }, + + // Public: Toggles the display of the grid and chart views. + // + // Used as a callback function for the NavigationView "change" event. + // + // selected - The name of the newly selected view. + // + // Returns nothing. + // + onNavChange: function (selected) { + var isGrid = selected === 'grid'; + this.grid[isGrid ? 'show' : 'hide'](); + this.chart[isGrid ? 'hide' : 'show'](); + }, + + // Public: Toggles the display of the editor panel. + // + // Used as a callback function for the NavigationView "toggle-editor" event. + // + // showEditor - True if the editor should be visible. + // + // Returns nothing. + // + onNavToggleEditor: function (showEditor) { + this.el.toggleClass('dataexplorer-tableview-hide-editor', !showEditor); + this.redraw(); + }, + + // Public: Updates the chart view when the editor is submitted. + // + // chart - The chart object to render. + // + // Returns nothing. + // + onEditorSubmit: function (chart) { + this.nav.toggle('chart'); + this.chart.update(chart); + } + }); + + // Public: Navigation element for switching between views. + // + // Handles the toggling of views within the plugin by firing events when + // buttons are clicked within the view. + // + // element - The Element to use as navigation. + // + // Examples + // + // var nav = new NavigationView($('.dataexplorer-tableview-nav')); + // + // // Recieve events when the navigation buttons are clicked. + // nav.bind('change', onNavigationChangeHandler); + // + // Returns a new instance of NavigationView. + // + ui.NavigationView = inherit(ui.View, { + constructor: function NavigationView(element) { + this.__super__.constructor.apply(this, arguments); + + bindAll(this, 'onEditorToggleChange', 'onPanelToggleChange'); + + this.panelButtons = this.$('.dataexplorer-tableview-nav-toggle').buttonset(); + this.panelButtons.change(this.onPanelToggleChange); + + this.editorButton = this.$('#dataexplorer-tableview-nav-editor').button(); + this.editorButton.change(this.onEditorToggleChange); + }, + + // Public: Toggles a navigation button. + // + // Triggers the "change" event with the panel name provided. + // + // panel - The name of a button to be selected. + // + // Examples + // + // nav.toggle("grid"); + // + // Returns itself. + // + toggle: function (panel) { + // Need to fire all these events just to get jQuery UI to change state. + this.$('input[value="' + panel + '"]').click().change().next().click(); + return this; + }, + + // Public: Triggers the "change" event when the navgation changes. + // + // Passes the name of the selected item into all callbacks. + // + // event - An event object. + // + // Returns nothing + // + onPanelToggleChange: function (event) { + this.trigger('change', [event.target.value]); + }, + + // Public: Triggers the "toggle-editor" event when the editor button is + // clicked. Passes true into callbacks if the button is active. + // + // event - An event object. + // + // Returns nothing + // + onEditorToggleChange: function (event) { + this.trigger('toggle-editor', [event.target.checked]); + } + }); + + // Public: Creates and manages a SlickGrid instance for displaying the + // resource data in a useful grid. + // + // SlickGrid documentation: http://github.com/mleibman/SlickGrid/wiki + // + // element - The Element to use as a container for the SlickGrid. + // columns - Column options formatted for use in the SlickGrid container. + // data - Data Object formatted for use in the SlickGrid. + // options - Additional instance and SlickGrid options. + // + // Examples + // + // var grid = new GridView($('.dataexplorer-tableview-grid'), columns, data); + // + // Returns a new instance of GridView. + // + ui.GridView = inherit(ui.View, { + constructor: function GridView(element, columns, data, options) { + this.__super__.constructor.apply(this, arguments); + + bindAll(this, '_onSort', 'redraw'); + + this.dirty = false; + this.columns = columns; + this.data = data; + this.grid = new Slick.Grid(element, data, columns, $.extend({ + enableColumnReorder: false, + forceFitColumns: true, + syncColumnCellResize: true, + enableCellRangeSelection: false + }, options)); + + this.grid.onSort = this._onSort; + + // In order to extend the resize handles across into the adjacent column + // we need to disable overflow hidden and increase each cells z-index. + // We then wrap the contents in order to reapply the overflow hidden. + this.$('.slick-header-column') + .wrapInner('
      ') + .css('overflow', 'visible') + .css('z-index', function (index) { + return columns.length - index; + }); + + new Slick.Controls.ColumnPicker(this.columns, this.grid); + }, + + // Public: Reveals the view. + // + // If the dirty property is true then it will redraw the grid. + // + // Examples + // + // grid.show(); + // + // Returns itself. + // + show: function () { + this.__super__.show.apply(this, arguments); + if (this.dirty) { + this.redraw(); + this.dirty = false; + } + return this; + }, + + // Public: Redraws the grid. + // + // The grid will only be drawn if the element is visible. If hidden the + // dirty property will be set to true and the grid redrawn the next time + // the view is shown. + // + // Examples + // + // grid.redraw(); + // + // Returns itself. + // + redraw: function () { + if (this.el.is(':visible')) { + this.grid.resizeCanvas(); + this.grid.autosizeColumns(); + } else { + this.dirty = true; + } + }, + + // Public: Sort callback for the SlickGrid grid. + // + // Called when the grids columns are re-ordered. Accepts the selected + // column and the direction and should sort the data property. + // + // column - The column object being sorted. + // sortAsc - True if the solumn should be sorted by ascending items. + // + // Returns nothing. + // + _onSort: function (column, sortAsc) { + this.data.sort(function (a, b) { + var x = a[column.field], + y = b[column.field]; + + if (x == y) { + return 0; + } + return (x > y ? 1 : -1) * (sortAsc ? 1 : -1); + }); + this.grid.invalidate(); + } + }); + + // Public: Creates a wrapper around a jQuery.Flot() chart. + // + // Currently a very basic implementation that accepts data prepared for the + // SlickGrid, ie columns and data objects and uses them to generate a canvas + // chart. + // + // Flot documentation: http://people.iola.dk/olau/flot/API.txt + // + // element - Element to use as a container for the Flot canvas. + // columns - Array of column data. + // data - Data Object. + // chart - Optional chart data to load. + // + // Examples + // + // new ChartView($('.dataexplorer-tableview-chart'), columns, data, { + // id: 'my-chart-id', // Any unique id for the chart used for storage. + // type: 'line', // ID of one of the ChartView.TYPES. + // groups: 'column-2', // The column to use as the x-axis. + // series: ['column-3', 'column-4'] // Columns to use as the series. + // }); + // + // Returns a new instance of ChartView. + // + ui.ChartView = inherit(ui.View, { + constructor: function ChartView(element, columns, data, chart) { + this.__super__.constructor.apply(this, arguments); + this.data = data; + this.columns = columns; + this.chart = chart; + this.createPlot((chart && chart.type) || 'line'); + this.draw(); + }, + + // Public: Creates a new Flot chart and assigns it to the plot property. + // + // typeId - The id String of the grid to create used to load chart + // specific options on creation. + // + // Examples + // + // chart.createPlot('line'); + // + // Returns itself. + // + createPlot: function (typeId) { + var type = ui.ChartView.findTypeById(typeId), + options = type && type.getOptions ? type.getOptions(this) : {}; + + this.plot = $.plot(this.el, this.createSeries(), options); + return this; + }, + + // Public: Creates the series/data Array required by jQuery.plot() + // + // Examples + // + // $.plot(editor.el, editor.createSeries(), options); + // + // Returns an Array containing points for each series in the chart. + // + createSeries: function () { + var series = [], view = this; + if (this.chart) { + $.each(this.chart.series, function (seriesIndex, field) { + var points = []; + $.each(view.data, function (index) { + var x = this[view.chart.groups], y = this[field]; + if (typeof x === 'string') { + x = index; + } + points.push([x, y]); + }); + series.push({data: points, label: view._getColumnName(field)}); + }); + } + return series; + }, + + // Public: Redraws the chart with regenerated series data. + // + // Usually .update() will be called instead. + // + // Returns itself. + // + draw: function () { + this.plot.setData(this.createSeries()); + return this.redraw(); + }, + + // Public: Updates the current plot with a new chart Object. + // + // chart - A chart Object usually provided by the EditorView. + // + // Examples + // + // editor.bind('submit', function (chart) { + // chart.update(chart); + // }); + // + // Returns itself. + // + update: function (chart) { + if (!this.chart || chart.type !== this.chart.type) { + this.createPlot(chart.type); + } + this.chart = chart; + this.draw(); + return this; + }, + + // Public: Redraws the current chart in the canvas. + // + // Used if the chart data has changed or the viewport has been resized. + // + // Examples + // + // $(window).resize(function () { + // chart.redraw(); + // }); + // + // Returns itself. + // + redraw: function () { + this.plot.resize(); + this.plot.setupGrid(); + this.plot.draw(); + return this; + }, + + // Public: Gets the human readable column name for the field id. + // + // field - A field id String used in the data object. + // + // Examples + // + // chart._getColumnName('column-1'); + // + // Returns the String column name. + // + _getColumnName: function (field) { + for (var i = 0, count = this.columns.length; i < count; i += 1) { + if (this.columns[i].field === field) { + return this.columns[i].name; + } + } + return name; + } + }, { + // Array of chart formatters. They require an id and name attribute and + // and optional getOptions() method. Used to generate different chart types. + // + // id - A unique id String for the chart type. + // name - A human readable name for the type. + // getOptions - Function that accepts an instance of ChartView and returns + // an options object suitable for use in $.plot(); + // + TYPES: [{ + id: 'line', + name: 'Line Chart' + }, { + id: 'bar', + name: 'Bar Chart (draft)', + getOptions: function (view) { + return { + series: { + lines: {show: false}, + bars: { + show: true, + barWidth: 1, + align: "left", + fill: true + } + }, + xaxis: { + tickSize: 1, + tickLength: 1, + tickFormatter: function (val) { + if (view.data[val]) { + return view.data[val][view.chart.groups]; + } + return ''; + } + } + }; + } + }], + + // Public: Helper method for findind a chart type by id key. + // + // id - The id key to search for in the ChartView.TYPES Array. + // + // Examples + // + // var type = ChartView.findTypeById('line'); + // + // Returns the type object or null if not found. + // + findTypeById: function (id) { + var filtered = $.grep(this.TYPES, function (type) { + return type.id === id; + }); + return filtered.length ? filtered[0] : null; + } + }); + + // Public: Creates a form for editing chart metadata. + // + // Publishes "submit" and "save" events providing a chart Obejct to all + // registered callbacks. + // + // element - The Element to use as the form wrapper. + // columns - Array of columns that are used by the data set. + // chart - Optional chart Object to display on load. + // + // Examples + // + // new EditorView($('.dataexplorer-tableview-editor'), columns, { + // id: 'my-chart-id', + // type: 'line', + // groups: 'column-2', + // series: ['column-3', 'column-4'] + // }); + // + // Returns a new instance of EditorView. + // + ui.EditorView = inherit(ui.View, { + constructor: function EditorView(element, columns, chart) { + this.__super__.constructor.apply(this, arguments); + + bindAll(this, 'onAdd', 'onRemove', 'onSubmit', 'onSave'); + + this.columns = columns; + this.type = this.$('.dataexplorer-tableview-editor-type select'); + this.groups = this.$('.dataexplorer-tableview-editor-group select'); + this.series = this.$('.dataexplorer-tableview-editor-series select'); + this.id = this.$('.dataexplorer-tableview-editor-id'); + + this.$('button').button(); + this.save = this.$('.dataexplorer-tableview-editor-save').click(this.onSave); + this.el.bind('submit', this.onSubmit); + this.el.delegate('a[href="#remove"]', 'click', this.onRemove); + this.el.delegate('select', 'change', this.onSubmit); + + this.$('.dataexplorer-tableview-editor-add').click(this.onAdd); + + this.setupTypeOptions().setupColumnOptions(); + + this.seriesClone = this.series.parent().clone(); + + if (chart) { + this.load(chart); + } + }, + + // Public: Fills the "type" select box with options. + // + // Returns itself. + // + setupTypeOptions: function () { + var types = {}; + // TODO: This shouldn't be referenced directly but passed in as an option. + $.each(ui.ChartView.TYPES, function () { + types[this.id] = this.name; + }); + + this.type.html(this._createOptions(types)); + return this; + }, + + // Public: Fills the groups and series select elements with options. + // + // Returns nothing. + // + setupColumnOptions: function () { + var options = {}, optionsString = ''; + $.each(this.columns, function (index, column) { + options[column.field] = column.name; + }); + optionsString = this._createOptions(options); + + this.groups.html(optionsString); + this.series.html(optionsString); + return this; + }, + + // Public: Adds a new empty series select box to the editor. + // + // All but the first select box will have a remove button that allows them + // to be removed. + // + // Examples + // + // editor.addSeries(); + // + // Returns itself. + // + addSeries: function () { + var element = this.seriesClone.clone(), + label = element.find('label'), + index = this.series.length; + + this.$('ul').append(element); + this.updateSeries(); + + label.append('Remove'); + label.find('span').text(String.fromCharCode(this.series.length + 64)); + + return this; + }, + + // Public: Removes a series list item from the editor. + // + // Also updates the labels of the remianing series elements. + // + // element - A jQuery wrapped list item Element to remove. + // + // Examples + // + // // Remove the third series element. + // editor.removeSeries(editor.series.eq(2).parent()); + // + // Returns itself. + // + removeSeries: function (element) { + element.remove(); + this.updateSeries(); + this.series.each(function (index) { + if (index > 0) { + var labelSpan = $(this).prev().find('span'); + labelSpan.text(String.fromCharCode(index + 65)); + } + }); + return this.submit(); + }, + + // Public: Resets the series property to reference the select elements. + // + // Returns itself. + // + updateSeries: function () { + this.series = this.$('.dataexplorer-tableview-editor-series select'); + return this; + }, + + // Public: Loads a chart into the editor. + // + // For an example of the chart object structure see the ChartView docs. + // + // chart - A chart Object to be loaded. + // + // Examples + // + // editor.load(chart); + // + // Returns itself. + // + load: function (chart) { + var editor = this; + this._selectOption(this.type, chart.type); + this._selectOption(this.groups, chart.groups); + + this.id.val(chart.id); + this.type.val(chart.type); + $.each(chart.series, function update(index, option) { + var element = editor.series.eq(index); + if (!element.length) { + editor.addSeries(); + return update(index, option); + } + editor._selectOption(element, option); + }); + + return this; + }, + + // Public: Submits the current form. + // + // Triggers the "submit" event providing a chart object to all listeners. + // + // Examples + // + // editor.bind("submit", chart.update); + // editor.submit(); + // + // Returns itself. + // + submit: function () { + return this._triggerChartData('submit'); + }, + + // Public: Toggles the loading state on the view. + // + // Freezes all interface elements and displays a loading message. + // + // show - If false disables the loading state. + // + // Examples + // + // // Set the state to loading. + // editor.loading(); + // + // // Disable the loading state. + // editor.loading(false); + // + // Returns itself. + // + loading: function (show) { + var action = show === false ? 'enable' : 'disable'; + + this.$('select').attr('disabled', show !== false); + this.save.button(action); + + this._updateSaveText(show === false ? null : 'Loading...'); + + return this; + }, + + // Public: Toggles the saving state on the view. + // + // show - If false disables the saving state. + // + // Examples + // + // // Set the state to saving. + // editor.saving(); + // + // // Disable the saving state. + // editor.saving(false); + // + // Returns itself. + // + saving: function (show) { + this.disableSave(show); + this._updateSaveText(show === false ? null : 'Saving...'); + return this; + }, + + // Public: Toggles the save button state between enabled/disabled. + // + // disable - If false enables the button. + // + // Returns itself. + // + disableSave: function (disable) { + this.save.button(disable === false ? 'enable' : 'disable'); + return this; + }, + + // Public: Event callback for the "Add series" button. + // + // event - A jQuery Event object. + // + // Examples + // + // $('button').click(event.onAdd); + // + // Returns nothing. + // + onAdd: function (event) { + event.preventDefault(); + this.addSeries(); + }, + + // Public: Event callback for the "Remove series" button. + // + // event - A jQuery Event object. + // + // Examples + // + // $('button').click(event.onRemove); + // + // Returns nothing. + // + onRemove: function (event) { + event.preventDefault(); + var element = $(event.target).parents('.dataexplorer-tableview-editor-series'); + this.removeSeries(element); + }, + + // Public: Event callback for the "Save" button. + // + // Triggers the "save" event passing a chart object to all registered + // callbacks. + // + // event - A jQuery Event object. + // + // Examples + // + // $('button.save').click(editor.onSave); + // + // Returns nothing. + // + onSave: function (event) { + event.preventDefault(); + this._triggerChartData('save'); + }, + + // Public: Event callback for the editor form. + // + // event - A jQuery Event object. + // + // Examples + // + // $('form.editor').submit(editor.onSubmit); + // + // Returns nothing. + // + onSubmit: function (event) { + event && event.preventDefault(); + this.submit(); + }, + + // Updates the text on the save button. + // + // If no text is provided reverts to the original button text. + // + // text - A text String to use in the button. + // + // Examples + // + // editor._updateSaveText('Now saving!'); + // + // Returns nothing. + // + _updateSaveText: function (text) { + var span = this.save.find('span'), + original = span.data('default'); + + if (!original) { + span.data('default', span.text()); + } + + span.text(text || original); + }, + + // Triggers an event on the editor and passes a chart object to callbacks. + // + // topic - Topic String for the event to fire. + // + // Examples + // + // editor.bind('save', function (chart) { + // // DO something with the chart. + // }); + // editor._triggerChartData('save'); + // + // Returns + // + _triggerChartData: function (topic) { + var series = this.series.map(function () { + return $(this).val(); + }); + + return this.trigger(topic, [{ + id: this.id.val(), + type: this.type.val(), + groups: this.groups.val(), + series: $.makeArray(series) + }]); + }, + + // Finds an option by "value" in a select element and makes it selected. + // + // select - A jQuery wrapped select Element. + // option - The String value of the options "value" attribute. + // + // Examples + // + // // For + // editor._selectOption(mySelect, 'bill'); + // + // Returns nothing. + // + _selectOption: function (select, option) { + select.find('[value="' + option + '"]').attr('selected', 'selected'); + }, + + // Creates a String of option elements. + // + // options - An object of value/text pairs. + // + // Examples + // + // var html = editor._createOptions({ + // value1: 'Value 1', + // value2: 'Value 2' + // }); + // + // Returns a String of HTML. + // + _createOptions: function (options) { + var html = []; + $.each(options, function (value, text) { + html.push(''); + }); + return html.join(''); + } + }); + + // Exports the UI and createTableView() methods onto the plugin object. + $.extend(true, this, {DATAEXPLORER: {TABLEVIEW: { + + UI: ui, + + // Public: Helper method for creating a new view. + // + // element - The main DOM Element used for the plugin. + // columns - The columns array for the data rows formatted for SlickGrid. + // data - A data object formatted for use in SlickGrid. + // chart - An optional chart object to load. + // + // Examples + // + // TABLEVIEW.createTableView($('my-view'), columns, data); + // + // Returns a new instance of MainView. + // + createTableView: function (element, columns, data, chart) { + return new ui.MainView(element, columns, data, chart); + } + }}}); + +})(jQuery); diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index bbb2902e275..e742b692554 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -15,7 +15,7 @@ - + @@ -111,14 +111,9 @@ - - - - + + + From b77a36af407345e178ca7045ccb28dc183800b49 Mon Sep 17 00:00:00 2001 From: rgrp Date: Fri, 25 Nov 2011 08:47:30 +0000 Subject: [PATCH 26/70] [package/view][s]: resource entries now link to resource page rather than resource url. * Introduce new helper function resource_link for use here and improve resource_display_name function to use description and then id if no name. --- ckan/lib/helpers.py | 23 ++++++++++++++++++++--- ckan/templates/package/read_core.html | 9 +-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 92e392a4faf..640598f7066 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -293,9 +293,6 @@ def button_attr(enable, type='primary'): return 'class="pretty-button %s"' % type return 'disabled class="pretty-button disabled"' -def resource_display_name(resource_dict): - return resource_dict['name'] or resource_dict['id'] - def dataset_display_name(package_or_package_dict): if isinstance(package_or_package_dict, dict): return package_or_package_dict.get('title', '') or package_or_package_dict.get('name', '') @@ -313,3 +310,23 @@ def dataset_link(package_or_package_dict): url_for(controller='package', action='read', id=name) ) +# TODO: (?) support resource objects as well +def resource_display_name(resource_dict): + name = resource_dict['name'] + description = resource_dict['description'] + if name: + return name + elif description: + description = description.split('.')[0][:60] + return description + else: + return '[no name] %s ' % resource_dict['id'] + +def resource_link(resource_dict, package_id): + text = resource_display_name(resource_dict) + url = url_for(controller='package', + action='resource_read', + id=package_id, + resource_id=resource_dict['id']) + return link_to(text, url) + diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 431b33e61c8..bdf17e79767 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -17,14 +17,7 @@

      Resources

      - - - ${res.get('name')} - - - (no name) - - + ${h.resource_link(res, c.pkg_dict['name'])}
      ${h.markdown_extract(res.get('description'))}
      @@ -139,28 +137,6 @@

      This dataset is Not Open

      - - - - - - -
      - - - - - - - - - - - diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index e742b692554..923bace040b 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -17,6 +17,9 @@ + @@ -79,15 +82,13 @@
      ${h.markdown_extract(c.package.get('notes'), 300)}
      - - - - - - - +
      +

      Preview

      +
      +
      +

      Additional Information

      diff --git a/ckan/templates/package/resources.html b/ckan/templates/package/resources.html index 8e0b6083f78..a121aee70e4 100644 --- a/ckan/templates/package/resources.html +++ b/ckan/templates/package/resources.html @@ -2,4 +2,4 @@ Someresources -
      \ No newline at end of file + From 14562ae3db32f77ffa730c741603d2dd928ddb38 Mon Sep 17 00:00:00 2001 From: rgrp Date: Fri, 25 Nov 2011 11:47:17 +0000 Subject: [PATCH 28/70] [js/dataexplorer][s]: tweaking and refactoring of dataexplorer to have it work in page rather than in dialog. * Now working pretty well though further minor improvements that could be made. --- ckan/public/scripts/application.js | 15 ++-- .../dataexplorer/table-view-template.js | 4 +- .../scripts/dataexplorer/table-view.css | 23 ++--- .../public/scripts/dataexplorer/table-view.js | 88 ++++++------------- 4 files changed, 47 insertions(+), 83 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 85bc0e386a4..8fc58e6858d 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -699,14 +699,15 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ (function ($) { var my = {}; my.jsonpdataproxyUrl = 'http://jsonpdataproxy.appspot.com/'; + my.dialogId = 'ckanext-datapreview'; + my.$dialog = $('#' + my.dialogId); // Initialize data explorer on Resource view page // // resourceData: resource as simple hash (suitable for initializing backbone model or result of backboneModel.toJSON()) my.setupDataPreview = function(resourceData) { - var dialogId = 'ckanext-datapreview'; // initialize the tableviewer system - DATAEXPLORER.TABLEVIEW.initialize(dialogId); + DATAEXPLORER.TABLEVIEW.initialize(my.dialogId); resourceData.formatNormalized = my.normalizeFormat(resourceData.format); // do not create previews for some items @@ -728,6 +729,8 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ || _tformat === 'other' ) { + var _msg = $('

      We are unable to preview this type of resource: ' + resourceData.format + '

      '); + my.$dialog.html(_msg); return; } my.loadPreviewDialog(resourceData); @@ -744,6 +747,7 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ // Returns nothing. my.loadPreviewDialog = function(resourceData) { resourceData.url = my.normalizeUrl(resourceData.url); + my.$dialog.html('

      Loading ...

      '); // 4 situations // a) have a webstore_url @@ -801,15 +805,12 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ else { // HACK: but should work // we displays a fullscreen dialog with the url in an iframe. - // HACK: we borrow dialog from DATAEXPLORER.TABLEVIEW - var $dialog = DATAEXPLORER.TABLEVIEW.$dialog; - $dialog.empty(); - $dialog.dialog('option', 'title', 'Preview: ' + resourceData.url); + my.$dialog.empty(); var el = $(''); el.attr('src', resourceData.url); el.attr('width', '100%'); el.attr('height', '100%'); - $dialog.append(el).dialog('open');; + my.$dialog.append(el); } }; diff --git a/ckan/public/scripts/dataexplorer/table-view-template.js b/ckan/public/scripts/dataexplorer/table-view-template.js index b7a4743a80c..91a5cce6b40 100644 --- a/ckan/public/scripts/dataexplorer/table-view-template.js +++ b/ckan/public/scripts/dataexplorer/table-view-template.js @@ -10,8 +10,6 @@ DATAEXPLORER.TABLEVIEW.template.html = ' \ \ \
      \ -
      \ -
      \
      \
      \

      Help

      \ @@ -45,5 +43,7 @@ DATAEXPLORER.TABLEVIEW.template.html = ' \
      \ \
      \ +
      \ +
      \
    \ '; diff --git a/ckan/public/scripts/dataexplorer/table-view.css b/ckan/public/scripts/dataexplorer/table-view.css index dbd0e1fe1c3..2f89967a076 100644 --- a/ckan/public/scripts/dataexplorer/table-view.css +++ b/ckan/public/scripts/dataexplorer/table-view.css @@ -22,7 +22,7 @@ list-style-type: none; margin: 0; padding: 0; - height: 22px; + height: 30px; } .dataexplorer-tableview-nav-toggle { @@ -38,18 +38,6 @@ label[for=dataexplorer-tableview-nav-editor] { float: right; } -/* Adds border to left of data table */ -.dataexplorer-tableview-grid, -.dataexplorer-tableview-graph, -.dataexplorer-tableview-editor { - position: absolute; - left: 0; - right: 220px; - top: 28px; - bottom: 0; - z-index: 0; -} - .dataexplorer-tableview-grid { border-left: 1px solid #ccc; } @@ -57,18 +45,18 @@ label[for=dataexplorer-tableview-nav-editor] { .dataexplorer-tableview-graph { z-index: 1; background-color: #fff; + margin-right: 220px; } .dataexplorer-tableview-editor { z-index: 1; background-color: #efefef; - right: 0; - left: auto; width: 198px; padding: 5px 10px; border: 1px solid #ccc; overflow: auto; overflow-x: hidden; + float: right; } .dataexplorer-tableview-editor ul { @@ -252,3 +240,8 @@ label[for=dataexplorer-tableview-nav-editor] { .slick-columnpicker input { cursor: pointer; } + +.dataexplorer-tableview-graph .legend table { + width: auto; +} + diff --git a/ckan/public/scripts/dataexplorer/table-view.js b/ckan/public/scripts/dataexplorer/table-view.js index 3d7aea0c861..dc70b0400fd 100644 --- a/ckan/public/scripts/dataexplorer/table-view.js +++ b/ckan/public/scripts/dataexplorer/table-view.js @@ -12,43 +12,6 @@ html: '' }; - // **Public: Loads the plugin UI into the dialog and sets up event listeners.** - // - // columns - Column Array formatted for use in SlickGrid. - // data - A data Array for use in SlickGrid. - // - // Returns nothing. - dp.loadTableView = function (columns, data) { - var dialog = dp.$dialog; - - // Need to create the grid once the dialog is open for cells to render - // correctly. - dialog.dialog(dp.dialogOptions).one("dialogopen", function () { - var element = $(dp.template.html).appendTo(dialog); - var viewer = new dp.createTableView(element, columns, data); - - // Load chart data from external source - // TODO: reinstate - // this used to load chart info from related CKAN dataset - viewer.editor.loading(); - viewer.editor.loading(false).disableSave(); - - // Save chart data to the client provided callback - // TODO: implement - viewer.editor.bind('save', function (chart) { - viewer.editor.saving(); - viewer.editor.saving(false); - }); - - dialog.bind("dialogresizestop.data-preview", viewer.redraw); - - // Remove bindings when dialog is closed. - dialog.bind("dialogbeforeclose", function () { - dialog.unbind(".data-preview"); - }); - }); - }; - // Public: Sets up the dialog for displaying a full screen of data. // // dialogTitle - title for dialog window. @@ -56,25 +19,8 @@ // Returns nothing. // dp.setupFullscreenDialog = function (dialogTitle) { - var dialog = dp.$dialog, $window = $(window), timer; - - dialog.empty().dialog('option', 'title', 'View: ' + dialogTitle); - - // Ensure the lightbox always fills the screen. - $window.bind('resize.data-preview', function () { - clearTimeout(timer); - timer = setTimeout(function () { - dialog.dialog('option', { - width: $window.width() - 20, - height: $window.height() - 20 - }); - dialog.trigger('dialogresizestop'); - }, 100); - }); - - dialog.bind("dialogbeforeclose", function () { - $window.unbind("resize.data-preview"); - }); + var dialog = dp.$dialog; + dialog.empty(); } // Public: Displays a smaller alert style dialog with an error message. @@ -84,8 +30,8 @@ // Returns nothing. // dp.showError = function (error) { - var _html = '' + $.trim(error.title) + '
    ' + $.trim(error.message); - dp.$dialog.html(_html).dialog(dp.errorDialogOptions); + var _html = '

    ' + $.trim(error.title) + '
    ' + $.trim(error.message) + '

    '; + dp.$dialog.html(_html); }; // Public: Displays the datapreview UI in a fullscreen dialog. @@ -105,7 +51,31 @@ } var tabular = dp.convertData(data); - dp.loadTableView(tabular.columns, tabular.data); + // dp.loadTableView(tabular.columns, tabular.data); + var columns = tabular.columns; + var data = tabular.data; + + var element = $(dp.template.html).appendTo(dp.$dialog); + // set plot height explicitly or flot is not happy + // also for grid + var height = $(window).height(); + // $('.dataexplorer-tableview-viewer').height(height); + $('.dataexplorer-tableview-grid').height(height); + $('.dataexplorer-tableview-graph').height(height); + var viewer = new dp.createTableView(element, columns, data); + + // Load chart data from external source + // TODO: reinstate + // this used to load chart info from related CKAN dataset + viewer.editor.loading(); + viewer.editor.loading(false).disableSave(); + + // Save chart data to the client provided callback + // TODO: implement + viewer.editor.bind('save', function (chart) { + viewer.editor.saving(); + viewer.editor.saving(false); + }); }; // **Public: parse data from webstore or other source into form for data From 229a1128956c6ea7471f6cc39444ab2cbe07b6ad Mon Sep 17 00:00:00 2001 From: kindly Date: Mon, 28 Nov 2011 10:11:13 +0000 Subject: [PATCH 29/70] add capacites, not tested! --- .../versions/046_rename_package_group_member.py | 12 ++++++++---- ckan/model/group.py | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ckan/migration/versions/046_rename_package_group_member.py b/ckan/migration/versions/046_rename_package_group_member.py index 442da705d0d..6944b8e36ff 100644 --- a/ckan/migration/versions/046_rename_package_group_member.py +++ b/ckan/migration/versions/046_rename_package_group_member.py @@ -41,9 +41,11 @@ def upgrade(migrate_engine): ALTER TABLE "member" ADD COLUMN table_name text; + ADD COLUMN capacity text; ALTER TABLE member_revision ADD COLUMN table_name text; + ADD COLUMN capacity text; ALTER TABLE "member" ADD CONSTRAINT member_pkey PRIMARY KEY (id); @@ -66,14 +68,16 @@ def upgrade(migrate_engine): ALTER TABLE member_revision ADD CONSTRAINT member_revision_revision_id_fkey FOREIGN KEY (revision_id) REFERENCES revision(id); -update member set table_name = 'package'; -update member_revision set table_name = 'package'; +update member set table_name = 'package', capacity = 'member'; +update member_revision set table_name = 'package', capacity = 'member'; ALTER TABLE "member" - ALTER COLUMN table_name set not null; + ALTER COLUMN table_name set not null + ALTER COLUMN capacity set not null; ALTER TABLE member_revision - ALTER COLUMN table_name set not null; + ALTER COLUMN table_name set not null + ALTER COLUMN capacity set not null; COMMIT; ''' ) diff --git a/ckan/model/group.py b/ckan/model/group.py index 0f4b44e515a..332fb143166 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -18,6 +18,7 @@ Column('id', UnicodeText, primary_key=True, default=make_uuid), Column('table_name', UnicodeText, nullable=False), Column('table_id', UnicodeText, nullable=False), + Column('capacity', UnicodeText, nullable=False), Column('group_id', UnicodeText, ForeignKey('group.id')), ) @@ -28,6 +29,7 @@ Column('id', UnicodeText, primary_key=True, default=make_uuid), Column('name', UnicodeText, nullable=False, unique=True), Column('title', UnicodeText), + Column('type', UnicodeText, nullable=False), Column('description', UnicodeText), Column('created', DateTime, default=datetime.datetime.now), ) From f5aef81bbcc9a80d8497974674cc7e22dcd42d89 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 28 Nov 2011 12:00:54 +0100 Subject: [PATCH 30/70] Ask users to add email address, #1413 If the user does not have an email address in their profile then flash a notice when they login (actually, whenever they visit their profile page) asking them to add their email address. --- ckan/controllers/user.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index 035d4b4aa6b..880c21056b7 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -99,6 +99,12 @@ def read(self, id=None): except NotFound: h.redirect_to(controller='user', action='login', id=None) + if c.userobj.email is None: + h.flash_notice('''Please
    update your +profile and add your email address. %s uses your email address to send you +notifications and updates, and to let you reset your password.''' \ + % (g.site_title), allow_html=True) + c.user_dict = user_dict c.is_myself = user_dict['name'] == c.user c.about_formatted = self._format_about(user_dict['about']) From adf581a9dbf2acefe19965e937d1022600eb41c8 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 28 Nov 2011 15:00:39 +0100 Subject: [PATCH 31/70] I18n of the 'please add your email' notice --- ckan/controllers/user.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index 880c21056b7..2e944728a66 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -100,10 +100,10 @@ def read(self, id=None): h.redirect_to(controller='user', action='login', id=None) if c.userobj.email is None: - h.flash_notice('''Please update your -profile and add your email address. %s uses your email address to send you -notifications and updates, and to let you reset your password.''' \ - % (g.site_title), allow_html=True) + msg = _('''Please update your profile and +add your email address. %s uses your email address to send you notifications +and updates, and to let you reset your password.''') % (g.site_title) + h.flash_notice(msg, allow_html=True) c.user_dict = user_dict c.is_myself = user_dict['name'] == c.user From b1c438d798a3160ad6a2d7fb9b08de162c191d73 Mon Sep 17 00:00:00 2001 From: rgrp Date: Mon, 28 Nov 2011 15:05:57 +0000 Subject: [PATCH 32/70] [resource/view][s]: re-introduce some minor things with title overwritten in merge and move up description. --- ckan/public/css/style.css | 6 +++++ ckan/templates/package/resource_read.html | 29 +++++++---------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index c159a188443..be2a6d132df 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1088,6 +1088,11 @@ body.package.resource_read #content { width: 950px; } +.resource_read .notes { + margin-bottom: 1em; + font-size: 110%; +} + .resource_read .quick-info dt { width: 115px; float: left; @@ -1097,6 +1102,7 @@ body.package.resource_read #content { .resource_read .resource-actions { float: right; + text-align: right; } body.package.resource_read #dataset-description { margin-bottom: 2em; } diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index b5969c9e510..c41159f76ed 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -23,28 +23,19 @@ - - ${c.package.get('title', c.package['name'])} > - ${c.resource.get('name')} - Datasets - - - ${c.package.get('title', c.package['name'])} > - ${c.resource.get('id')} - Datasets - + ${h.dataset_display_name(c.package)} / + ${h.resource_display_name(c.resource)} - Dataset - Resource - - - ${c.resource.get('name')} - - - ${c.resource.get('id')} - - + ${h.dataset_display_name(c.package)} / ${h.resource_display_name(c.resource)}
    +
    + ${c.resource.get('description') or '(No description)'} +
    + -
    - ${c.resource.get('description')} -
    -
    From ac130b0891343491be08b6e52cf977397272201e Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 28 Nov 2011 16:48:10 +0000 Subject: [PATCH 33/70] [icons][s]: Added two icons. --- ckan/public/images/icons/download-16.png | Bin 0 -> 6524 bytes ckan/public/images/icons/download-32.png | Bin 0 -> 6888 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 ckan/public/images/icons/download-16.png create mode 100644 ckan/public/images/icons/download-32.png diff --git a/ckan/public/images/icons/download-16.png b/ckan/public/images/icons/download-16.png new file mode 100644 index 0000000000000000000000000000000000000000..2b9732a9cc6b317ec8f2d9ab43d34666b554f66c GIT binary patch literal 6524 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
  • {+iEED(WlYR?`CF7nM{CRXG8!11YLkSX#nrX03I&^ z%DVw%Zvb$s0JxI?ybJ&-DFMVS5D;SP-}i?g=z%|MhbkC=DTD_xK*Esy$Qk4jvVe-C zHfTIrgkC|XZ~{0B935AVyMm>>Jtdaky{{k-SKwoHWjFT)VjWxa)aLcpmd6@sarI`K|b0 z3+xt@6}-D{vyimVBjFqoQ<3@gZK8={ieeMubrMlz74n>9?}h_Xq0-ti95NHK*W~Ku z4=Q9RMl1O%JF8fz8mMWjt81ufYG`R|o9ejh()70Lml$*zjv6B-N~Zp1x#m|a=B;e3 zi)<$C%e(bBuqwSYF zFnOruF#V`X;ncC(l1*hiagf&f2uCKmW3W);Zg~ z?}GfrhnI7E-TKK_R|nn?j@-O``|92Dp_pOQha!(wA5T6Vd;WCn*~{_qsWiO*Q<>rjaH^%pp*)u;H=Z^f0oY$Oxvv6cleX)PZYw5|d^K$=+{>t%{rB&b6 zwl(gx;I*2yNv0|@h1tzy0)PxSLlPW=i|`g8xMaqSz+b=^VgO zNKz%;U_MhVLDJoj{DBy5PdPexU=x0U`<_i|cQTR*7YZ`%4&+ zB_vrSe{6UwH6ndm=Bn%!xeM~03Z05QO1;XrRGz4QR9n&D*HqVX)n@3F>JI458b}-Z z8yz;jYf3V6GcUAwVWn!l$7a+{&%VfE*(uz4$kp8K{6-~@W-oQ`E?*nJk${*$ET}5P zD)eL6L8<_)BvLG@GKL&m9WO<%OH@i~OEyipn?~Kdy!A+i^0sR`f->iJ?cXDt)t4QZ zv$(G;&oY1NK;a?tg3m`v3+;-26rU({E?cOmtu#Dwr#h@=t>vT3b1Ob8wJTp&4Og>Pht@>aeAe>Tde`Qd zip&sZA@dfK2>?Q11q`TxM+gBiL=ur2WEe$JEi@7>M<3$Yan`s@+y&esrjKpGdayOT z6+Rz7N>C;2A&e0XiRCO9OESwOYb5J5+g5fG`&o_<5+CU{=Uy&HZf@>To@U-1d|v!2 z0&D`G1s|>J6FM*4BGRKfZM?`ezagzL8GEg85N)*6Ee#dOeI!{UUMi1jhsb#}!L>m4hdWn5a_4BQ7jf;<|{y3f8+Y5S1mQIF=sT- zuC(1~&+D}5TJ1S|k#d>yN@ritRk&7t-E#2F%{{ke?p(d+J@k4w`oX715hJ5d9G|v5 z7aH9(nh{fi`nI)a2^rfz)nPvIqkmX~` zLo37;vz6qP6DxOD7FT6gJy&nakBo=4uT9ZVaF|?$>_@0006vK>1AofWaFH z|Kt4szX0KKy#|+Lj)?#O03c&XQcVB=ZI}Q6bB+K2`vd?0gRB4mdXNAB>ZSkyG422W z6uivOMj`+J0c}Y{K~#9!q?0{M9bpiKpS`gNf}n-gse*E^C_xBX1OjPn3}UB+poN%5 zOsAkJB&0HspO8kY1hF$!NTr}nh*l{g60o!oqu!6l!r8FCejqq-Sa#-}*>}%5lc^+; z0k42z;7Q~TfOX&-P%R!VCC&Z77Vrh=1^x!gzzVPlytvY#<`_}`sbAGfA)lj5T~*K3 z(LA3Qd(<=aeW9U6T~R-)&4v7;T34Uv12Xlyx{-XbL#?UDYFkoUQ#aK|sL$f+eaWGA z^+-KbTa#KjdIn0sOW-%K12h9eK~JMVRXv`sfycmZV6X&KfIonNkHA~tIEFNU6JQ?r z5N81Pfu~K0l-2OTa@^(=I0qJhcfcM{OJ$r>f2yVUze%;>Wz+(Od}9S3w$-KENSyL& z)YS2tNW4qNH-!Nccgc90kx*yU=_Gf}NDs1dmTY4`@CoPyeg*5qdw2L{7Wnahb?>px iS!li%>s&M#7Xtt{UlWU=6Sy$|0000P)KLZ*U+ZLjgcJK0bj8Ae;{X@?rnx6Zs#WVSha-l*qsM z*De46z+xSpoES&h3jf~~AD8-Ph9CfNB&Jw;0DuqxA{3qC-~|Bu0El{2s=fDbY@HtE zMZUCe>InDDAZb*-^a{c2I zLveHm08Rj;i^2f-J?na6io4fu&*$_wSLfe&CW2w>@e88_TC$DjuMDVY3d76eS1+m> z5pC=6YZhvm80YT%TjRk<_3#D&X#kW;qkI49-AkodI{|O8GkU?0tFZl2{cgo%~%2j7(jzGpu_4P-M#6~ z>HpRf_P4_p-T?z5|6Hp#t52%0{85v+{^TOq5UdII1Y3f_-=vh^TQGnI$>0QWK!a3Z z01fDW-T!^7)W3@VxvK}zfDW+`2ho3%`oKG2K=fZPr@!jo3QQK$#B?xA%mg#V41kOY zVj`e|X=D1B1!jgBVLF(>zujl@e`HdD2%rE1(trlZ5DOoG0r3DSv@IzB09FZ!TN%-j zQ7L4L#Kbt7GTDJ1uBuGdRM*e|KmP#ouSa{<0}uc}1c&~`QNIId6#^h7|Kego0D49N zxO4x-$^8Ssa}c19Ej*Qx_KU*+0Pw&DT)+>)AP!O>4=SJux?lw6U;~ce243I~!4M8n z5DyI44BKEAWJ4YtfTN~nPbI0fgR6D~p@T!-6mA4Xsl#^F7Df*JS;%LsxH5fZ|O z2qO}R45EZ+BKn9aVuLs%9*92@ibNuGBo*0)>_PI7!$>Jog)|~(k#3|9xrq!T&yd&1 zB=Q|uLUAYu%8!bnvZxxWk6NHks23W9Mxse*I+}$ZK#S38v<2-z`_S9y2s(~_Lg#RR zW5)^NBylP@eVi4}4Htlmz%g(;aJjf5Ts5v0*Ml3xJ;aUUrg4iH0pr8SmV zIF^L%!1A$DtO4u5u3-+tRP0sIL5J^m+w zKoBIz5%dW51b;#dA%l=ls34ps^b&>%ZwYfmB2k#AL^L6~5h=tJVm7ga*h0KQd_a6p zTwq~mkzmnev1JKhp|k8}DPn12xywDH^ zHXb%PHWM~4wivcuY{hJ?Yy)g#Y_sg_>>Jn(*f+9Av+ratWNiRtYoP3iS&JNB8oL{(D zxn#I3xPrOTxr(^jxQ4i3cnD+L=;6fifk4+E;1l8y`F!)(faW9x$DoZe=drODvElDW{B2^ z-VvP>6Bn}+OB5>+yDByOqHyXyeGN5L2iThhTR)ZZ+IcaCZ#VGA$3%$Uus5LLfS=on{>1Ea~W0{eVHhk zV={v>^RjZXzOp&8ow6V0gybCL(&d`v#^g!zX7Wk$)$)%N2nzZNu?iIm_Z3k^9mOcc zGQ}Y!R7qDUTB$;5SQ)QupiEb;R(_(wp<(%QPQpk+f{JGPSz2zG*9I)3lFkKh@#WanZ@q z>DO7+)zf9@w(3smN$Z8`mFqpx=ht`F&)2_UfH$x-$TYZUuxO}jm|}R&@T-xEQLIsu z(MMxBW14ZT@mrG(CKQt@lW|kBX{hN5({VFNGm2TY*&A~ybE~!oh?0W6-_D=Q%_D>wd z9KszM9cCQ$9Jf1ObK-FFbSiavH_9A*=#D@^gd80Fgx&ZkX+D~pqs%W!EwQtLbyUGA#I`f zP~Xs|&=rasrJ6Dq<`7mMHWO|Yek}YG)tq{iI!QC39i~l0m_!_o_!wytc_eZ&$}FlV zYC75~x-|M*jD1XH%zUg{Y<(;<&NuFKJWG6Nd^eq!9!tNLAfB)#VK`AKF*|WQ$uOxf zX@=p%s7pqYgOa;a_)`*7?xf16{*(GT%`~knZDF&|=C&<7Tj*QvY*pO4Z|g+5ZTiU! zEF(Olf1BjCUE5x7x7c2_1KAO_qd!wBGb{7`PP?5AyV!Qc?z+2MefQzrKlb?U>B$ny z+L`tCAG?2=vpKUF*-vteaw_*?d!zT>+o!d!WZzmYHTPDYdR|f9N`83$t^FGNi}y1R zL>#zxQ1{^RLxe;0Lyrs03K|ac98N#{_K3@oj-%wGxkrB#h7{f`(k`kz#&#_A*m$vX zaaW0S$)S>!(x}prGRv~l<)Y>L%I7Pn6%US^A8)M`tIVrhIuU*1X_Z}7N40EqQ4OIc zt>#0mPwn7I{gaJ#B6WFntM&By@dl5EYmIu1jZLCW2bSz?S=`tWR~ds3IwdZyq9a}qQI-@$ryZpO`yB)jxdyIP8 zFQ{IqzbJXJ?2^ExgO}MZXI*AqNxw4Jo7g+u7uol=Kcs*3s^8T|*EU|eH{djIX7?@qTb;LcZ+G0$xzm1E=WhEw-FqEFdP80J4es|08xLQ8VD_Nz zq4mS-j~pJ|9&sHRe(e4D>65@G<4>tiKR%0l_U(Dv^X1Wm0 z^tkT$rPtQ4@4WGRGx|3C?WcFi?^fPteIR`(nvj@i{HXcy;-u~5{i#h;?>{AeTAt4S z%=5YYi~N`KU(LVXnem%>|BdmD`8|JDc((qB_K*HK_qmrp<9{yC=Pn2@G%V^b4lemF zO)PI-A+8j!Dy;UbIjxN`6%uyOwSHuzmQ1ONp9 z40u5qJRloxAUw!MqzsusUC|DlEUp3*$C~gK_;-Z;L{s7{ODk&k2o zeD9ZimvZ;#1?DT~FYkYPVEhoVK;v-0k*uT5h4+f4jxkF}rMzW4*reaEu!WPC3ninxwAz>?0Y&H9DyA^QMFC+QStJy#uf z9ZxfF8{b9#n*uKdXNA~=rA5rwhl=hLJ0X5v;u86qNrEW`Km$@L@BzHtURl!qH zNpV%_wsMh5i0TH_QME#KHw{*e0nMFS###&7?K%uyHQi~wM*Ro_8G|>5l|})^g2oR` z3QRYe@tTd87h42d%36N0>axzVakmwBx^Q=@aLi@7U@>r=N{_oR&` z9vmJoJWqS=^7izR@tOC%=~w0-7ht-Hd(-Q{b3s|b-XSs}bD;y2qOhoNeJTs}8Lc@Y zBhn>GJnCz7Z_L5i;5g;D<@j6ll7z@aog`e+ea7+RxD zv1yykwy)c}ckIb@-6_9|W!Kd1dwbfmj{mbaJ0&M{uj@XeT=_hKe8`{L|M)=v!83LLsdv@R)b8bBSfCewkXiY=y*ep-R3JTveoMQVnM<&q>ib&3cE1;6_GM zR&!xX?WuFES5DtK^YrXQ8+Km5J+7m&bGRGVqjiCLvFOs^<;7m*K3ae8HHCpg*XM5d z-R!x&{!Yf-(IKPz6~oMjher~hI6PAr-SEQxRqGqZ`%9BUUsmTPm`niR0ZGsVC&+*< z1VJ>BIOG)a6SYJO(HWdOt`n2RD)6HCDuOcMB5@<}6U$LnH#Tv$Pwagh$4H5sE?i37 zoZLToUh>}L>*TK&C=)DLmo2nII8}tY-ceLpj9cuh_$`TQa+;*g2F?w`QU|3SWe}M* zS*jebT)VuF{11f!MK#4cO0mkU$_*-ZDif+XYVvA>>d_kP8m*e1nhRRTwQaPgbPnn2 z>Auk0tuL>C*MMQT&hVm9m@&b)(Ztzg#`Kt(nc1Xyp@o&jcgt!kFKeRp1)D@$Y1
  • {+iEED(WlYR?`CF7nM{CRXG8!11YLkSX#nrX03I&^ z%DVw%Zvb$s0JxI?ybJ&-DFMVS5D;SP-}i?g=z%|MhbkC=DTD_xK*Esy$Qk4jvVe-C zHfTIrgkC|XZ~{0B935AVyMm>>Jtdaky{{k-SKwoHWjFT)VjWxa)aLcpmd6@sarI`K|b0 z3+xt@6}-D{vyimVBjFqoQ<3@gZK8={ieeMubrMlz74n>9?}h_Xq0-ti95NHK*W~Ku z4=Q9RMl1O%JF8fz8mMWjt81ufYG`R|o9ejh()70Lml$*zjv6B-N~Zp1x#m|a=B;e3 zi)<$C%e(bBuqwSYF zFnOruF#V`X;ncC(l1*hiagf&f2uCKmW3W);Zg~ z?}GfrhnI7E-TKK_R|nn?j@-O``|92Dp_pOQha!(wA5T6Vd;WCn*~{_qsWiO*Q<>rjaH^%pp*)u;H=Z^f0oY$Oxvv6cleX)PZYw5|d^K$=+{>t%{rB&b6 zwl(gx;I*2yNv0|@h1tzy0)PxSLlPW=i|`g8xMaqSz+b=^VgO zNKz%;U_MhVLDJoj{DBy5PdPexU=x0U`<_i|cQTR*7YZ`%4&+ zB_vrSe{6UwH6ndm=Bn%!xeM~03Z05QO1;XrRGz4QR9n&D*HqVX)n@3F>JI458b}-Z z8yz;jYf3V6GcUAwVWn!l$7a+{&%VfE*(uz4$kp8K{6-~@W-oQ`E?*nJk${*$ET}5P zD)eL6L8<_)BvLG@GKL&m9WO<%OH@i~OEyipn?~Kdy!A+i^0sR`f->iJ?cXDt)t4QZ zv$(G;&oY1NK;a?tg3m`v3+;-26rU({E?cOmtu#Dwr#h@=t>vT3b1Ob8wJTp&4Og>Pht@>aeAe>Tde`Qd zip&sZA@dfK2>?Q11q`TxM+gBiL=ur2WEe$JEi@7>M<3$Yan`s@+y&esrjKpGdayOT z6+Rz7N>C;2A&e0XiRCO9OESwOYb5J5+g5fG`&o_<5+CU{=Uy&HZf@>To@U-1d|v!2 z0&D`G1s|>J6FM*4BGRKfZM?`ezagzL8GEg85N)*6Ee#dOeI!{UUMi1jhsb#}!L>m4hdWn5a_4BQ7jf;<|{y3f8+Y5S1mQIF=sT- zuC(1~&+D}5TJ1S|k#d>yN@ritRk&7t-E#2F%{{ke?p(d+J@k4w`oX715hJ5d9G|v5 z7aH9(nh{fi`nI)a2^rfz)nPvIqkmX~` zLo37;vz6qP6DxOD7FT6gJy&nakBo=4uT9ZVaF|?$>_@0006vK>1AofWaFH z|Kt4szX0KKy#|+Lj)?#O03c&XQcVB=ZI}Q6bB+K2`vd?0gRB4mdXNAB>ZSkyG422W z6uivOMj`+J0@+DKK~#9!%$Hqgj$s(bfA4E!n_+D}QYgx-umgn@YsT`Cj}nOkJ6T$+ zBnJ+5h~x-ONfd=Bijs09EKVG7WG8GyYiw=rtoH8H-^umV-P8TP&-?6yU3GWgUH5hU zuj{#==lNgPosdLKSq3Zx<^v@_0!RT}K!*(hvw;36u)G|DCV^Vu2v7wq1`0wp!$2qS z7I+A}$O&0e)Ho#Rqx>=?X)wcHtE6L5_=zm9U=eT?IPB=Y1FwLWKr7H|$6W%f0;+); zV7cS-95@Mli*o`Ml0G?4(`v6u*YImG(Nh-@BaCpp- zblJh_lvEj&K(V9`j?>u;;02QEB{kZpla!aiwn5UU;eD30AX@^>PVlBIPgiCiU?5$R zW@oWK;n?2Kl)y$O(?=12}vaF zXQ%-7*t!|`!Q@L1aLLwH!1{pU0CpKG4gt5BI(Y!}*?O;=K#k4sfwm9@ubx1_wN@(v z`DH`~u+2?ijm>XD0DpjM#$<7&3AkuX=}L>uD}nMPPzjWp@{0#(h9z+{M(_sUJTT_j z3n;l5C`$rGK)xw|dmM&szT?ydUjYep$ARm%9`)FzY@P`e=2DJ>cbE^{v!{CO37!V( zfg2v*to^_MFai_;b2GdG*Z!_mzqF$t1R8-Wo<1ThCCT+CFz`-K!v0ccVnDM^h(;2#s8FJyJ5V=XrH9CCwQNv>bS*dyyf^n=p<71 zolK}4$7x8AwBP=Q0ZCgUY;$Tz; Date: Mon, 28 Nov 2011 16:56:24 +0000 Subject: [PATCH 34/70] [ux][s]: Added a download icon to the download button. Needed inline style to make it look good though. --- ckan/templates/package/resource_read.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index c41159f76ed..de120c1b9f7 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -40,7 +40,7 @@ View View API Endpoint - Download + Download
  • From 29a86cd31852df16e95593a5060d194f7022fac4 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 28 Nov 2011 17:33:37 +0000 Subject: [PATCH 35/70] [ux][m]: Pair coding some aesthetic tweaks with Rufus. --- ckan/public/css/style.css | 36 ++++++++------------------- ckan/templates/package/read_core.html | 8 +++++- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index be2a6d132df..63c598e2309 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -986,18 +986,19 @@ body.package.read .tags li, body.package.read .groups li { list-style-type: none; display: inline-block; padding: 3px 3px 3px 0px; + margin-right: 6px; + padding:0 5px 2px 5px; + border:none; + font-weight:normal; + font-size:0.8em; + background:#afc6e9; } body.package.read .tags a, body.package.read .groups a { - display: inline-block; - color: #222; - padding: 5px; - border: 1px solid #e0e0e0; - -webkit-border-radius: 4px !important; - border-radius: 4px !important; + color: black; } -body.package.read .tags a:hover, -body.package.read .groups a:hover { - border: 1px solid #aaa; +body.package.read .tags li:hover, +body.package.read .groups li:hover { + background: #bdf; } body.package.read .related-datasets { padding: 0; @@ -1055,23 +1056,6 @@ body.package.read #notes-extract p { body.package.read #notes-remainder { margin-top: 1em; } -body.package.read table { - margin: 0; -} -body.package.read tbody tr:nth-child(odd) td, tbody tr.odd td { - background-color: #ffffff; -} -body.package.read tbody tr:nth-child(even) td, tbody tr.even td { - /* background-color: #f8f8f8; */ - background-color: #ffffff; -} -body.package.read #dataset-information { - /* border: 1px solid #e0e0e0; */ - /* padding: 10px; */ -} -body.package.read #dataset-information td { - padding-left: 0; -} body.package.read #dataset-information .dataset-label { font-weight: bold; min-width: 10em; diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index f6572cbff7c..e37a03d0152 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -44,9 +44,15 @@

    Resources

    -

    Information

    +

    Additional Information

    + + + + + + From af5323393a59f6063799ea5e932ee23ff92460db Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 28 Nov 2011 17:46:45 +0000 Subject: [PATCH 36/70] [ux][m]: More aesthetic (experimental) tweaks to the dataset view page. Note that download button isn't hooked up. --- ckan/public/css/style.css | 17 +++++++++++++++- ckan/templates/package/read_core.html | 29 ++++++++++++++------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 63c598e2309..87baee37c41 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1012,7 +1012,22 @@ body.package.read #dataset-resources { margin-bottom: 2em; } body.package.read .dataset-resource { - margin-bottom: 1em; + padding: 0.5em 0; + margin: 0; + border-bottom: 1px solid #eee; +} +body.package.read .dataset-resource .inner { + border-left: 2px solid #eee; + padding-left: 8px; +} +body.package.read .dataset-resource:hover .inner { + border-left: 2px solid #aaa; +} +body.package.read .dataset-resource button { + float: right; +} +body.package.read #dataset-resources h3 { + margin-bottom: 4px; } body.package.read .resource-view-url { font-size: 1.3em; diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index e37a03d0152..71e559c25c6 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -19,21 +19,22 @@

    Resources

    - ${h.resource_link(res, c.pkg_dict['name'])} - ${res.get('format')} - (unknown format) -
    ${h.markdown_extract(res.get('description'))}
    -
    - - ${res.get('url', '')} - +
    + + ${h.resource_link(res, c.pkg_dict['name'])} + ${res.get('format')} + (unknown format) +  —  ${h.markdown_extract(res.get('description'))} + +
      +
    • Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago
    • +
    -
      -
    • - Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago -
    • -
    From 31c95b6fb8578f597c8636c997450b3bb9aa791d Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Nov 2011 14:51:08 +0100 Subject: [PATCH 37/70] Move 'please add your email' notice to front page --- ckan/controllers/home.py | 8 ++++++++ ckan/controllers/user.py | 6 ------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index 6cf76961cc7..a794f93f927 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -60,6 +60,14 @@ def index(self): c.package_count = 0 c.groups = [] + if c.userobj is not None: + if c.userobj.email is None: + msg = _('''Please update your profile +and add your email address. %s uses your email address to send you +notifications and updates, and to let you reset your password.''') % \ + (g.site_title) + h.flash_notice(msg, allow_html=True) + return render('home/index.html') def license(self): diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index 2e944728a66..035d4b4aa6b 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -99,12 +99,6 @@ def read(self, id=None): except NotFound: h.redirect_to(controller='user', action='login', id=None) - if c.userobj.email is None: - msg = _('''Please update your profile and -add your email address. %s uses your email address to send you notifications -and updates, and to let you reset your password.''') % (g.site_title) - h.flash_notice(msg, allow_html=True) - c.user_dict = user_dict c.is_myself = user_dict['name'] == c.user c.about_formatted = self._format_about(user_dict['about']) From 709dc204e97a3135b0eeeb604be1f503df3c5b0d Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Nov 2011 17:10:48 +0100 Subject: [PATCH 38/70] Add unit test for 'please add your email' notices The test currently fails due to bad string wrapping in ckan/controllers/home.py --- ckan/tests/functional/test_home.py | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/ckan/tests/functional/test_home.py b/ckan/tests/functional/test_home.py index 1635c158642..3eef0154069 100644 --- a/ckan/tests/functional/test_home.py +++ b/ckan/tests/functional/test_home.py @@ -138,6 +138,47 @@ def test_locale_change_with_false_hash(self): finally: self.clear_language_setting() + def test_add_email_notice(self): + msg = 'Please update your profile and add ' \ + 'your email address' + url = url_for('home') + + # msg should not be shown if not logged in. + response = self.app.get(url) + assert msg not in response + + users = model.Session.query(model.user.User).all() + + # Make sure that the test users start out with no email. + for user in users: + model.repo.new_revision() + model.Session.add(user) + user.email = None + model.Session.commit() + model.repo.commit_and_remove() + + # When user is logged in and has no email, msg should be shown. + for user in users: + if user.email is None: + response = self.app.get(url, + extra_environ={'REMOTE_USER': user.name.encode('utf-8')}) + assert msg in response + + # Now add emails to the users. + for user in users: + model.repo.new_revision() + model.Session.add(user) + user.email = 'testing@testemail.com' + model.Session.commit() + model.repo.commit_and_remove() + + # When user is logged in and does have an email, msg should not be + # shown. + for user in users: + response = self.app.get(url, + extra_environ={'REMOTE_USER': user.name.encode('utf-8')}) + assert msg not in response + class TestDatabaseNotInitialised(TestController): @classmethod def setup_class(cls): From f1ddc0fd259bb4f15724762c87509e7c8e5a953e Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Nov 2011 17:13:31 +0100 Subject: [PATCH 39/70] Fix wrapping of 'Please add your email' string --- ckan/controllers/home.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index a794f93f927..cd4cd26eda4 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -62,10 +62,10 @@ def index(self): if c.userobj is not None: if c.userobj.email is None: - msg = _('''Please update your profile -and add your email address. %s uses your email address to send you -notifications and updates, and to let you reset your password.''') % \ - (g.site_title) + msg = _('Please update your profile ' + 'and add your email address. %s uses your email address ' + 'to send you notifications and updates, and to let you ' + ' reset your password.''') % (g.site_title) h.flash_notice(msg, allow_html=True) return render('home/index.html') From b35deaa3db5cdf20026e273c8a01a48edcec785f Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Nov 2011 17:17:27 +0100 Subject: [PATCH 40/70] Remove an unnecessary space from a string --- ckan/controllers/home.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index cd4cd26eda4..6bfc663d62a 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -65,7 +65,7 @@ def index(self): msg = _('Please update your profile ' 'and add your email address. %s uses your email address ' 'to send you notifications and updates, and to let you ' - ' reset your password.''') % (g.site_title) + 'reset your password.''') % (g.site_title) h.flash_notice(msg, allow_html=True) return render('home/index.html') From 43e434f04d0e28b263dfd3a193d95546c01405d0 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Nov 2011 18:21:28 +0100 Subject: [PATCH 41/70] Test for both email and fullname notices. Update test_home.py to test for both the 'add your email' and the 'add you fullname' notices. --- ckan/tests/functional/test_home.py | 77 ++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/ckan/tests/functional/test_home.py b/ckan/tests/functional/test_home.py index 3eef0154069..629311e363c 100644 --- a/ckan/tests/functional/test_home.py +++ b/ckan/tests/functional/test_home.py @@ -138,46 +138,71 @@ def test_locale_change_with_false_hash(self): finally: self.clear_language_setting() - def test_add_email_notice(self): - msg = 'Please update your profile and add ' \ - 'your email address' + def test_update_profile_notice(self): + email_notice = 'Please update your profile' \ + ' and add your email address.' + fullname_notice = 'Please update your profile' \ + ' and add your full name' + email_and_fullname_notice ='Please update your' \ + ' profile and add your email address and your full name.' url = url_for('home') - # msg should not be shown if not logged in. + # No update profile notices should be flashed if no one is logged in. response = self.app.get(url) - assert msg not in response + assert email_notice not in response + assert fullname_notice not in response + assert email_and_fullname_notice not in response + + # Make some test users. + user1 = model.user.User(name='user1', fullname="user 1's full name", + email='user1@testusers.org') + user2 = model.user.User(name='user2', fullname="user 2's full name") + user3 = model.user.User(name='user3', email='user3@testusers.org') + user4 = model.user.User(name='user4') + users = (user1, user2, user3, user4) - users = model.Session.query(model.user.User).all() - - # Make sure that the test users start out with no email. for user in users: model.repo.new_revision() model.Session.add(user) - user.email = None model.Session.commit() - model.repo.commit_and_remove() - # When user is logged in and has no email, msg should be shown. - for user in users: - if user.email is None: - response = self.app.get(url, - extra_environ={'REMOTE_USER': user.name.encode('utf-8')}) - assert msg in response + response = self.app.get(url, extra_environ={'REMOTE_USER': + user.name.encode('utf-8')}) - # Now add emails to the users. - for user in users: model.repo.new_revision() model.Session.add(user) - user.email = 'testing@testemail.com' + + if not user.email and not user.fullname: + assert email_and_fullname_notice in response + assert email_notice not in response + assert fullname_notice not in response + + elif user.email and not user.fullname: + assert email_notice not in response + assert fullname_notice in response + assert email_and_fullname_notice not in response + + elif not user.email and user.fullname: + assert email_notice in response + assert fullname_notice not in response + assert email_and_fullname_notice not in response + + elif user.email and user.fullname: + assert email_notice not in response + assert fullname_notice not in response + assert email_and_fullname_notice not in response + + if not user.email: + user.email = "mr_tusks@tusk_family.org" + if not user.fullname: + user.fullname = "Mr. Tusks" model.Session.commit() - model.repo.commit_and_remove() - # When user is logged in and does have an email, msg should not be - # shown. - for user in users: - response = self.app.get(url, - extra_environ={'REMOTE_USER': user.name.encode('utf-8')}) - assert msg not in response + response = self.app.get(url, extra_environ={'REMOTE_USER': + user.name.encode('utf-8')}) + assert email_notice not in response + assert fullname_notice not in response + assert email_and_fullname_notice not in response class TestDatabaseNotInitialised(TestController): @classmethod From c7dd040065908e66e76f8bd5ceed8d1ab9bf44fd Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Nov 2011 18:22:34 +0100 Subject: [PATCH 42/70] Implement 'add your full name' notice. Add a flash notice to the front page asking users to edit their profile and add their full name, if they don't have one. --- ckan/controllers/home.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index 6bfc663d62a..f0943446b95 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -61,11 +61,22 @@ def index(self): c.groups = [] if c.userobj is not None: - if c.userobj.email is None: - msg = _('Please update your profile ' - 'and add your email address. %s uses your email address ' - 'to send you notifications and updates, and to let you ' - 'reset your password.''') % (g.site_title) + msg = None + if not c.userobj.email and not c.userobj.fullname: + msg = _('Please update your profile' + ' and add your email address and your full name. %s uses' + ' your email address to send you notifications and' + ' updates, and to let you reset your password.''') \ + % (g.site_title) + elif not c.userobj.email: + msg = _('Please update your profile' + ' and add your email address. %s uses your email address' + ' to send you notifications and updates, and to let you' + ' reset your password.''') % (g.site_title) + elif not c.userobj.fullname: + msg = _('Please update your profile' + ' and add your full name.') + if msg: h.flash_notice(msg, allow_html=True) return render('home/index.html') From fc9cef8ff3631c293859e70f3f90ab4f1545d00b Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Nov 2011 18:52:09 +0100 Subject: [PATCH 43/70] Replace some hardcoded URLs with url_for() --- ckan/controllers/home.py | 14 ++++++++------ ckan/tests/functional/test_home.py | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index f0943446b95..4d900f3f2ab 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -14,6 +14,7 @@ from ckan.lib.base import * import ckan.lib.stats from ckan.lib.hash import get_redirect +from ckan.lib.helpers import url_for class HomeController(BaseController): repo = model.repo @@ -62,20 +63,21 @@ def index(self): if c.userobj is not None: msg = None + url = url_for(controller='user', action='edit') if not c.userobj.email and not c.userobj.fullname: - msg = _('Please update your profile' + msg = _('Please update your profile' ' and add your email address and your full name. %s uses' ' your email address to send you notifications and' ' updates, and to let you reset your password.''') \ - % (g.site_title) + % (url, g.site_title) elif not c.userobj.email: - msg = _('Please update your profile' + msg = _('Please update your profile' ' and add your email address. %s uses your email address' ' to send you notifications and updates, and to let you' - ' reset your password.''') % (g.site_title) + ' reset your password.''') % (url, g.site_title) elif not c.userobj.fullname: - msg = _('Please update your profile' - ' and add your full name.') + msg = _('Please update your profile' + ' and add your full name.') % (url) if msg: h.flash_notice(msg, allow_html=True) diff --git a/ckan/tests/functional/test_home.py b/ckan/tests/functional/test_home.py index 629311e363c..e475a1a3149 100644 --- a/ckan/tests/functional/test_home.py +++ b/ckan/tests/functional/test_home.py @@ -139,12 +139,14 @@ def test_locale_change_with_false_hash(self): self.clear_language_setting() def test_update_profile_notice(self): - email_notice = 'Please update your profile' \ - ' and add your email address.' - fullname_notice = 'Please update your profile' \ - ' and add your full name' - email_and_fullname_notice ='Please update your' \ - ' profile and add your email address and your full name.' + edit_url = url_for(controller='user', action='edit') + email_notice = 'Please update your profile' \ + ' and add your email address.' % (edit_url) + fullname_notice = 'Please update your profile' \ + ' and add your full name' % (edit_url) + email_and_fullname_notice ='Please update your' \ + ' profile and add your email address and your full name.' \ + % (edit_url) url = url_for('home') # No update profile notices should be flashed if no one is logged in. From 2159d3b501032cf800802fee9ea984613842bc79 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Wed, 30 Nov 2011 16:05:00 +0000 Subject: [PATCH 44/70] [ux,dataset-read][m]: Refactored the resource listing to use a large HTML anchor, so it's all clickable. --- ckan/public/css/style.css | 62 +++++++++++++----- .../{download-16.png => arrow-down-16.png} | Bin .../{download-32.png => arrow-down-32.png} | Bin .../images/icons/arrow-right-16-black.png | Bin 0 -> 6518 bytes ckan/public/images/icons/arrow-right-16.png | Bin 0 -> 6550 bytes ckan/public/images/icons/arrow-right-32.png | Bin 0 -> 7027 bytes ckan/templates/package/read_core.html | 34 +++++----- ckan/templates/package/resource_read.html | 2 +- 8 files changed, 64 insertions(+), 34 deletions(-) rename ckan/public/images/icons/{download-16.png => arrow-down-16.png} (100%) rename ckan/public/images/icons/{download-32.png => arrow-down-32.png} (100%) create mode 100644 ckan/public/images/icons/arrow-right-16-black.png create mode 100644 ckan/public/images/icons/arrow-right-16.png create mode 100644 ckan/public/images/icons/arrow-right-32.png diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 87baee37c41..6667b8d0f42 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1006,37 +1006,65 @@ body.package.read .related-datasets { body.package.read .related-datasets li { list-style-type: none; } -body.package.read #dataset-license img.open-data { vertical-align: top; } -body.package.read #dataset-resources { + +#dataset-license img.open-data { vertical-align: top; } +#dataset-resources { margin-top: 2em; margin-bottom: 2em; } -body.package.read .dataset-resource { - padding: 0.5em 0; - margin: 0; - border-bottom: 1px solid #eee; +#dataset-resources h3 { + margin-bottom: 8px; } -body.package.read .dataset-resource .inner { +.dataset-resource { border-left: 2px solid #eee; - padding-left: 8px; + margin: 0; + position: relative; +} +.dataset-resource .resource-view-link { + display: block; + padding: 10px 8px; + padding-bottom: 25px; +} +.dataset-resource .resource-view-link .plain-text { + color: #000; } -body.package.read .dataset-resource:hover .inner { + +.dataset-resource:hover { border-left: 2px solid #aaa; + background: #f7f7f7; } -body.package.read .dataset-resource button { - float: right; +.dataset-resource-divider { + border-bottom: 1px solid #eee; + margin: 8px 0; } -body.package.read #dataset-resources h3 { - margin-bottom: 4px; + + +body.package.read .dataset-resource .view-more-link { + float: right; + text-align: middle; + margin-top: 4px; + padding: 3px 22px 3px 10px; + background: url('/images/icons/arrow-right-16-black.png') no-repeat right; + opacity:0.4; + filter:alpha(opacity=40); /* For IE8 and earlier */ } -body.package.read .resource-view-url { - font-size: 1.3em; +body.package.read .dataset-resource:hover .view-more-link { + opacity:1.0; + filter:alpha(opacity=100); /* For IE8 and earlier */ } + body.package.read .resource-url { - color: #808080; + display: block; + position: absolute; + left: 10px; + bottom: 5px; } body.package.read .resource-url a { - color: #808080; + color: #888; +} +body.package.read .resource-url a:hover { + color: #333; + text-decoration: underline; } body.package.read .resource-information { padding-left: 0; diff --git a/ckan/public/images/icons/download-16.png b/ckan/public/images/icons/arrow-down-16.png similarity index 100% rename from ckan/public/images/icons/download-16.png rename to ckan/public/images/icons/arrow-down-16.png diff --git a/ckan/public/images/icons/download-32.png b/ckan/public/images/icons/arrow-down-32.png similarity index 100% rename from ckan/public/images/icons/download-32.png rename to ckan/public/images/icons/arrow-down-32.png diff --git a/ckan/public/images/icons/arrow-right-16-black.png b/ckan/public/images/icons/arrow-right-16-black.png new file mode 100644 index 0000000000000000000000000000000000000000..5096d9e2b29edf123674e263b3df49688f9a4acb GIT binary patch literal 6518 zcmV-+8HwhJP)KLZ*U+ZLjgcJK0bj8Ae;{X@?rnx6Zs#WVSha-l*qsM z*De46z+xSpoES&h3jf~~AD8-Ph9CfNB&Jw;0DuqxA{3qC-~|Bu0El{2s=fDbY@HtE zMZUCe>InDDAZb*-^a{c2I zLveHm08Rj;i^2f-J?na6io4fu&*$_wSLfe&CW2w>@e88_TC$DjuMDVY3d76eS1+m> z5pC=6YZhvm80YT%TjRk<_3#D&X#kW;qkI49-AkodI{|O8GkU?0tFZl2{cgo%~%2j7(jzGpu_4P-M#6~ z>HpRf_P4_p-T?z5|6Hp#t52%0{85v+{^TOq5UdII1Y3f_-=vh^TQGnI$>0QWK!a3Z z01fDW-T!^7)W3@VxvK}zfDW+`2ho3%`oKG2K=fZPr@!jo3QQK$#B?xA%mg#V41kOY zVj`e|X=D1B1!jgBVLF(>zujl@e`HdD2%rE1(trlZ5DOoG0r3DSv@IzB09FZ!TN%-j zQ7L4L#Kbt7GTDJ1uBuGdRM*e|KmP#ouSa{<0}uc}1c&~`QNIId6#^h7|Kego0D49N zxO4x-$^8Ssa}c19Ej*Qx_KU*+0Pw&DT)+>)AP!O>4=SJux?lw6U;~ce243I~!4M8n z5DyI44BKEAWJ4YtfTN~nPbI0fgR6D~p@T!-6mA4Xsl#^F7Df*JS;%LsxH5fZ|O z2qO}R45EZ+BKn9aVuLs%9*92@ibNuGBo*0)>_PI7!$>Jog)|~(k#3|9xrq!T&yd&1 zB=Q|uLUAYu%8!bnvZxxWk6NHks23W9Mxse*I+}$ZK#S38v<2-z`_S9y2s(~_Lg#RR zW5)^NBylP@eVi4}4Htlmz%g(;aJjf5Ts5v0*Ml3xJ;aUUrg4iH0pr8SmV zIF^L%!1A$DtO4u5u3-+tRP0sIL5J^m+w zKoBIz5%dW51b;#dA%l=ls34ps^b&>%ZwYfmB2k#AL^L6~5h=tJVm7ga*h0KQd_a6p zTwq~mkzmnev1JKhp|k8}DPn12xywDH^ zHXb%PHWM~4wivcuY{hJ?Yy)g#Y_sg_>>Jn(*f+9Av+ratWNiRtYoP3iS&JNB8oL{(D zxn#I3xPrOTxr(^jxQ4i3cnD+L=;6fifk4+E;1l8y`F!)(faW9x$DoZe=drODvElDW{B2^ z-VvP>6Bn}+OB5>+yDByOqHyXyeGN5L2iThhTR)ZZ+IcaCZ#VGA$3%$Uus5LLfS=on{>1Ea~W0{eVHhk zV={v>^RjZXzOp&8ow6V0gybCL(&d`v#^g!zX7Wk$)$)%N2nzZNu?iIm_Z3k^9mOcc zGQ}Y!R7qDUTB$;5SQ)QupiEb;R(_(wp<(%QPQpk+f{JGPSz2zG*9I)3lFkKh@#WanZ@q z>DO7+)zf9@w(3smN$Z8`mFqpx=ht`F&)2_UfH$x-$TYZUuxO}jm|}R&@T-xEQLIsu z(MMxBW14ZT@mrG(CKQt@lW|kBX{hN5({VFNGm2TY*&A~ybE~!oh?0W6-_D=Q%_D>wd z9KszM9cCQ$9Jf1ObK-FFbSiavH_9A*=#D@^gd80Fgx&ZkX+D~pqs%W!EwQtLbyUGA#I`f zP~Xs|&=rasrJ6Dq<`7mMHWO|Yek}YG)tq{iI!QC39i~l0m_!_o_!wytc_eZ&$}FlV zYC75~x-|M*jD1XH%zUg{Y<(;<&NuFKJWG6Nd^eq!9!tNLAfB)#VK`AKF*|WQ$uOxf zX@=p%s7pqYgOa;a_)`*7?xf16{*(GT%`~knZDF&|=C&<7Tj*QvY*pO4Z|g+5ZTiU! zEF(Olf1BjCUE5x7x7c2_1KAO_qd!wBGb{7`PP?5AyV!Qc?z+2MefQzrKlb?U>B$ny z+L`tCAG?2=vpKUF*-vteaw_*?d!zT>+o!d!WZzmYHTPDYdR|f9N`83$t^FGNi}y1R zL>#zxQ1{^RLxe;0Lyrs03K|ac98N#{_K3@oj-%wGxkrB#h7{f`(k`kz#&#_A*m$vX zaaW0S$)S>!(x}prGRv~l<)Y>L%I7Pn6%US^A8)M`tIVrhIuU*1X_Z}7N40EqQ4OIc zt>#0mPwn7I{gaJ#B6WFntM&By@dl5EYmIu1jZLCW2bSz?S=`tWR~ds3IwdZyq9a}qQI-@$ryZpO`yB)jxdyIP8 zFQ{IqzbJXJ?2^ExgO}MZXI*AqNxw4Jo7g+u7uol=Kcs*3s^8T|*EU|eH{djIX7?@qTb;LcZ+G0$xzm1E=WhEw-FqEFdP80J4es|08xLQ8VD_Nz zq4mS-j~pJ|9&sHRe(e4D>65@G<4>tiKR%0l_U(Dv^X1Wm0 z^tkT$rPtQ4@4WGRGx|3C?WcFi?^fPteIR`(nvj@i{HXcy;-u~5{i#h;?>{AeTAt4S z%=5YYi~N`KU(LVXnem%>|BdmD`8|JDc((qB_K*HK_qmrp<9{yC=Pn2@G%V^b4lemF zO)PI-A+8j!Dy;UbIjxN`6%uyOwSHuzmQ1ONp9 z40u5qJRloxAUw!MqzsusUC|DlEUp3*$C~gK_;-Z;L{s7{ODk&k2o zeD9ZimvZ;#1?DT~FYkYPVEhoVK;v-0k*uT5h4+f4jxkF}rMzW4*reaEu!WPC3ninxwAz>?0Y&H9DyA^QMFC+QStJy#uf z9ZxfF8{b9#n*uKdXNA~=rA5rwhl=hLJ0X5v;u86qNrEW`Km$@L@BzHtURl!qH zNpV%_wsMh5i0TH_QME#KHw{*e0nMFS###&7?K%uyHQi~wM*Ro_8G|>5l|})^g2oR` z3QRYe@tTd87h42d%36N0>axzVakmwBx^Q=@aLi@7U@>r=N{_oR&` z9vmJoJWqS=^7izR@tOC%=~w0-7ht-Hd(-Q{b3s|b-XSs}bD;y2qOhoNeJTs}8Lc@Y zBhn>GJnCz7Z_L5i;5g;D<@j6ll7z@aog`e+ea7+RxD zv1yykwy)c}ckIb@-6_9|W!Kd1dwbfmj{mbaJ0&M{uj@XeT=_hKe8`{L|M)=v!83LLsdv@R)b8bBSfCewkXiY=y*ep-R3JTveoMQVnM<&q>ib&3cE1;6_GM zR&!xX?WuFES5DtK^YrXQ8+Km5J+7m&bGRGVqjiCLvFOs^<;7m*K3ae8HHCpg*XM5d z-R!x&{!Yf-(IKPz6~oMjher~hI6PAr-SEQxRqGqZ`%9BUUsmTPm`niR0ZGsVC&+*< z1VJ>BIOG)a6SYJO(HWdOt`n2RD)6HCDuOcMB5@<}6U$LnH#Tv$Pwagh$4H5sE?i37 zoZLToUh>}L>*TK&C=)DLmo2nII8}tY-ceLpj9cuh_$`TQa+;*g2F?w`QU|3SWe}M* zS*jebT)VuF{11f!MK#4cO0mkU$_*-ZDif+XYVvA>>d_kP8m*e1nhRRTwQaPgbPnn2 z>Auk0tuL>C*MMQT&hVm9m@&b)(Ztzg#`Kt(nc1Xyp@o&jcgt!kFKeRp1)D@$Y1
  • {+iEED(WlYR?`CF7nM{CRXG8!11YLkSX#nrX03I&^ z%DVw%Zvb$s0JxI?ybJ&-DFMVS5D;SP-}i?g=z%|MhbkC=DTD_xK*Esy$Qk4jvVe-C zHfTIrgkC|XZ~{0B935AVyMm>>Jtdaky{{k-SKwoHWjFT)VjWxa)aLcpmd6@sarI`K|b0 z3+xt@6}-D{vyimVBjFqoQ<3@gZK8={ieeMubrMlz74n>9?}h_Xq0-ti95NHK*W~Ku z4=Q9RMl1O%JF8fz8mMWjt81ufYG`R|o9ejh()70Lml$*zjv6B-N~Zp1x#m|a=B;e3 zi)<$C%e(bBuqwSYF zFnOruF#V`X;ncC(l1*hiagf&f2uCKmW3W);Zg~ z?}GfrhnI7E-TKK_R|nn?j@-O``|92Dp_pOQha!(wA5T6Vd;WCn*~{_qsWiO*Q<>rjaH^%pp*)u;H=Z^f0oY$Oxvv6cleX)PZYw5|d^K$=+{>t%{rB&b6 zwl(gx;I*2yNv0|@h1tzy0)PxSLlPW=i|`g8xMaqSz+b=^VgO zNKz%;U_MhVLDJoj{DBy5PdPexU=x0U`<_i|cQTR*7YZ`%4&+ zB_vrSe{6UwH6ndm=Bn%!xeM~03Z05QO1;XrRGz4QR9n&D*HqVX)n@3F>JI458b}-Z z8yz;jYf3V6GcUAwVWn!l$7a+{&%VfE*(uz4$kp8K{6-~@W-oQ`E?*nJk${*$ET}5P zD)eL6L8<_)BvLG@GKL&m9WO<%OH@i~OEyipn?~Kdy!A+i^0sR`f->iJ?cXDt)t4QZ zv$(G;&oY1NK;a?tg3m`v3+;-26rU({E?cOmtu#Dwr#h@=t>vT3b1Ob8wJTp&4Og>Pht@>aeAe>Tde`Qd zip&sZA@dfK2>?Q11q`TxM+gBiL=ur2WEe$JEi@7>M<3$Yan`s@+y&esrjKpGdayOT z6+Rz7N>C;2A&e0XiRCO9OESwOYb5J5+g5fG`&o_<5+CU{=Uy&HZf@>To@U-1d|v!2 z0&D`G1s|>J6FM*4BGRKfZM?`ezagzL8GEg85N)*6Ee#dOeI!{UUMi1jhsb#}!L>m4hdWn5a_4BQ7jf;<|{y3f8+Y5S1mQIF=sT- zuC(1~&+D}5TJ1S|k#d>yN@ritRk&7t-E#2F%{{ke?p(d+J@k4w`oX715hJ5d9G|v5 z7aH9(nh{fi`nI)a2^rfz)nPvIqkmX~` zLo37;vz6qP6DxOD7FT6gJy&nakBo=4uT9ZVaF|?$>_@0006vK>1AofWaFH z|Kt4szX0KKy#|+Lj)?#O03c&XQcVB=ZI}Q6bB+K2`vd?0gRB4mdXNAB>ZSkyG422W z6uivOMj`+J0cS}>K~#9!bkng*R$&wd@SoR9+0+&`6!wWiq=O)Wguyo@2tjK!1P6gI`4lpRkUPEynJ~Wi8}#b#itP?q6#P`&evX7xGjE zcQM?lK2YN&mJ8w?Y~eNLi?iiuD!7dujI@OP2#0Z8Xy7B!%nsrqdU%B)T*Adf_9234 z%;5))@iWGkYh1xeh|kc)Y4&IMm3M{bsmmLD!EjxKT*v))_Pv(0nwnmnyB-g)f{ljz zX;OR~Yz^D_zL9jyU>Bn;ZVyt#_qgA{N28hPcjF7J7UH?&`WE+!v(;$+bn+zLVW~kH zN<9V(@lv>xWw>4JVG}otIjMqehSUAuFpc3!rtw=bOxH4v8yTwBAM~;DG*2h)Cn@A{ cd9~*Q09Xb``f$N^1poj507*qoM6N<$f~N+NApigX literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/arrow-right-16.png b/ckan/public/images/icons/arrow-right-16.png new file mode 100644 index 0000000000000000000000000000000000000000..102269baf899d6b2ef618adb066637d1f8bc3c8e GIT binary patch literal 6550 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
  • {+iEED(WlYR?`CF7nM{CRXG8!11YLkSX#nrX03I&^ z%DVw%Zvb$s0JxI?ybJ&-DFMVS5D;SP-}i?g=z%|MhbkC=DTD_xK*Esy$Qk4jvVe-C zHfTIrgkC|XZ~{0B935AVyMm>>Jtdaky{{k-SKwoHWjFT)VjWxa)aLcpmd6@sarI`K|b0 z3+xt@6}-D{vyimVBjFqoQ<3@gZK8={ieeMubrMlz74n>9?}h_Xq0-ti95NHK*W~Ku z4=Q9RMl1O%JF8fz8mMWjt81ufYG`R|o9ejh()70Lml$*zjv6B-N~Zp1x#m|a=B;e3 zi)<$C%e(bBuqwSYF zFnOruF#V`X;ncC(l1*hiagf&f2uCKmW3W);Zg~ z?}GfrhnI7E-TKK_R|nn?j@-O``|92Dp_pOQha!(wA5T6Vd;WCn*~{_qsWiO*Q<>rjaH^%pp*)u;H=Z^f0oY$Oxvv6cleX)PZYw5|d^K$=+{>t%{rB&b6 zwl(gx;I*2yNv0|@h1tzy0)PxSLlPW=i|`g8xMaqSz+b=^VgO zNKz%;U_MhVLDJoj{DBy5PdPexU=x0U`<_i|cQTR*7YZ`%4&+ zB_vrSe{6UwH6ndm=Bn%!xeM~03Z05QO1;XrRGz4QR9n&D*HqVX)n@3F>JI458b}-Z z8yz;jYf3V6GcUAwVWn!l$7a+{&%VfE*(uz4$kp8K{6-~@W-oQ`E?*nJk${*$ET}5P zD)eL6L8<_)BvLG@GKL&m9WO<%OH@i~OEyipn?~Kdy!A+i^0sR`f->iJ?cXDt)t4QZ zv$(G;&oY1NK;a?tg3m`v3+;-26rU({E?cOmtu#Dwr#h@=t>vT3b1Ob8wJTp&4Og>Pht@>aeAe>Tde`Qd zip&sZA@dfK2>?Q11q`TxM+gBiL=ur2WEe$JEi@7>M<3$Yan`s@+y&esrjKpGdayOT z6+Rz7N>C;2A&e0XiRCO9OESwOYb5J5+g5fG`&o_<5+CU{=Uy&HZf@>To@U-1d|v!2 z0&D`G1s|>J6FM*4BGRKfZM?`ezagzL8GEg85N)*6Ee#dOeI!{UUMi1jhsb#}!L>m4hdWn5a_4BQ7jf;<|{y3f8+Y5S1mQIF=sT- zuC(1~&+D}5TJ1S|k#d>yN@ritRk&7t-E#2F%{{ke?p(d+J@k4w`oX715hJ5d9G|v5 z7aH9(nh{fi`nI)a2^rfz)nPvIqkmX~` zLo37;vz6qP6DxOD7FT6gJy&nakBo=4uT9ZVaF|?$>_@0006vK>1AofWaFH z|Kt4szX0KKy#|+Lj)?#O03c&XQcVB=ZI}Q6bB+K2`vd?0gRB4mdXNAB>ZSkyG422W z6uivOMj`+J0f$LMK~#9!Y?HA{oIw>=O7290A>wT*<`Kzya_r3~G@JfgJ?rA=EOL)vmguP9=DmQ&H;%7xms?R&A^6 ziJ-0ysMXA9J-D-_L7#e}Ze_$J^@n<{u4J{X&{R-gsyFInkG9|WFWTy>1fLAeJnCn4 zKMF(YWA%|b9PNJylaBg2G1*r?)qru}XWZ#Wpb7kr^)1kj(N%#jz(DXWfQec*#y+49 zbP|I+`qLqzZmNgT*04IJj;bRuh4((hT5^O#brW??{iQZ~T;kgh-m0q!-U!VcYM~yh z+nIP#y;85$H(6~vG(`!_sDIRrgfyfMs9T{?*IS*07*qo IM6N<$f@M*eYXATM literal 0 HcmV?d00001 diff --git a/ckan/public/images/icons/arrow-right-32.png b/ckan/public/images/icons/arrow-right-32.png new file mode 100644 index 0000000000000000000000000000000000000000..f68956e02a59576bbc1ef91cf5b1263fbcb5e69d GIT binary patch literal 7027 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
  • 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 RpSl15002ovPDHLkV1j;ueggmi literal 0 HcmV?d00001 diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 71e559c25c6..3e13e7f0e8a 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -19,23 +19,25 @@

    Resources

    -
    - - ${h.resource_link(res, c.pkg_dict['name'])} - ${res.get('format')} - (unknown 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')} + (unknown format) +  —  ${h.markdown_extract(res.get('description'))} +
      +
    • Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago
    • +
    +
    +
    + + + ${res.get('url', '')} + +
    +
    (none) diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index de120c1b9f7..c37c4ab4630 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -40,7 +40,7 @@ View View API Endpoint - Download + Download
  • From 47eb9c27518ad6df055d9243cefa1e34d84a1c8e Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Wed, 30 Nov 2011 19:16:09 +0000 Subject: [PATCH 45/70] [ux,dataset/read][m]: Changes to the way the README description is displayed. Solved glitches with overlapping HTML anchors. --- ckan/lib/helpers.py | 4 +- ckan/public/css/style.css | 71 ++++++++++++++------------ ckan/public/images/ldquo.png | Bin 0 -> 6940 bytes ckan/public/scripts/application.js | 12 +++-- ckan/public/scripts/templates.js | 2 +- ckan/templates/package/read_core.html | 23 ++++----- 6 files changed, 61 insertions(+), 51 deletions(-) create mode 100644 ckan/public/images/ldquo.png diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 640598f7066..2fbeb2b0b1f 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -317,7 +317,9 @@ def resource_display_name(resource_dict): if name: return name elif description: - description = description.split('.')[0][:60] + description = description.split('.')[0] + max_len = 60; + if len(description)>max_len: description = description[:max_len]+'...' return description else: return '[no name] %s ' % resource_dict['id'] diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 6667b8d0f42..419de87ff9a 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1012,35 +1012,26 @@ body.package.read .related-datasets li { margin-top: 2em; margin-bottom: 2em; } -#dataset-resources h3 { +body.package.read h3 { margin-bottom: 8px; } .dataset-resource { border-left: 2px solid #eee; margin: 0; - position: relative; -} -.dataset-resource .resource-view-link { - display: block; - padding: 10px 8px; - padding-bottom: 25px; -} -.dataset-resource .resource-view-link .plain-text { - color: #000; + padding: 8px; + margin-bottom: 16px; } - .dataset-resource:hover { border-left: 2px solid #aaa; background: #f7f7f7; } -.dataset-resource-divider { - border-bottom: 1px solid #eee; - margin: 8px 0; -} - -body.package.read .dataset-resource .view-more-link { +.dataset-resource .main-link { + font-size: 125%; +} +.dataset-resource .view-more-link { float: right; + color: #000; text-align: middle; margin-top: 4px; padding: 3px 22px 3px 10px; @@ -1048,24 +1039,20 @@ body.package.read .dataset-resource .view-more-link { opacity:0.4; filter:alpha(opacity=40); /* For IE8 and earlier */ } -body.package.read .dataset-resource:hover .view-more-link { +.dataset-resource .view-more-link:hover { opacity:1.0; filter:alpha(opacity=100); /* For IE8 and earlier */ + text-decoration: underline; } -body.package.read .resource-url { - display: block; - position: absolute; - left: 10px; - bottom: 5px; -} -body.package.read .resource-url a { +.dataset-resource .resource-url a { color: #888; } -body.package.read .resource-url a:hover { +.dataset-resource .resource-url a:hover { color: #333; text-decoration: underline; } + body.package.read .resource-information { padding-left: 0; color: #808080; @@ -1088,20 +1075,38 @@ body.package.read .resource-format { font-weight: bold; } body.package.read #sidebar li.widget-container { - border: 0 + border: 0 +} +body.package.read .notes { + padding: 8px; + border-left: 2px solid #eee; + background: url('/images/ldquo.png') no-repeat top left #f7f7f7; +} +body.package.read .notes #dataset-notes-toggle a { + cursor: pointer; +} +body.package.read .notes #dataset-notes-toggle a.more:after { + content: ' »'; + font-size: 150%; + position: relative; + bottom: -1px; } -body.package.read .notes a { - cursor: pointer; +body.package.read .notes #dataset-notes-toggle a.less:before { + content: '« '; + font-size: 150%; + position: relative; + bottom: -1px; } + body.package.read #notes-extract p { - margin-bottom: 0; + margin-bottom: 0; } body.package.read #notes-remainder { - margin-top: 1em; + margin-top: 1em; } body.package.read #dataset-information .dataset-label { - font-weight: bold; - min-width: 10em; + font-weight: bold; + min-width: 10em; } diff --git a/ckan/public/images/ldquo.png b/ckan/public/images/ldquo.png new file mode 100644 index 0000000000000000000000000000000000000000..286f719f3a1f6d821c3222b585fd3c913819d0da GIT binary patch literal 6940 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
  • {+iEED(WlYR?`CF7nM{CRXG8!11YLkSX#nrX03I&^ z%DVw%Zvb$s0JxI?ybJ&-DFMVS5D;SP-}i?g=z%|MhbkC=DTD_xK*Esy$Qk4jvVe-C zHfTIrgkC|XZ~{0B935AVyMm>>Jtdaky{{k-SKwoHWjFT)VjWxa)aLcpmd6@sarI`K|b0 z3+xt@6}-D{vyimVBjFqoQ<3@gZK8={ieeMubrMlz74n>9?}h_Xq0-ti95NHK*W~Ku z4=Q9RMl1O%JF8fz8mMWjt81ufYG`R|o9ejh()70Lml$*zjv6B-N~Zp1x#m|a=B;e3 zi)<$C%e(bBuqwSYF zFnOruF#V`X;ncC(l1*hiagf&f2uCKmW3W);Zg~ z?}GfrhnI7E-TKK_R|nn?j@-O``|92Dp_pOQha!(wA5T6Vd;WCn*~{_qsWiO*Q<>rjaH^%pp*)u;H=Z^f0oY$Oxvv6cleX)PZYw5|d^K$=+{>t%{rB&b6 zwl(gx;I*2yNv0|@h1tzy0)PxSLlPW=i|`g8xMaqSz+b=^VgO zNKz%;U_MhVLDJoj{DBy5PdPexU=x0U`<_i|cQTR*7YZ`%4&+ zB_vrSe{6UwH6ndm=Bn%!xeM~03Z05QO1;XrRGz4QR9n&D*HqVX)n@3F>JI458b}-Z z8yz;jYf3V6GcUAwVWn!l$7a+{&%VfE*(uz4$kp8K{6-~@W-oQ`E?*nJk${*$ET}5P zD)eL6L8<_)BvLG@GKL&m9WO<%OH@i~OEyipn?~Kdy!A+i^0sR`f->iJ?cXDt)t4QZ zv$(G;&oY1NK;a?tg3m`v3+;-26rU({E?cOmtu#Dwr#h@=t>vT3b1Ob8wJTp&4Og>Pht@>aeAe>Tde`Qd zip&sZA@dfK2>?Q11q`TxM+gBiL=ur2WEe$JEi@7>M<3$Yan`s@+y&esrjKpGdayOT z6+Rz7N>C;2A&e0XiRCO9OESwOYb5J5+g5fG`&o_<5+CU{=Uy&HZf@>To@U-1d|v!2 z0&D`G1s|>J6FM*4BGRKfZM?`ezagzL8GEg85N)*6Ee#dOeI!{UUMi1jhsb#}!L>m4hdWn5a_4BQ7jf;<|{y3f8+Y5S1mQIF=sT- zuC(1~&+D}5TJ1S|k#d>yN@ritRk&7t-E#2F%{{ke?p(d+J@k4w`oX715hJ5d9G|v5 z7aH9(nh{fi`nI)a2^rfz)nPvIqkmX~` zLo37;vz6qP6DxOD7FT6gJy&nakBo=4uT9ZVaF|?$>_@0006vK>1AofWaFH z|Kt4szX0KKy#|+Lj)?#O03c&XQcVB=ZI}Q6bB+K2`vd?0gRB4mdXNAB>ZSkyG422W z6uivOMj`+J0}V+;K~#9!?48}RgCGoqL!j^f!WPa=opGE~tNd@+Ty>_c4f(PmT0|!z zFZph75s6~gf{3_`?xW7AGAijZj)0fyZEV&>^ZPmWq{WPHJQr|u6F#nB4m%xz@i?A1vR zDzsciOH>#UGIRwMid7Zv1B}L1sFqcDgve$FRE0Z4t12vMW&|m@)vyXn#s<~sDOHHA zLhBGqR7i*ubfm5jq7WkXm?9x!pBE-7aSn*fnQ}bXrPLZkS3!x15b;YXA%c%3tvYZv zHH64gN{Cb;AzHC;GdS*|{G>%8UY*i(jZOIC+|yOk`<4F@m#thUi0%-ZC%+e^@hX9E zDy_a?fWDX0oznpS+a6^QKb_0R7fk3=;w&VJxT8}=f(Ci z$r6wr1>z3Uk=x~|$n+4UQ{#YWSqVN0q7#N+sf0Bt#n} zl@Mu!$mWI+s{pV#FQ%20xI;`-B7P~f1~DQ*FR$Lj!q z+koGEg8)nj5Jv@bMDje1OLleY332peT$4=rC|RknEs8D{yxBlj+1ExF&HinId2*}O z;Dy<Q!7s8mzjz1@X>@XmcAE4-+Dd5NU)+BSb=^ i5h9HcX@qFZ{ssX20n~5+$tW5C0000 \ \ - \
    \
    \ + \ '; diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 3e13e7f0e8a..70449919609 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -19,25 +19,22 @@

    Resources

    - - ${h.resource_display_name(res)}   - - + View + ${h.resource_display_name(res)} +    ${res.get('format')} (unknown format) -  —  ${h.markdown_extract(res.get('description'))} +
    ${h.markdown_extract(res.get('description'))}
    • Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago
    - - - - - ${res.get('url', '')} - - +
    + + + ${res.get('url', '')} + +
    -
    (none) From 18125a49c67fd0ec659cecc9c1715968df2d985a Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Wed, 30 Nov 2011 19:25:30 +0000 Subject: [PATCH 46/70] [ux][s]: Experimental. Tables desaturated. The gold colourscheme looks like a highlight, yet there's no significant info. --- ckan/public/css/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 419de87ff9a..8dc4b34d942 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -175,10 +175,10 @@ table caption { background-color: white; } tbody tr:nth-child(even) td, tbody tr.even td { - background-color: #FCF6CF; + background-color: #F9F9F9; } tbody tr:nth-child(odd) td, tbody tr.odd td { - background-color: #FEFEF2; + background-color: #F2F2F2; } tbody tr.table-empty td { background: #f8f8f8; From 4687152b506b9c43a174f5d11f2ce6349ab98875 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Thu, 1 Dec 2011 14:25:15 +0000 Subject: [PATCH 47/70] [navbar,dataset-read][l]: Added dropdown menu feature to navbars. Implemented for dataset resources. Required tweak to package controller. --- ckan/controllers/package.py | 3 ++ ckan/public/css/style.css | 80 +++++++++++++++++++++++++++--- ckan/templates/package/layout.html | 15 ++++-- 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index c55304408f2..fccf571628b 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -360,6 +360,7 @@ def edit(self, id, data=None, errors=None, error_summary=None): if schema and not data: old_data, errors = validate(old_data, schema, context=context) data = data or old_data + c.pkg_dict = data except NotAuthorized: abort(401, _('Unauthorized to read package %s') % '') except NotFound: @@ -532,6 +533,7 @@ def authz(self, id): context = {'model':model,'user':c.user or c.author, 'package':pkg} check_access('package_edit_permissions',context) c.authz_editable = True + c.pkg_dict = get_action('package_show')(context, {'id': id}) except NotAuthorized: c.authz_editable = False if not c.authz_editable: @@ -634,6 +636,7 @@ def resource_read(self, id, resource_id): # required for nav menu c.pkg = c.package c.resource_json = json.dumps(c.resource) + c.pkg_dict = c.package except NotFound: abort(404, _('Resource not found')) except NotAuthorized: diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 8dc4b34d942..f78c4d91fb8 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -231,8 +231,14 @@ tbody tr.table-empty td { #minornavigation li { display: inline-block; margin-right: 3px; - border: 2px solid transparent; - padding: 5px 11px 5px 9px + padding: 5px 11px 5px 9px; + + border: 1px solid transparent; + border-bottom: none; + border-right: none; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; } #minornavigation li.action { float: right; @@ -240,12 +246,7 @@ tbody tr.table-empty td { #minornavigation li.current-tab { background: #000; background-color: #fff; - border: 1px solid #777; - border-bottom: 0px solid #ccc; - border-right: 0px solid #ccc; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border-radius: 5px; + border-color: #777; } #minornavigation li a { text-decoration: none; @@ -261,6 +262,69 @@ tbody tr.table-empty td { color: #222; } +/* ======================================== */ +/* MinorNavigation extension: Dropdown Menu */ +/* ======================================== */ +#minornavigation .dropdown { + position: relative; + display: inline-block; +} +#minornavigation .dropdown .dropdown-appears { + display: none; + position: absolute; + top: 24px; + left: -1px; + width: 100%; +} +#minornavigation .dropdown:hover { + background: #fff; + border-color: #ddd; +} +#minornavigation .dropdown:hover .dropdown-appears { + display: block; +} +#minornavigation .dropdown-items { + margin-top: 9px; + width: 250px; + background: #fff; + border: 1px solid #ddd; + border-top: none; + padding: 2px 0 6px 0; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + box-shadow: 1px 1px 3px #aaa; +} +#minornavigation .dropdown-items a { + display: block; + padding: 5px 12px; + margin: 2px 0; +} +#minornavigation .dropdown-items a:hover { + background: #f0f0f0; +} +/* White box is a hack to connect the LI to the DIV beneath it, covering up + * that bottom bit of the mainnavigation bar. */ +#minornavigation .dropdown .white-box { + border-left: 1px solid #ddd; + background: #fff; + margin-right: -1px; + height: 9px; + position: absolute: + top: 0; + left: 0; +} +/* Rules are different on current tab */ +#minornavigation .dropdown.current-tab:hover, +#minornavigation .dropdown.current-tab .dropdown-items, +#minornavigation .dropdown.current-tab .white-box { + border-color: #777; +} +#minornavigation .dropdown.current-tab .dropdown-items a, +#minornavigation .dropdown.current-tab .dropdown-items a:visited { + color: #bb2222; +} + + /* ======= */ /* Sidebar */ diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index c1feb603782..c14a517bb79 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -9,9 +9,18 @@
    • ${h.subnav_link(c, h.icon('package') + _('View'), controller='package', action='read', id=c.pkg.name)}
    • -
    • ${h.subnav_link(c, h.icon('package') + _('Resource'), - controller='package', action='read', id=c.pkg.name, anchor='dataset-resources')}
    • +
    • ${h.subnav_link(c, h.icon('package_edit') + _('Edit'), controller='package', action='edit', id=c.pkg.name)}
    • From 060efe4a0e7e4ede3337623092848740c58107f9 Mon Sep 17 00:00:00 2001 From: kindly Date: Sun, 4 Dec 2011 00:42:29 +0000 Subject: [PATCH 48/70] add collection and capacity --- .../046_rename_package_group_member.py | 26 ++++++++++++++++--- ckan/model/group.py | 12 ++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/ckan/migration/versions/046_rename_package_group_member.py b/ckan/migration/versions/046_rename_package_group_member.py index 6944b8e36ff..0ab86d87e9e 100644 --- a/ckan/migration/versions/046_rename_package_group_member.py +++ b/ckan/migration/versions/046_rename_package_group_member.py @@ -41,10 +41,12 @@ def upgrade(migrate_engine): ALTER TABLE "member" ADD COLUMN table_name text; +ALTER TABLE "member" ADD COLUMN capacity text; -ALTER TABLE member_revision +ALTER TABLE "member_revision" ADD COLUMN table_name text; +ALTER TABLE "member_revision" ADD COLUMN capacity text; ALTER TABLE "member" @@ -68,16 +70,34 @@ def upgrade(migrate_engine): ALTER TABLE member_revision ADD CONSTRAINT member_revision_revision_id_fkey FOREIGN KEY (revision_id) REFERENCES revision(id); +ALTER TABLE "group" + ADD COLUMN "type" text; +ALTER TABLE "group_revision" + ADD COLUMN "type" text; + update member set table_name = 'package', capacity = 'member'; update member_revision set table_name = 'package', capacity = 'member'; +update "group" set type = 'collection'; +update group_revision set type = 'collection'; + + +ALTER TABLE "member" + ALTER COLUMN table_name set not null; ALTER TABLE "member" - ALTER COLUMN table_name set not null ALTER COLUMN capacity set not null; ALTER TABLE member_revision - ALTER COLUMN table_name set not null + ALTER COLUMN table_name set not null; +ALTER TABLE "member_revision" ALTER COLUMN capacity set not null; + +ALTER TABLE "group" + ALTER COLUMN "type" set not null; +ALTER TABLE "group_revision" + ALTER COLUMN "type" set not null; + + COMMIT; ''' ) diff --git a/ckan/model/group.py b/ckan/model/group.py index 332fb143166..4b820280fa0 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -41,6 +41,15 @@ class Member(vdm.sqlalchemy.RevisionedObjectMixin, vdm.sqlalchemy.StatefulObjectMixin, DomainObject): + def __init__(self, group=None, table_id=None, group_id=None, + table_name=None, capacity='member'): + self.group = group + self.group_id = group_id + self.table_id = table_id + self.table_name = table_name + self.capacity = capacity + + def related_packages(self): # TODO do we want to return all related packages or certain ones? return Session.query(Package).filter_by(id=self.table_id).all() @@ -48,10 +57,11 @@ def related_packages(self): class Group(vdm.sqlalchemy.RevisionedObjectMixin, vdm.sqlalchemy.StatefulObjectMixin, DomainObject): - def __init__(self, name=u'', title=u'', description=u''): + def __init__(self, name=u'', title=u'', description=u'', type=u'collection'): self.name = name self.title = title self.description = description + self.type = type @property def display_name(self): From 6f70385061d11ac5fa2322f823ffdc89746c4e88 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 5 Dec 2011 13:05:34 +0100 Subject: [PATCH 49/70] Only bug Google OpenID users for their full names Only display the 'please enter your full name' flash notice for users who use a Google OpenId and haven't entered a full name. We want Google OpenID users to enter a full name because Google OpenIDs do not make human readable user names, but there's no need to bug other users for full names. --- ckan/controllers/home.py | 6 ++- ckan/tests/functional/test_home.py | 74 ++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index 4d900f3f2ab..1db47997d5f 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -64,7 +64,9 @@ def index(self): if c.userobj is not None: msg = None url = url_for(controller='user', action='edit') - if not c.userobj.email and not c.userobj.fullname: + is_google_id = \ + c.userobj.name.startswith('https://www.google.com/accounts/o8/id') + if not c.userobj.email and (is_google_id and not c.userobj.fullname): msg = _('Please update your profile' ' and add your email address and your full name. %s uses' ' your email address to send you notifications and' @@ -75,7 +77,7 @@ def index(self): ' and add your email address. %s uses your email address' ' to send you notifications and updates, and to let you' ' reset your password.''') % (url, g.site_title) - elif not c.userobj.fullname: + elif is_google_id and not c.userobj.fullname: msg = _('Please update your profile' ' and add your full name.') % (url) if msg: diff --git a/ckan/tests/functional/test_home.py b/ckan/tests/functional/test_home.py index e475a1a3149..e8af2c05821 100644 --- a/ckan/tests/functional/test_home.py +++ b/ckan/tests/functional/test_home.py @@ -161,7 +161,29 @@ def test_update_profile_notice(self): user2 = model.user.User(name='user2', fullname="user 2's full name") user3 = model.user.User(name='user3', email='user3@testusers.org') user4 = model.user.User(name='user4') - users = (user1, user2, user3, user4) + + # Some test users with Google OpenIDs. + user5 = model.user.User( + name='https://www.google.com/accounts/o8/id/id=ACyQatixLeL' + 'ODscWvwqsCXWQ2sa3RRaBhaKTkcsvUElI6tNHIQ1_egX_wt1x3fA' + 'Y983DpW4UQV_U', + fullname="user 5's full name", email="user5@testusers.org") + user6 = model.user.User( + name='https://www.google.com/accounts/o8/id/id=ACyQatixLeL' + 'ODscWvwqsCXWQ2sa3RRaBhaKTkcsvUElI6tNHIQ1_egX_wt1x3fA' + 'Y983DpW4UQV_J', + fullname="user 6's full name") + user7 = model.user.User( + name='https://www.google.com/accounts/o8/id/id=AItOawl27F2' + 'M92ry4jTdjiVx06tuFNA', + email='user7@testusers.org') + user8 = model.user.User( + name='https://www.google.com/accounts/o8/id/id=AItOawl27F2' + 'M92ry4jTdjiVx06tuFNs' + ) + + users = (user1, user2, user3, user4, user5, user6, user7, user8) + google_users = (user5, user6, user7, user8) for user in users: model.repo.new_revision() @@ -174,25 +196,37 @@ def test_update_profile_notice(self): model.repo.new_revision() model.Session.add(user) - if not user.email and not user.fullname: - assert email_and_fullname_notice in response - assert email_notice not in response - assert fullname_notice not in response - - elif user.email and not user.fullname: - assert email_notice not in response - assert fullname_notice in response - assert email_and_fullname_notice not in response - - elif not user.email and user.fullname: - assert email_notice in response - assert fullname_notice not in response - assert email_and_fullname_notice not in response - - elif user.email and user.fullname: - assert email_notice not in response - assert fullname_notice not in response - assert email_and_fullname_notice not in response + if user in google_users: + # Users with Google OpenIDs are asked to give their email if + # they don't have one and to enter a full name if they don't + # have one. + if not user.email and not user.fullname: + assert email_and_fullname_notice in response + assert email_notice not in response + assert fullname_notice not in response + elif user.email and not user.fullname: + assert email_notice not in response + assert fullname_notice in response + assert email_and_fullname_notice not in response + elif not user.email and user.fullname: + assert email_notice in response + assert fullname_notice not in response + assert email_and_fullname_notice not in response + elif user.email and user.fullname: + assert email_notice not in response + assert fullname_notice not in response + assert email_and_fullname_notice not in response + else: + # Users without Google OpenIDs are just asked to give their + # email if they don't have one. + if not user.email: + assert email_notice in response + assert email_and_fullname_notice not in response + assert fullname_notice not in response + elif user.email: + assert email_notice not in response + assert fullname_notice not in response + assert email_and_fullname_notice not in response if not user.email: user.email = "mr_tusks@tusk_family.org" From 27680df1ddce18719bc47bf334bb916653e5b6c2 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 5 Dec 2011 13:16:15 +0100 Subject: [PATCH 50/70] Change the 'please add your email address' notice --- ckan/controllers/home.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index 1db47997d5f..fc1c2e9f2db 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -69,14 +69,13 @@ def index(self): if not c.userobj.email and (is_google_id and not c.userobj.fullname): msg = _('Please update your profile' ' and add your email address and your full name. %s uses' - ' your email address to send you notifications and' - ' updates, and to let you reset your password.''') \ - % (url, g.site_title) + ' your email address if you need to reset your' + ' password.''') % (url, g.site_title) elif not c.userobj.email: msg = _('Please update your profile' ' and add your email address. %s uses your email address' - ' to send you notifications and updates, and to let you' - ' reset your password.''') % (url, g.site_title) + ' if you need to reset your password.') \ + % (url, g.site_title) elif is_google_id and not c.userobj.fullname: msg = _('Please update your profile' ' and add your full name.') % (url) From 3794fb418b292f98d3512fb2dcda27cec6e55ebc Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 5 Dec 2011 16:41:31 +0000 Subject: [PATCH 51/70] [dataset view,dropdown][xs]: Fixed dropdown menu being occluded by datapreviewer. --- ckan/public/css/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index f78c4d91fb8..9ea406cb653 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -271,6 +271,7 @@ tbody tr.table-empty td { } #minornavigation .dropdown .dropdown-appears { display: none; + z-index: 2; position: absolute; top: 24px; left: -1px; From 333eaf5fd0d37e2b35b8f4a38c51d233eedd6418 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 5 Dec 2011 17:30:21 +0000 Subject: [PATCH 52/70] [dataset,nav][s]: Added logical division to dataset navigation. --- ckan/public/css/style.css | 4 ++++ ckan/templates/package/layout.html | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 9ea406cb653..7813b234f6e 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -261,6 +261,10 @@ tbody tr.table-empty td { #minornavigation li.current-tab a:visited { color: #222; } +#minornavigation .divider { + font-size: 14px; + color: #888; +} /* ======================================== */ /* MinorNavigation extension: Dropdown Menu */ diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index c14a517bb79..d339a6a10a9 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -21,10 +21,13 @@ ${h.subnav_link(c, h.icon('package') + _('Resources'), controller='package', action='read', id=c.pkg.name, anchor='dataset-resources')}    -
    • - ${h.subnav_link(c, h.icon('package_edit') + _('Edit'), controller='package', action='edit', id=c.pkg.name)} -
    • ${h.subnav_link(c, h.icon('page_stack') + _('History'), controller='package', action='history', id=c.pkg.name)}
    • + +   |   +
    • + ${h.subnav_link(c, h.icon('package_edit') + _('Edit'), controller='package', action='edit', id=c.pkg.name)} +
    • +
    • ${h.subnav_link(c, h.icon('lock') + _('Authorization'), controller='package', action='authz', id=c.pkg.name)}
    • From 583d5c72bf410bb54fd6a34c23d963579b3667f8 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 5 Dec 2011 17:35:57 +0000 Subject: [PATCH 53/70] [dataset-view][s]: Aesthetic changes to format tags. --- ckan/public/css/style.css | 6 ++++-- ckan/templates/package/read_core.html | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 7813b234f6e..85c5350d553 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1133,8 +1133,10 @@ body.package.read .resource-information li { font-weight: normal; } body.package.read .resource-format { - border: 1px solid #ececec; - padding: 2px; + border: 1px solid #EEE; + margin-left: 8px; + padding: 2px 8px; + box-shadow: 1px 1px 3px #f7f7f7; color: #808080; } .flash-messages .success .new-dataset { diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 70449919609..8da25245075 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -23,7 +23,6 @@

      Resources

      ${h.resource_display_name(res)}    ${res.get('format')} - (unknown format)
      ${h.markdown_extract(res.get('description'))}
      • Last updated: ${h.time_ago_in_words_from_str(res.get('last_modified'), granularity='day')} ago
      • From f3ae2e82d3aac9ce8ec8c4fc92e8b0e9c486f001 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 5 Dec 2011 18:03:21 +0000 Subject: [PATCH 54/70] [dataset view,dropdown][m]: Resources dropdown is disabled when there are no resources. --- ckan/public/css/style.css | 7 ++++++- ckan/public/images/icons/package-disabled.png | Bin 0 -> 3474 bytes ckan/templates/package/layout.html | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 ckan/public/images/icons/package-disabled.png diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 85c5350d553..8990e772b7c 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -252,7 +252,8 @@ tbody tr.table-empty td { text-decoration: none; font-weight: bold; } -#minornavigation li a img { +#minornavigation li a img, +#minornavigation li.disabled img:first-child { margin-bottom: -4px; padding-right: 5px; } @@ -265,6 +266,10 @@ tbody tr.table-empty td { font-size: 14px; color: #888; } +#minornavigation li.disabled { + font-weight: bold; + color: #777; +} /* ======================================== */ /* MinorNavigation extension: Dropdown Menu */ diff --git a/ckan/public/images/icons/package-disabled.png b/ckan/public/images/icons/package-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..beac3e99cd0d61749122070b4de241af8543cfb8 GIT binary patch literal 3474 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 A!T
        • ${h.subnav_link(c, h.icon('package') + _('View'), controller='package', action='read', id=c.pkg.name)}
        • -
        • + ${h.icon('package-disabled') + _('Resources')}    +
        • + + + +    + + +
        • ${h.subnav_link(c, h.icon('page_stack') + _('History'), controller='package', action='history', id=c.pkg.name)}
        •   |   From 6c5e48f12dcbd9f2ef1f3d85e3b19bd233b4feec Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Mon, 5 Dec 2011 18:14:42 +0000 Subject: [PATCH 55/70] [dataset view][s]: Clicking 'Resources' tab links to first resource. --- ckan/templates/package/layout.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ckan/templates/package/layout.html b/ckan/templates/package/layout.html index dc21041adb7..f87dad7b45f 100644 --- a/ckan/templates/package/layout.html +++ b/ckan/templates/package/layout.html @@ -25,9 +25,10 @@ - ${h.subnav_link(c, h.icon('package') + _('Resources'), - controller='package', action='read', id=c.pkg.name, anchor='dataset-resources')} + + ${h.icon('package') + _('Resources')}    + From 21ef64532c6403bae6a19167d0f7e9d456b7df18 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 12:10:16 +0000 Subject: [PATCH 56/70] [dataset view][s]: Aesthetics; resources with sparse attributes are displayed nicely. --- ckan/public/css/style.css | 10 +++------- ckan/templates/package/read_core.html | 13 ++++++------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 8990e772b7c..93db456f080 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1126,17 +1126,13 @@ body.package.read h3 { color: #333; text-decoration: underline; } +.dataset-resource p { + margin: 0; +} body.package.read .resource-information { - padding-left: 0; color: #808080; } -body.package.read .resource-information li { - display: inline; - margin-right: 4px; - border: none; - font-weight: normal; -} body.package.read .resource-format { border: 1px solid #EEE; margin-left: 8px; diff --git a/ckan/templates/package/read_core.html b/ckan/templates/package/read_core.html index 8da25245075..a3944139406 100644 --- a/ckan/templates/package/read_core.html +++ b/ckan/templates/package/read_core.html @@ -23,16 +23,15 @@

          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.markdown_extract(res.get('description'))}

          +

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

          +

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

          From 17f33031f5e348e623146601f830b8c67462fe00 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 12:44:25 +0000 Subject: [PATCH 57/70] Erring on the side of caution (for now). Stripped datapreview hack. We were using a blacklist of filetypes which wouldn't be previewed; all other items would be put in an iframe. Any remote binary file not on the blacklist (BZ2, EXE...) would be automatically downloaded. Until we can check the remote mimetype and be certain it is text, it's not safe to put the file in an iframe. --- ckan/public/scripts/application.js | 36 ++++-------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 3acca3b6032..d7ab957bff8 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -731,7 +731,6 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ } }); - (function ($) { var my = {}; my.jsonpdataproxyUrl = 'http://jsonpdataproxy.appspot.com/'; @@ -746,29 +745,6 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ DATAEXPLORER.TABLEVIEW.initialize(my.dialogId); resourceData.formatNormalized = my.normalizeFormat(resourceData.format); - // do not create previews for some items - var _tformat = resourceData.format.toLowerCase(); - if ( - _tformat.indexOf('zip') != -1 - || - _tformat.indexOf('tgz') != -1 - || - _tformat.indexOf('targz') != -1 - || - _tformat.indexOf('gzip') != -1 - || - _tformat.indexOf('gz:') != -1 - || - _tformat.indexOf('word') != -1 - || - _tformat.indexOf('pdf') != -1 - || - _tformat === 'other' - ) { - var _msg = $('

          We are unable to preview this type of resource: ' + resourceData.format + '

          '); - my.$dialog.html(_msg); - return; - } my.loadPreviewDialog(resourceData); }; @@ -839,14 +815,10 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ }); } else { - // HACK: but should work - // we displays a fullscreen dialog with the url in an iframe. - my.$dialog.empty(); - var el = $(''); - el.attr('src', resourceData.url); - el.attr('width', '100%'); - el.attr('height', '100%'); - my.$dialog.append(el); + // Cannot reliably preview this item - with no mimetype/format information, + // can't guarantee it's not a remote binary file such as an executable. + var _msg = $('

          We are unable to preview this type of resource: ' + resourceData.formatNormalized + '

          '); + my.$dialog.html(_msg); } }; From 2e1961f4211856b9aff290b40ecaf49d58600b61 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 13:23:49 +0000 Subject: [PATCH 58/70] [resource read][m]: Aesthetic changes to resource read page. --- ckan/controllers/package.py | 2 +- ckan/public/css/style.css | 16 ++++++++-------- ckan/templates/package/resource_read.html | 22 ++++++++++++++++------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index fccf571628b..7b7c3b5b4c5 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -634,7 +634,7 @@ def resource_read(self, id, resource_id): c.resource = get_action('resource_show')(context, {'id': resource_id}) c.package = get_action('package_show')(context, {'id': id}) # required for nav menu - c.pkg = c.package + c.pkg = context['package'] c.resource_json = json.dumps(c.resource) c.pkg_dict = c.package except NotFound: diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 93db456f080..0c90b417cf5 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1081,7 +1081,7 @@ body.package.read .related-datasets li { list-style-type: none; } -#dataset-license img.open-data { vertical-align: top; } +img.open-data { margin: 1px 0 0 8px; vertical-align: top; } #dataset-resources { margin-top: 2em; margin-bottom: 2em; @@ -1149,34 +1149,34 @@ body.package.read .resource-format { body.package.read #sidebar li.widget-container { border: 0 } -body.package.read .notes { +.notes { padding: 8px; border-left: 2px solid #eee; background: url('/images/ldquo.png') no-repeat top left #f7f7f7; } -body.package.read .notes #dataset-notes-toggle a { +.notes #dataset-notes-toggle a { cursor: pointer; } -body.package.read .notes #dataset-notes-toggle a.more:after { +.notes #dataset-notes-toggle a.more:after { content: ' »'; font-size: 150%; position: relative; bottom: -1px; } -body.package.read .notes #dataset-notes-toggle a.less:before { +.notes #dataset-notes-toggle a.less:before { content: '« '; font-size: 150%; position: relative; bottom: -1px; } -body.package.read #notes-extract p { +#notes-extract p { margin-bottom: 0; } -body.package.read #notes-remainder { +#notes-remainder { margin-top: 1em; } -body.package.read #dataset-information .dataset-label { +.dataset-label { font-weight: bold; min-width: 10em; } diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index c37c4ab4630..4f358a5c3ee 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -57,14 +57,21 @@
          ${c.resource['format'] or 'Unknown'}
          -
          Openness
          +
          Licence
          - + + ${c.pkg.license.title.split('::')[-1]} + ${c.pkg.license.title} + ${c.pkg.license_id} + + + - [Open Data] + [Open Data] - + ${h.icon('lock')} @@ -87,6 +94,7 @@

          Preview

          +

          Additional Information

  • FieldValue
    Source
    @@ -98,8 +106,10 @@

    Additional Information

    - - + + + +
    ${_(key)}${c.resource[key]}${_(key)}${c.resource[key]}
    From 438ee5cf405d9e5b70dedd3b8788c268b2808b55 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 14:50:33 +0000 Subject: [PATCH 59/70] [tests,fix][s]: Adjusted some new code to pass tests. --- ckan/controllers/package.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index b67ab1caef0..be35c0c4fc9 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -360,7 +360,8 @@ def edit(self, id, data=None, errors=None, error_summary=None): if schema and not data: old_data, errors = validate(old_data, schema, context=context) data = data or old_data - c.pkg_dict = data + # Merge all elements for the complete package dictionary + c.pkg_dict = dict(old_data.items() + data.items()) except NotAuthorized: abort(401, _('Unauthorized to read package %s') % '') except NotFound: From 96889476e45e4b9f3799057ec494696970fcc749 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 15:55:27 +0000 Subject: [PATCH 60/70] [group-read][xs]: Spacing out the group/read page. --- ckan/public/css/style.css | 6 ++++++ ckan/templates/group/read.html | 12 +++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index bf7ed0d3e54..0908a94abe1 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -588,6 +588,12 @@ form.simple-form input[type=password] { .group.read .property-list li ul li { margin-left: -2.5em; } +.group.read .notes p { + margin-bottom: 0; +} +.group-dataset-list { + margin: 2em 0; +} /* ============== */ diff --git a/ckan/templates/group/read.html b/ckan/templates/group/read.html index 1b9093551db..7bb0f85a378 100644 --- a/ckan/templates/group/read.html +++ b/ckan/templates/group/read.html @@ -26,11 +26,13 @@

    Administrators

    ${c.group_description_formatted}
    -

    Datasets:

    -

    There are ${c.page.item_count} datasets in this group.

    - ${c.page.pager()} - ${package_list_from_dict(c.page.items)} - ${c.page.pager()} +
    +

    Datasets:

    +

    There are ${c.page.item_count} datasets in this group.

    + ${c.page.pager()} + ${package_list_from_dict(c.page.items)} + ${c.page.pager()} +
    From 03122c8c44091fbbdc7f1791983779c762cb4b78 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 16:56:00 +0000 Subject: [PATCH 61/70] [resource preview][s]: Making a best effort to display datapreviews of HTML and GoogleDocs (in response to: 17f33031) --- ckan/public/scripts/application.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index c6d050ffdcd..fee666df39f 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -819,6 +819,16 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ DATAEXPLORER.TABLEVIEW.$dialog.dialog('open'); }); } + else if (resourceData.formatNormalized in {'html':'', 'htm':''} + || resourceData.url.substring(0,23)=='http://docs.google.com/') { + // we displays a fullscreen dialog with the url in an iframe. + my.$dialog.empty(); + var el = $(''); + el.attr('src', resourceData.url); + el.attr('width', '100%'); + el.attr('height', '100%'); + my.$dialog.append(el); + } else { // Cannot reliably preview this item - with no mimetype/format information, // can't guarantee it's not a remote binary file such as an executable. From a89a48731ba548170045a60ac2930e0019c299c7 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 17:48:47 +0000 Subject: [PATCH 62/70] [groups][xs]: Changed navbar text when not logged in. --- ckan/templates/group/layout.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ckan/templates/group/layout.html b/ckan/templates/group/layout.html index 64153aa871e..a5204e7af2c 100644 --- a/ckan/templates/group/layout.html +++ b/ckan/templates/group/layout.html @@ -26,7 +26,8 @@ ${h.subnav_link(c, h.icon('group') + _('List Groups'), controller='group', action='index')}
  • - ${h.subnav_link(c, h.icon('group_add') + _('Add a Group'), controller='group', action='new')} + + ${h.subnav_link(c, h.icon('group_add') + _('Login to Add a Group'), controller='group', action='new')}
  • From b2748e395760083be2071b007641e787a071f955 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Tue, 6 Dec 2011 18:59:19 +0000 Subject: [PATCH 63/70] [gravatars][s]: Showing gravatars on all pages. Trac #1528. --- ckan/templates/layout_base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/templates/layout_base.html b/ckan/templates/layout_base.html index 4f8f1810f03..d608889476a 100644 --- a/ckan/templates/layout_base.html +++ b/ckan/templates/layout_base.html @@ -56,7 +56,7 @@