diff --git a/ckan/ckan_nose_plugin.py b/ckan/ckan_nose_plugin.py index 58b9d0061a0..7018e31c23c 100644 --- a/ckan/ckan_nose_plugin.py +++ b/ckan/ckan_nose_plugin.py @@ -1,10 +1,13 @@ from nose.plugins import Plugin from inspect import isclass +import hashlib import os import sys +import re import pkg_resources from paste.deploy import loadapp from pylons import config +import unittest import time class CkanNose(Plugin): @@ -54,12 +57,35 @@ def options(self, parser, env): action='store_true', dest='docstrings', help='set this to display test docstrings instead of module names') + parser.add_option( + '--segments', + dest='segments', + help='A string containing a hex digits that represent which of' + 'the 16 test segments to run. i.e 15af will run segments 1,5,a,f') + + def wantClass(self, cls): + name = cls.__name__ + + wanted = (not cls.__name__.startswith('_') + and (issubclass(cls, unittest.TestCase) + or re.search('(?:^|[\b_\./-])[Tt]est', name) + )) + + if self.segments and str(hashlib.md5(name).hexdigest())[0] not in self.segments: + return False + + return wanted + + def finalize(self, report): + if self.segments: + print 'Segments: %s' % self.segments def configure(self, settings, config): CkanNose.settings = settings if settings.is_ckan: self.enabled = True self.is_first_test = True + self.segments = settings.segments def describeTest(self, test): if not CkanNose.settings.docstrings: @@ -70,22 +96,22 @@ def startTest(self, test): """ startTest: start timing. """ - self._started = time.time() +## self._started = time.time() def stopTest(self, test): """ stopTest: stop timing, canonicalize the test name, and save the run time. """ - runtime = time.time() - self._started - - # CTB: HACK! - f = open('times.txt', 'a') - - testname = str(test) - #if ' ' in testname: - # testname = testname.split(' ')[1] - - f.write('%s,%s\n' % (testname, str(runtime))) - - f.close() +## runtime = time.time() - self._started +## +## # CTB: HACK! +## f = open('times.txt', 'a') +## +## testname = str(test) +## #if ' ' in testname: +## # testname = testname.split(' ')[1] +## +## f.write('%s,%s\n' % (testname, str(runtime))) +## +## f.close() diff --git a/ckan/config/environment.py b/ckan/config/environment.py index b4c79d9b8ca..33e9360ff9d 100644 --- a/ckan/config/environment.py +++ b/ckan/config/environment.py @@ -75,6 +75,10 @@ def find_controller(self, controller): plugin.update_config(config) # This is set up before globals are initialized + site_id = os.environ.get('CKAN_SITE_ID') + if site_id: + config['ckan.site_id'] = site_id + site_url = config.get('ckan.site_url', '') ckan_host = config['ckan.host'] = urlparse(site_url).netloc if config.get('ckan.site_id') is None: @@ -125,7 +129,12 @@ def template_loaded(template): warnings.filterwarnings('ignore', "^Did not recognize type 'BIGINT' of column 'size'", sqlalchemy.exc.SAWarning) warnings.filterwarnings('ignore', "^Did not recognize type 'tsvector' of column 'search_vector'", sqlalchemy.exc.SAWarning) - engine = sqlalchemy.engine_from_config(config, 'sqlalchemy.') + ckan_db = os.environ.get('CKAN_DB') + + if ckan_db: + engine = sqlalchemy.create_engine(ckan_db) + else: + engine = sqlalchemy.engine_from_config(config, 'sqlalchemy.') if not model.meta.engine: model.init_model(engine) diff --git a/ckan/config/routing.py b/ckan/config/routing.py index 149c01bd2a7..9d89d82d3de 100644 --- a/ckan/config/routing.py +++ b/ckan/config/routing.py @@ -27,8 +27,8 @@ def make_map(): PUT_POST = dict(method=['PUT','POST']) OPTIONS = dict(method=['OPTIONS']) - from lib.plugins import register_package_plugins - from lib.plugins import register_group_plugins + from ckan.lib.plugins import register_package_plugins + from ckan.lib.plugins import register_group_plugins map = Mapper(directory=config['pylons.paths']['controllers'], always_scan=config['debug']) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index 7c8332f47dd..d85cd0f0ee1 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -14,7 +14,7 @@ import ckan.forms import ckan.logic.action.get -from lib.plugins import lookup_group_plugin +from ckan.lib.plugins import lookup_group_plugin log = logging.getLogger(__name__) @@ -233,10 +233,6 @@ def edit(self, id, data=None, errors=None, error_summary=None): old_data = get_action('group_show')(context, data_dict) c.grouptitle = old_data.get('title') c.groupname = old_data.get('name') - schema = self._db_to_form_schema() - if schema and not data: - old_data, errors = validate(old_data, schema, context=context) - data = data or old_data except NotFound: abort(404, _('Group not found')) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index 90260ab48e3..5d8c9cefa4d 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -23,7 +23,7 @@ import ckan.logic.action.get from home import CACHE_PARAMETER -from lib.plugins import lookup_package_plugin +from ckan.lib.plugins import lookup_package_plugin log = logging.getLogger(__name__) @@ -428,9 +428,6 @@ def edit(self, id, data=None, errors=None, error_summary=None): return self._save_edit(id, context) try: old_data = get_action('package_show')(context, {'id':id}) - schema = self._db_to_form_schema(package_type=package_type) - if schema and not data: - old_data, errors = validate(old_data, schema, context=context) data = data or old_data # Merge all elements for the complete package dictionary c.pkg_dict = dict(old_data.items() + data.items()) diff --git a/ckan/lib/plugins.py b/ckan/lib/plugins.py index 18c9594cf94..548f7674f9a 100644 --- a/ckan/lib/plugins.py +++ b/ckan/lib/plugins.py @@ -207,15 +207,6 @@ def history_template(self): def package_form(self): return 'package/new_package_form.html' - def form_to_db_schema(self): - schema = logic.schema.package_form_schema() - schema['groups'] = { - 'name': [not_empty, val.group_id_or_name_exists, unicode], - 'id': [ignore_missing, unicode], - } - return schema - - def form_to_db_schema_options(self, options): ''' This allows us to select different schemas for different purpose eg via the web interface or via the api or creation vs diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py index a36547206af..fd17862a2b2 100644 --- a/ckan/logic/action/create.py +++ b/ckan/logic/action/create.py @@ -1,7 +1,7 @@ import logging from pylons.i18n import _ -import lib.plugins as lib_plugins +import ckan.lib.plugins as lib_plugins import ckan.logic as logic import ckan.rating as ratings import ckan.plugins as plugins diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index bf28588a769..65eef45e270 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -15,13 +15,16 @@ import ckan.logic as logic import ckan.logic.action import ckan.lib.dictization.model_dictize as model_dictize +import ckan.lib.navl.dictization_functions import ckan.model.misc as misc import ckan.plugins as plugins import ckan.lib.search as search +import ckan.lib.plugins as lib_plugins log = logging.getLogger('ckan.logic') # define some shortcuts +validate = ckan.lib.navl.dictization_functions.validate table_dictize = ckan.lib.dictization.table_dictize render = ckan.lib.base.render Authorizer = ckan.authz.Authorizer @@ -370,6 +373,7 @@ def package_relationships_list(context, data_dict): def package_show(context, data_dict): model = context['model'] + context['session'] = model.Session name_or_id = data_dict.get("id") or data_dict['name_or_id'] pkg = model.Package.get(name_or_id) @@ -386,6 +390,11 @@ def package_show(context, data_dict): for item in plugins.PluginImplementations(plugins.IPackageController): item.read(pkg) + schema = lib_plugins.lookup_package_plugin(package_dict['type']).db_to_form_schema() + + if schema: + package_dict, errors = validate(package_dict, schema, context=context) + return package_dict def resource_show(context, data_dict): @@ -436,6 +445,11 @@ def group_show(context, data_dict): for item in plugins.PluginImplementations(plugins.IGroupController): item.read(group) + schema = lib_plugins.lookup_group_plugin(group_dict['type']).db_to_form_schema() + + if schema: + package_dict, errors = validate(group_dict, schema, context=context) + return group_dict def group_package_show(context, data_dict): diff --git a/ckan/logic/action/update.py b/ckan/logic/action/update.py index 2fc23f2328f..39b801261c7 100644 --- a/ckan/logic/action/update.py +++ b/ckan/logic/action/update.py @@ -12,7 +12,7 @@ import ckan.lib.dictization.model_save as model_save import ckan.lib.navl.dictization_functions import ckan.lib.navl.validators as validators -import lib.plugins as lib_plugins +import ckan.lib.plugins as lib_plugins log = logging.getLogger(__name__) @@ -138,7 +138,7 @@ def package_update(context, data_dict): model = context['model'] user = context['user'] - name_or_id = data_dict.get("id") or data_dict['name_or_id'] + name_or_id = data_dict.get("id") or data_dict['name'] model.Session.remove() model.Session()._context = context diff --git a/ckan/logic/schema.py b/ckan/logic/schema.py index aa0d84ea0c4..e77b8948dcd 100644 --- a/ckan/logic/schema.py +++ b/ckan/logic/schema.py @@ -115,6 +115,7 @@ def default_package_schema(): 'url': [ignore_missing, unicode],#, URL(add_http=False)], 'version': [ignore_missing, unicode, package_version_validator], 'state': [ignore_not_package_admin, ignore_missing], + 'type': [ignore_missing, unicode], '__extras': [ignore], '__junk': [empty], 'resources': default_resource_schema(), @@ -125,6 +126,7 @@ def default_package_schema(): 'groups': { 'id': [ignore_missing, unicode], 'name': [ignore_missing, unicode], + 'title': [ignore_missing, unicode], '__extras': [ignore], } } @@ -153,8 +155,7 @@ def package_form_schema(): schema['log_message'] = [ignore_missing, unicode, no_http] schema['groups'] = { 'id': [ignore_missing, unicode], - '__extras': [empty], - 'name': [ignore, unicode], + '__extras': [ignore], } schema['tag_string'] = [ignore_missing, tag_string_convert] schema['extras_validation'] = [duplicate_extras_key, ignore] diff --git a/ckan/logic/validators.py b/ckan/logic/validators.py index b532389e05a..58c69ee3aef 100644 --- a/ckan/logic/validators.py +++ b/ckan/logic/validators.py @@ -312,6 +312,9 @@ def ignore_not_package_admin(key, data, errors, context): model = context['model'] user = context.get('user') + if 'ignore_auth' in context: + return + if user and Authorizer.is_sysadmin(user): return diff --git a/ckan/public/img/glyphicons-halflings-white.png b/ckan/public/img/glyphicons-halflings-white.png new file mode 100644 index 00000000000..a20760bfde5 Binary files /dev/null and b/ckan/public/img/glyphicons-halflings-white.png differ diff --git a/ckan/public/img/glyphicons-halflings.png b/ckan/public/img/glyphicons-halflings.png new file mode 100644 index 00000000000..92d4445dfd0 Binary files /dev/null and b/ckan/public/img/glyphicons-halflings.png differ diff --git a/ckan/public/scripts/vendor/recline/css/data-explorer.css b/ckan/public/scripts/vendor/recline/css/data-explorer.css index 208ce906a56..b27e3f82081 100644 --- a/ckan/public/scripts/vendor/recline/css/data-explorer.css +++ b/ckan/public/scripts/vendor/recline/css/data-explorer.css @@ -23,39 +23,32 @@ height: 30px; } -.header .recline-query-editor form { - height: 30px; - margin-bottom: 0; +.header .recline-query-editor .input-prepend { + margin-bottom: auto; } -.header .recline-query-editor label { - float: none; +.header .recline-query-editor .text-query input { + float: left; } -.header .recline-query-editor label { - float: none; +.recline-query-editor .text-query .btn-group { + display: inline; + float:left; + margin-left:-2px; } -.header .recline-query-editor input.text-query { - float: left; - margin-top: 1px; - margin-right: 5px; - width: 200px; +.recline-query-editor .text-query .btn-group .dropdown-toggle { + -moz-border-radius:0px 3px 3px 0px; + -webkit-border-radius:0px 3px 3px 0px; + border-radius:0px 3px 3px 0px; } -.header .recline-query-editor .pagination input { - width: 30px; - height: 18px; - padding: 2px 4px; - margin-top: -4px; +.recline-query-editor .text-query .btn-group ul { + margin-left:-110px; } .header .recline-query-editor .pagination a { - line-height: 28px; -} - -.header .recline-query-editor form .btn { - vertical-align: top; + line-height: 26px; } .data-view-container { @@ -79,6 +72,11 @@ * Data Table *********************************************************/ +.data-table .btn-group .dropdown-toggle { + padding: 1px 3px; + line-height: auto; +} + .data-table-container { overflow: auto; height: 550px; @@ -86,7 +84,6 @@ .data-table { border: 1px solid #ccc; - font-size: 12px; width: 100%; } @@ -100,44 +97,46 @@ width: 20px; } +/* direct borrowing from twitter buttons */ +.data-table th, +.transform-column-view .expression-preview-table-wrapper th +{ + background-color: #e6e6e6; + background-repeat: no-repeat; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); + background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + color: #333; + border: 1px solid #ccc; + border-bottom-color: #bbb; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -webkit-transition: 0.1s linear all; + -moz-transition: 0.1s linear all; + -ms-transition: 0.1s linear all; + -o-transition: 0.1s linear all; + transition: 0.1s linear all; +} + + /********************************************************** * Data Table Menus *********************************************************/ -a.column-header-menu, a.root-header-menu { +.column-header-menu, a.root-header-menu { float: right; - display: block; - margin: 0 4px 0 0; - width: 17px; - height: 19px; - background-image: url(images/menu-dropdown.png); - background-repeat: no-repeat; -} - -a.row-header-menu:hover, a.root-header-menu:hover { - background-position: -17px 0px; - text-decoration: none; -} - -a.row-header-menu { - float: left; - display: block; - margin: -2px 0 -4px 0; - width: 17px; - height: 18px; - background-image: url(images/menu-dropdown.png); - background-repeat: no-repeat; } .read-only a.row-header-menu { display: none; } -a.column-header-menu:hover { - background-position: -17px 0px; - text-decoration: none; -} - .column-header-recon-stats-bar { margin-top: 10px; height: 4px; @@ -203,45 +202,6 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { color: red; } -.data-table-menu-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -ul.data-table-menu { - display: none; - outline-style: none; - background: white; - color: black; - font-size: 12px; - height: auto; - list-style: none; - overflow: hidden; - position: absolute; - text-align: left; - width: 120px; - z-index: 666; - border: 1px solid #CCC; - border-right: 1px solid #666; - border-bottom: 1px solid #666; - margin: 0; padding: 0; } - ul.data-table-menu * { - margin: 0; - padding: 0; } - ul.data-table-menu a { - line-height: 14px; - color: black; - display: block; - padding: 5px 7px; - text-decoration: none; } - ul.data-table-menu li { - height: 24px; } - ul.data-table-menu li:hover { - background-color: #DBE8F8 } - /* TODO: not sure the rest of this is needed */ .data-table-cell-editor, .data-table-topic-popup { overflow: auto; diff --git a/ckan/public/scripts/vendor/recline/recline.js b/ckan/public/scripts/vendor/recline/recline.js index 3102ad36c70..ee6f6fa33b6 100644 --- a/ckan/public/scripts/vendor/recline/recline.js +++ b/ckan/public/scripts/vendor/recline/recline.js @@ -131,14 +131,13 @@ this.recline.Model = this.recline.Model || {}; // ## A Dataset model // -// A model must have the following (Backbone) attributes: +// A model has the following (non-Backbone) attributes: // // * fields: (aka columns) is a FieldList listing all the fields on this -// Dataset (this can be set explicitly, or, on fetch() of Dataset -// information from the backend, or as is perhaps most common on the first -// query) -// * currentDocuments: a DocumentList containing the Documents we have currently loaded for viewing (you update currentDocuments by calling getRows) -// * docCount: total number of documents in this dataset (obtained on a fetch for this Dataset) +// Dataset (this can be set explicitly, or, will be set by Dataset.fetch() or Dataset.query() +// * currentDocuments: a DocumentList containing the Documents we have +// currently loaded for viewing (you update currentDocuments by calling query) +// * docCount: total number of documents in this dataset my.Dataset = Backbone.Model.extend({ __type__: 'Dataset', initialize: function(model, backend) { @@ -166,7 +165,7 @@ my.Dataset = Backbone.Model.extend({ query: function(queryObj) { this.trigger('query:start'); var self = this; - this.queryState.set(queryObj, {silent: true}); + this.queryState.set(queryObj); var dfd = $.Deferred(); this.backend.query(this, this.queryState.toJSON()).done(function(rows) { var docs = _.map(rows, function(row) { @@ -255,18 +254,6 @@ my.backends = {}; var util = function() { var templates = { transformActions: '
  • Global transform...
  • ' - , columnActions: ' \ -
  • Transform...
  • \ -
  • Delete this column
  • \ -
  • Sort ascending
  • \ -
  • Sort descending
  • \ -
  • Hide this column
  • \ - ' - , rowActions: '
  • Delete this row
  • ' - , rootActions: ' \ - {{#columns}} \ -
  • Show column: {{.}}
  • \ - {{/columns}}' , cellEditor: ' \