diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000000..ed2c3b6bc6f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +omit = /ckan/migration/*, /ckan/tests/*, */tests/* +source = ckan, ckanext diff --git a/.travis.yml b/.travis.yml index af450298b6c..c6503aca704 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,8 @@ python: env: - PGVERSION=9.1 - PGVERSION=8.4 -script: ./bin/travis-build +install: ./bin/travis-install-dependencies +script: ./bin/travis-run-tests notifications: irc: channels: @@ -14,3 +15,5 @@ notifications: on_failure: change template: - "%{repository} %{branch} %{commit} %{build_url} %{author}: %{message}" +after_success: + - coveralls diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b97798a4522..10ecc8fcce2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,10 +12,28 @@ v2.2 API changes and deprecations: + +* The `ckan.api_url` has been completely removed and it can no longer be used * The edit() and after_update() methods of IPackageController plugins are now called when updating a resource using the web frontend or the resource_update API action [#1052] +v2.1.1 2013-11-8 +================ + +Bug fixes: + * Fix errors on preview on non-root locations (#960) + * Fix place-holder images on non-root locations (#1309) + * Don't accept invalid URLs in resource proxy (#1106) + * Make sure came_from url is local (#1039) + * Fix logout redirect in non-root locations (#1025) + * Wrong auth checks for sysadmins on package_create (#1184) + * Don't return private datasets on package_list (#1295) + * Stop tracking failing when no lang/encoding headers (#1192) + * Fix for paster db clean command getting frozen + * Fix organization not set when editing a dataset (#1199) + * Fix PDF previews (#1194) + * Fix preview failing on private datastore resources (#1221) v2.1 2013-08-13 =============== @@ -96,6 +114,18 @@ Known issues: * Under certain authorization setups the frontend for the groups functionality may not work as expected (See #1176 #1175). +v2.0.3 2013-11-8 +================ + +Bug fixes: + * Fix errors on preview on non-root locations (#960) + * Don't accept invalid URLs in resource proxy (#1106) + * Make sure came_from url is local (#1039) + * Fix logout redirect in non-root locations (#1025) + * Don't return private datasets on package_list (#1295) + * Stop tracking failing when no lang/encoding headers (#1192) + * Fix for paster db clean command getting frozen + v2.0.2 2013-08-13 ================= diff --git a/README.rst b/README.rst index 93d80e668cc..9c810209231 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,10 @@ CKAN: The Open Source Data Portal Software :target: http://travis-ci.org/okfn/ckan :alt: Build Status +.. image:: https://coveralls.io/repos/okfn/ckan/badge.png + :target: https://coveralls.io/r/okfn/ckan + :alt: Test coverage + **CKAN is the world’s leading open-source data portal platform**. CKAN makes it easy to publish, share and work with data. It's a data management system that provides a powerful platform for cataloging, storing and accessing diff --git a/bin/travis-build b/bin/travis-install-dependencies similarity index 56% rename from bin/travis-build rename to bin/travis-install-dependencies index 51e633781b4..50b5ebd22cd 100755 --- a/bin/travis-build +++ b/bin/travis-install-dependencies @@ -1,4 +1,7 @@ -#!/bin/sh +#!/bin/bash + +# Exit immediately if any command fails +set -e # Drop Travis' postgres cluster if we're building using a different pg version TRAVIS_PGVERSION='9.1' @@ -32,43 +35,15 @@ python setup.py develop # Install npm dpes for mocha npm install -g mocha-phantomjs phantomjs -# Configure Solr -echo "NO_START=0\nJETTY_HOST=127.0.0.1\nJETTY_PORT=8983\nJAVA_HOME=$JAVA_HOME" | sudo tee /etc/default/jetty -# FIXME the solr schema cannot be hardcoded as it is dependent on the ckan version -sudo cp ckan/config/solr/schema-2.0.xml /etc/solr/conf/schema.xml -sudo service jetty restart - paster db init -c test-core.ini # If Postgres >= 9.0, we don't need to use datastore's legacy mode. if [ $PGVERSION != '8.4' ] then - psql -c 'CREATE USER datastore_default;' -U postgres - sed -i -e 's/.*datastore.read_url.*/ckan.datastore.read_url = postgresql:\/\/datastore_default@\/datastore_test/' test-core.ini + sed -i -e 's/.*datastore.read_url.*/ckan.datastore.read_url = postgresql:\/\/datastore_default:pass@\/datastore_test/' test-core.ini paster datastore set-permissions postgres -c test-core.ini else sed -i -e 's/.*datastore.read_url.*//' test-core.ini fi cat test-core.ini - -# Run mocha front-end tests -# We need ckan to be running for some tests -paster serve test-core.ini & -sleep 5 # Make sure the server has fully started -mocha-phantomjs http://localhost:5000/base/test/index.html -# Did an error occur? -MOCHA_ERROR=$? -# We are done so kill ckan -killall paster - -# And finally, run the nosetests -nosetests --ckan --with-pylons=test-core.ini --nologcapture ckan ckanext -# Did an error occur? -NOSE_ERROR=$? - -[ "0" -ne "$MOCHA_ERROR" ] && echo MOCKA tests have failed -[ "0" -ne "$NOSE_ERROR" ] && echo NOSE tests have failed - -# If an error occurred in our tests make sure travis knows -exit `expr $MOCHA_ERROR + $NOSE_ERROR` diff --git a/bin/travis-run-tests b/bin/travis-run-tests new file mode 100755 index 00000000000..760be3f458f --- /dev/null +++ b/bin/travis-run-tests @@ -0,0 +1,28 @@ +#!/bin/sh + +# Configure Solr +echo "NO_START=0\nJETTY_HOST=127.0.0.1\nJETTY_PORT=8983\nJAVA_HOME=$JAVA_HOME" | sudo tee /etc/default/jetty +# FIXME the solr schema cannot be hardcoded as it is dependent on the ckan version +sudo cp ckan/config/solr/schema-2.0.xml /etc/solr/conf/schema.xml +sudo service jetty restart + +# Run mocha front-end tests +# We need ckan to be running for some tests +paster serve test-core.ini & +sleep 5 # Make sure the server has fully started +mocha-phantomjs http://localhost:5000/base/test/index.html +# Did an error occur? +MOCHA_ERROR=$? +# We are done so kill ckan +killall paster + +# And finally, run the nosetests +nosetests --ckan --with-pylons=test-core.ini --nologcapture ckan ckanext +# Did an error occur? +NOSE_ERROR=$? + +[ "0" -ne "$MOCHA_ERROR" ] && echo MOCKA tests have failed +[ "0" -ne "$NOSE_ERROR" ] && echo NOSE tests have failed + +# If an error occurred in our tests make sure travis knows +exit `expr $MOCHA_ERROR + $NOSE_ERROR` diff --git a/ckan/config/deployment.ini_tmpl b/ckan/config/deployment.ini_tmpl index 2f91158ea49..5d89ad2a155 100644 --- a/ckan/config/deployment.ini_tmpl +++ b/ckan/config/deployment.ini_tmpl @@ -66,6 +66,7 @@ ckan.auth.user_create_organizations = true ckan.auth.user_delete_groups = true ckan.auth.user_delete_organizations = true ckan.auth.create_user_via_api = false +ckan.auth.create_user_via_web = true ## Search Settings @@ -79,6 +80,7 @@ ckan.site_id = default ## Plugins Settings # Note: Add ``datastore`` to enable the CKAN DataStore +# Add ``datapusher`` to enable DataPusher # Add ``pdf_preview`` to enable the resource preview for PDFs # Add ``resource_proxy`` to enable resorce proxying and get around the # same origin policy @@ -146,8 +148,8 @@ ckan.feeds.author_link = # Make sure you have set up the DataStore -datapusher.formats = csv -datapusher.url = http://datapusher.ckan.org/ +ckan.datapusher.formats = csv +ckan.datapusher.url = http://datapusher.ckan.org/ ## Activity Streams Settings diff --git a/ckan/config/environment.py b/ckan/config/environment.py index 49be9b726a6..409f77435a4 100644 --- a/ckan/config/environment.py +++ b/ckan/config/environment.py @@ -159,7 +159,7 @@ def find_controller(self, controller): ''' This code is based on Genshi code - Copyright © 2006-2012 Edgewall Software + Copyright © 2006-2012 Edgewall Software All rights reserved. Redistribution and use in source and binary forms, with or diff --git a/ckan/config/middleware.py b/ckan/config/middleware.py index 7699596138d..5ba02933d46 100644 --- a/ckan/config/middleware.py +++ b/ckan/config/middleware.py @@ -23,6 +23,7 @@ from ckan.plugins import PluginImplementations from ckan.plugins.interfaces import IMiddleware from ckan.lib.i18n import get_locales_from_config +import ckan.lib.uploader as uploader from ckan.config.environment import load_environment import ckan.lib.app_globals as app_globals @@ -148,13 +149,15 @@ def make_app(conf, full_stack=True, static_files=True, **app_conf): cache_max_age=static_max_age) static_parsers = [static_app, app] - storage_directory = config.get('ckan.storage_path') + storage_directory = uploader.get_storage_path() if storage_directory: path = os.path.join(storage_directory, 'storage') try: os.makedirs(path) except OSError, e: - pass + ## errno 17 is file already exists + if e.errno != 17: + raise storage_app = StaticURLParser(path, cache_max_age=static_max_age) diff --git a/ckan/config/routing.py b/ckan/config/routing.py index 91c766c3c89..327412cde2b 100644 --- a/ckan/config/routing.py +++ b/ckan/config/routing.py @@ -222,7 +222,6 @@ def make_map(): ]))) m.connect('/dataset/{action}/{id}', requirements=dict(action='|'.join([ - 'edit', 'new_metadata', 'new_resource', 'history', @@ -234,20 +233,24 @@ def make_map(): 'delete', 'api_data', ]))) + m.connect('dataset_edit', '/dataset/edit/{id}', action='edit', + ckan_icon='edit') m.connect('dataset_followers', '/dataset/followers/{id}', action='followers', ckan_icon='group') m.connect('dataset_activity', '/dataset/activity/{id}', action='activity', ckan_icon='time') m.connect('/dataset/activity/{id}/{offset}', action='activity') m.connect('/dataset/{id}.{format}', action='read') + m.connect('dataset_resources', '/dataset/resources/{id}', + action='resources', ckan_icon='reorder') m.connect('dataset_read', '/dataset/{id}', action='read', ckan_icon='sitemap') m.connect('/dataset/{id}/resource/{resource_id}', action='resource_read') m.connect('/dataset/{id}/resource_delete/{resource_id}', action='resource_delete') - m.connect('/dataset/{id}/resource_edit/{resource_id}', - action='resource_edit') + m.connect('resource_edit', '/dataset/{id}/resource_edit/{resource_id}', + action='resource_edit', ckan_icon='edit') m.connect('/dataset/{id}/resource/{resource_id}/download', action='resource_download') m.connect('/dataset/{id}/resource/{resource_id}/download/{filename}', @@ -361,6 +364,7 @@ def make_map(): action='followers', ckan_icon='group') m.connect('user_edit', '/user/edit/{id:.*}', action='edit', ckan_icon='cog') + m.connect('user_delete', '/user/delete/{id}', action='delete') m.connect('/user/reset/{id:.*}', action='perform_reset') m.connect('register', '/user/register', action='register') m.connect('login', '/user/login', action='login') diff --git a/ckan/controllers/admin.py b/ckan/controllers/admin.py index 288cf1afb7e..56b0face2b3 100644 --- a/ckan/controllers/admin.py +++ b/ckan/controllers/admin.py @@ -34,6 +34,11 @@ def _get_config_form_items(self): {'text': 'Green', 'value': '/base/css/green.css'}, {'text': 'Maroon', 'value': '/base/css/maroon.css'}, {'text': 'Fuchsia', 'value': '/base/css/fuchsia.css'}] + + homepages = [{'value': '1', 'text': 'Introductory area, search, featured group and featured organization'}, + {'value': '2', 'text': 'Search, stats, introductory area, featured organization and featured group'}, + {'value': '3', 'text': 'Search, introductory area and stats'}] + items = [ {'name': 'ckan.site_title', 'control': 'input', 'label': _('Site Title'), 'placeholder': ''}, {'name': 'ckan.main_css', 'control': 'select', 'options': styles, 'label': _('Style'), 'placeholder': ''}, @@ -42,6 +47,7 @@ def _get_config_form_items(self): {'name': 'ckan.site_about', 'control': 'markdown', 'label': _('About'), 'placeholder': _('About page text')}, {'name': 'ckan.site_intro_text', 'control': 'markdown', 'label': _('Intro Text'), 'placeholder': _('Text on home page')}, {'name': 'ckan.site_custom_css', 'control': 'textarea', 'label': _('Custom CSS'), 'placeholder': _('Customisable css inserted into the page header')}, + {'name': 'ckan.homepage_style', 'control': 'select', 'options': homepages, 'label': _('Homepage'), 'placeholder': ''}, ] return items diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index 90c78679bf4..08ba3a8e641 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -114,8 +114,10 @@ def _finish_ok(self, response_data=None, return self._finish(status_int, response_data, content_type) - def _finish_not_authz(self): + def _finish_not_authz(self, extra_msg=None): response_data = _('Access denied') + if extra_msg: + response_data = '%s - %s' % (response_data, extra_msg) return self._finish(403, response_data, 'json') def _finish_not_found(self, extra_msg=None): @@ -194,10 +196,14 @@ def action(self, logic_function, ver=None): 'data': request_data} return_dict['success'] = False return self._finish(400, return_dict, content_type='json') - except NotAuthorized: + except NotAuthorized, e: return_dict['error'] = {'__type': 'Authorization Error', 'message': _('Access denied')} return_dict['success'] = False + + if e.extra_msg: + return_dict['error']['message'] += ': %s' % e.extra_msg + return self._finish(403, return_dict, content_type='json') except NotFound, e: return_dict['error'] = {'__type': 'Not Found Error', @@ -277,8 +283,9 @@ def list(self, ver=None, register=None, subregister=None, id=None): except NotFound, e: extra_msg = e.extra_msg return self._finish_not_found(extra_msg) - except NotAuthorized: - return self._finish_not_authz() + except NotAuthorized, e: + extra_msg = e.extra_msg + return self._finish_not_authz(extra_msg) def show(self, ver=None, register=None, subregister=None, id=None, id2=None): @@ -308,8 +315,9 @@ def show(self, ver=None, register=None, subregister=None, except NotFound, e: extra_msg = e.extra_msg return self._finish_not_found(extra_msg) - except NotAuthorized: - return self._finish_not_authz() + except NotAuthorized, e: + extra_msg = e.extra_msg + return self._finish_not_authz(extra_msg) def _represent_package(self, package): return package.as_dict(ref_package_by=self.ref_package_by, @@ -354,8 +362,9 @@ def create(self, ver=None, register=None, subregister=None, data_dict.get("id"))) return self._finish_ok(response_data, resource_location=location) - except NotAuthorized: - return self._finish_not_authz() + except NotAuthorized, e: + extra_msg = e.extra_msg + return self._finish_not_authz(extra_msg) except NotFound, e: extra_msg = e.extra_msg return self._finish_not_found(extra_msg) @@ -410,8 +419,9 @@ def update(self, ver=None, register=None, subregister=None, try: response_data = action(context, data_dict) return self._finish_ok(response_data) - except NotAuthorized: - return self._finish_not_authz() + except NotAuthorized, e: + extra_msg = e.extra_msg + return self._finish_not_authz(extra_msg) except NotFound, e: extra_msg = e.extra_msg return self._finish_not_found(extra_msg) @@ -458,8 +468,9 @@ def delete(self, ver=None, register=None, subregister=None, try: response_data = action(context, data_dict) return self._finish_ok(response_data) - except NotAuthorized: - return self._finish_not_authz() + except NotAuthorized, e: + extra_msg = e.extra_msg + return self._finish_not_authz(extra_msg) except NotFound, e: extra_msg = e.extra_msg return self._finish_not_found(extra_msg) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index adbd3d7cacc..1f57d6dc416 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -7,7 +7,6 @@ from urllib import urlencode from pylons.i18n import get_lang -from pylons import config import ckan.lib.base as base import ckan.lib.helpers as h @@ -281,7 +280,8 @@ def pager_url(q=None, page=None): facets = OrderedDict() - default_facet_titles = {'groups': _('Groups'), + default_facet_titles = {'organization': _('Organizations'), + 'groups': _('Groups'), 'tags': _('Tags'), 'res_format': _('Formats'), 'license_id': _('License')} @@ -347,6 +347,9 @@ def pager_url(q=None, page=None): c.facets = {} c.page = h.Page(collection=[]) + self._setup_template_variables(context, {'id':id}, + group_type=group_type) + def bulk_process(self, id): ''' Allow bulk processing of datasets for an organization. Make private/public or delete. For organization admins.''' @@ -462,7 +465,6 @@ def edit(self, id, data=None, errors=None, error_summary=None): group = context.get("group") c.group = group - context.pop('for_edit') c.group_dict = self._action('group_show')(context, data_dict) try: @@ -521,8 +523,6 @@ def _force_reindex(self, grp): def _save_edit(self, id, context): try: - old_group = self._action('group_show')(context, {"id": id}) - old_image_url = old_group.get('image_url') data_dict = clean_dict(dict_fns.unflatten( tuplize_dict(parse_params(request.params)))) context['message'] = data_dict.get('log_message', '') @@ -620,6 +620,19 @@ def member_new(self, id): data_dict = clean_dict(dict_fns.unflatten( tuplize_dict(parse_params(request.params)))) data_dict['id'] = id + + email = data_dict.get('email') + if email: + user_data_dict = { + 'email': email, + 'group_id': data_dict['id'], + 'role': data_dict['role'] + } + del data_dict['email'] + user_dict = self._action('user_invite')(context, + user_data_dict) + data_dict['username'] = user_dict['name'] + c.group_dict = self._action('group_member_create')(context, data_dict) self._redirect_to(controller='group', action='members', id=id) else: @@ -823,7 +836,9 @@ def admins(self, id): def about(self, id): c.group_dict = self._get_group_dict(id) - return render(self._about_template(c.group_dict['type'])) + group_type = c.group_dict['type'] + self._setup_template_variables({}, {'id': id}, group_type=group_type) + return render(self._about_template(group_type)) def _get_group_dict(self, id): ''' returns the result of group_show action or aborts if there is a diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index da3d4b3901b..7aa15be87bd 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -67,6 +67,7 @@ def index(self): c.search_facets = query['search_facets'] c.facet_titles = { + 'organization': _('Organizations'), 'groups': _('Groups'), 'tags': _('Tags'), 'res_format': _('Formats'), diff --git a/ckan/controllers/organization.py b/ckan/controllers/organization.py index b44d95e057f..3907143a187 100644 --- a/ckan/controllers/organization.py +++ b/ckan/controllers/organization.py @@ -15,46 +15,5 @@ class OrganizationController(group.GroupController): # this makes us use organization actions group_type = 'organization' - def _group_form(self, group_type=None): - return 'organization/new_organization_form.html' - - def _form_to_db_schema(self, group_type=None): - return group.lookup_group_plugin(group_type).form_to_db_schema() - - def _db_to_form_schema(self, group_type=None): - '''This is an interface to manipulate data from the database - into a format suitable for the form (optional)''' - pass - - def _setup_template_variables(self, context, data_dict, group_type=None): - pass - - def _new_template(self, group_type): - return 'organization/new.html' - - def _about_template(self, group_type): - return 'organization/about.html' - - def _index_template(self, group_type): - return 'organization/index.html' - - def _admins_template(self, group_type): - return 'organization/admins.html' - - def _bulk_process_template(self, group_type): - return 'organization/bulk_process.html' - - def _read_template(self, group_type): - return 'organization/read.html' - - def _history_template(self, group_type): - return group.lookup_group_plugin(group_type).history_template() - - def _edit_template(self, group_type): - return 'organization/edit.html' - - def _activity_template(self, group_type): - return 'organization/activity_stream.html' - def _guess_group_type(self, expecting_name=False): return 'organization' diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index 4f88c2d568e..a9cb787f2cf 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -24,6 +24,7 @@ import ckan.lib.plugins import ckan.lib.uploader as uploader import ckan.plugins as p +import ckan.lib.render from ckan.common import OrderedDict, _, json, request, c, g, response from home import CACHE_PARAMETERS @@ -306,6 +307,31 @@ def _content_type_from_accept(self): ct, mu, ext = accept.parse_header(request.headers.get('Accept', '')) return ct, ext, (NewTextTemplate, MarkupTemplate)[mu] + def resources(self, id): + package_type = self._get_package_type(id.split('@')[0]) + context = {'model': model, 'session': model.Session, + 'user': c.user or c.author, 'for_view': True, + 'auth_user_obj': c.userobj} + data_dict = {'id': id} + + try: + check_access('package_update', context) + except NotAuthorized, e: + abort(401, _('User %r not authorized to edit %s') % (c.user, id)) + # check if package exists + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + c.pkg = context['package'] + except NotFound: + abort(404, _('Dataset not found')) + except NotAuthorized: + abort(401, _('Unauthorized to read package %s') % id) + + self._setup_template_variables(context, {'id': id}, + package_type=package_type) + + return render('package/resources.html') + def read(self, id, format='html'): if not format == 'html': ctype, extension, loader = \ @@ -371,7 +397,15 @@ def read(self, id, format='html'): template = self._read_template(package_type) template = template[:template.index('.') + 1] + format - return render(template, loader_class=loader) + try: + return render(template, loader_class=loader) + except ckan.lib.render.TemplateNotFound: + msg = _("Viewing {package_type} datasets in {format} format is " + "not supported (template file {file} not found).".format( + package_type=package_type, format=format, file=template)) + abort(404, msg) + + assert False, "We should never get here" def history(self, id): package_type = self._get_package_type(id.split('@')[0]) @@ -672,11 +706,14 @@ def new_resource(self, id, data=None, errors=None, error_summary=None): abort(404, _('The dataset {id} could not be found.').format(id=id)) # required for nav menu vars['pkg_dict'] = pkg_dict + template = 'package/new_resource_not_draft.html' if pkg_dict['state'] == 'draft': vars['stage'] = ['complete', 'active'] + template = 'package/new_resource.html' elif pkg_dict['state'] == 'draft-complete': vars['stage'] = ['complete', 'active', 'complete'] - return render('package/new_resource.html', extra_vars=vars) + template = 'package/new_resource.html' + return render(template, extra_vars=vars) def new_metadata(self, id, data=None, errors=None, error_summary=None): ''' FIXME: This is a temporary action to allow styling of the diff --git a/ckan/controllers/template.py b/ckan/controllers/template.py index 1ebc700345b..0755b1a2cf5 100644 --- a/ckan/controllers/template.py +++ b/ckan/controllers/template.py @@ -1,6 +1,5 @@ -from genshi.template.loader import TemplateNotFound - import ckan.lib.base as base +import ckan.lib.render class TemplateController(base.BaseController): @@ -29,11 +28,11 @@ def view(self, url): """ try: return base.render(url) - except TemplateNotFound: + except ckan.lib.render.TemplateNotFound: if url.endswith('.html'): base.abort(404) url += '.html' try: return base.render(url) - except TemplateNotFound: + except ckan.lib.render.TemplateNotFound: base.abort(404) diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index e1bd29b2b26..1c6fefff344 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -183,6 +183,22 @@ def new(self, data=None, errors=None, error_summary=None): c.form = render(self.new_user_form, extra_vars=vars) return render('user/new.html') + def delete(self, id): + '''Delete user with id passed as parameter''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'id': id} + + try: + get_action('user_delete')(context, data_dict) + user_index = h.url_for(controller='user', action='index') + h.redirect_to(user_index) + except NotAuthorized: + msg = _('Unauthorized to delete user with id "{user_id}".') + abort(401, msg.format(user_id=id)) + def _save_new(self, context): try: data_dict = logic.clean_dict(unflatten( @@ -392,6 +408,9 @@ def request_reset(self): if request.method == 'POST': id = request.params.get('user') + context = {'model': model, + 'user': c.user} + data_dict = {'id': id} user_obj = None try: @@ -435,18 +454,16 @@ def perform_reset(self, id): # FIXME We should reset the reset key when it is used to prevent # reuse of the url context = {'model': model, 'session': model.Session, - 'user': c.user, - 'auth_user_obj': c.userobj, + 'user': id, 'keep_sensitive_data': True} - data_dict = {'id': id} - try: check_access('user_reset', context) except NotAuthorized: abort(401, _('Unauthorized to reset password.')) try: + data_dict = {'id': id} user_dict = get_action('user_show')(context, data_dict) # Be a little paranoid, and get rid of sensitive data that's @@ -468,6 +485,7 @@ def perform_reset(self, id): new_password = self._get_form_password() user_dict['password'] = new_password user_dict['reset_key'] = c.reset_key + user_dict['state'] = model.State.ACTIVE user = get_action('user_update')(context, user_dict) h.flash_success(_("Your password has been reset.")) diff --git a/ckan/lib/app_globals.py b/ckan/lib/app_globals.py index 0739274c671..38138e87815 100644 --- a/ckan/lib/app_globals.py +++ b/ckan/lib/app_globals.py @@ -29,6 +29,7 @@ 'ckan.site_about', 'ckan.site_intro_text', 'ckan.site_custom_css', + 'ckan.homepage_style', ] config_details = { @@ -38,14 +39,13 @@ # has been setup in load_environment(): 'ckan.site_id': {}, 'ckan.recaptcha.publickey': {'name': 'recaptcha_publickey'}, - 'ckan.recaptcha.privatekey': {'name': 'recaptcha_publickey'}, 'ckan.template_title_deliminater': {'default': '-'}, 'ckan.template_head_end': {}, 'ckan.template_footer_end': {}, 'ckan.dumps_url': {}, 'ckan.dumps_format': {}, - 'ckan.api_url': {}, 'ofs.impl': {'name': 'ofs_impl'}, + 'ckan.homepage_style': {'default': '1'}, # split string 'search.facets': {'default': 'organization groups tags res_format license_id', diff --git a/ckan/lib/authenticator.py b/ckan/lib/authenticator.py index 6f061caad91..cf6ed016694 100644 --- a/ckan/lib/authenticator.py +++ b/ckan/lib/authenticator.py @@ -12,9 +12,9 @@ class OpenIDAuthenticator(object): def authenticate(self, environ, identity): if 'repoze.who.plugins.openid.userid' in identity: - openid = identity.get('repoze.who.plugins.openid.userid') + openid = identity['repoze.who.plugins.openid.userid'] user = User.by_openid(openid) - if user is None: + if user is None or not user.is_active(): return None else: return user.name @@ -25,14 +25,20 @@ class UsernamePasswordAuthenticator(object): implements(IAuthenticator) def authenticate(self, environ, identity): - if not 'login' in identity or not 'password' in identity: + if not ('login' in identity and 'password' in identity): return None - user = User.by_name(identity.get('login')) + + login = identity['login'] + user = User.by_name(login) + if user is None: - log.debug('Login failed - username %r not found', identity.get('login')) - return None - if user.validate_password(identity.get('password')): + log.debug('Login failed - username %r not found', login) + elif not user.is_active(): + log.debug('Login as %r failed - user isn\'t active', login) + elif not user.validate_password(identity['password']): + log.debug('Login as %r failed - password not valid', login) + else: return user.name - log.debug('Login as %r failed - password not valid', identity.get('login')) + return None diff --git a/ckan/lib/base.py b/ckan/lib/base.py index 3260bbb7e83..bb7e16682cb 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -128,8 +128,7 @@ def render_template(): try: template_path, template_type = render_.template_info(template_name) except render_.TemplateNotFound: - template_type = 'genshi' - template_path = '' + raise # snippets should not pass the context # but allow for legacy genshi templates @@ -227,6 +226,8 @@ def render_template(): raise ckan.exceptions.CkanUrlException( '\nAn Exception has been raised for template %s\n%s' % (template_name, e.message)) + except render_.TemplateNotFound: + raise class ValidationException(Exception): @@ -314,8 +315,9 @@ def _identify_user_default(self): if c.user: c.user = c.user.decode('utf8') c.userobj = model.User.by_name(c.user) - if c.userobj is None: - # This occurs when you are logged in, clean db + if c.userobj is None or not c.userobj.is_active(): + # This occurs when a user that was still logged in is deleted, + # or when you are logged in, clean db # and then restart (or when you change your username) # There is no user object, so even though repoze thinks you # are logged in and your cookie has ckan_display_name, we diff --git a/ckan/lib/cli.py b/ckan/lib/cli.py index 14c217ed096..72d971dbb71 100644 --- a/ckan/lib/cli.py +++ b/ckan/lib/cli.py @@ -10,6 +10,8 @@ import ckan.include.rcssmin as rcssmin import ckan.lib.fanstatic_resources as fanstatic_resources import sqlalchemy as sa +import urlparse +import routes import paste.script from paste.registry import Registry @@ -99,6 +101,12 @@ def _load_config(self): self.translator_obj = MockTranslator() self.registry.register(pylons.translator, self.translator_obj) + ## give routes enough information to run url_for + parsed = urlparse.urlparse(conf.get('ckan.site_url', 'http://0.0.0.0')) + request_config = routes.request_config() + request_config.host = parsed.netloc + parsed.path + request_config.protocol = parsed.scheme + def _setup_app(self): cmd = paste.script.appinstall.SetupCommand('setup-app') cmd.run([self.filename]) @@ -842,12 +850,10 @@ def show(self, dataset_ref): pprint.pprint(dataset.as_dict()) def delete(self, dataset_ref): - from ckan import plugins import ckan.model as model dataset = self._get_dataset(dataset_ref) old_state = dataset.state - plugins.load('synchronous_search') rev = model.repo.new_revision() dataset.delete() model.repo.commit_and_remove() @@ -855,12 +861,10 @@ def delete(self, dataset_ref): print '%s %s -> %s' % (dataset.name, old_state, dataset.state) def purge(self, dataset_ref): - from ckan import plugins import ckan.model as model dataset = self._get_dataset(dataset_ref) name = dataset.name - plugins.load('synchronous_search') rev = model.repo.new_revision() dataset.purge() model.repo.commit_and_remove() @@ -1286,8 +1290,6 @@ class CreateTestDataCommand(CkanCommand): def command(self): self._load_config() self._setup_app() - from ckan import plugins - plugins.load('synchronous_search') # so packages get indexed from create_test_data import CreateTestData if self.args: diff --git a/ckan/lib/create_test_data.py b/ckan/lib/create_test_data.py index 3edf865c7c7..6c7a275c36f 100644 --- a/ckan/lib/create_test_data.py +++ b/ckan/lib/create_test_data.py @@ -519,8 +519,9 @@ def _create_user_without_commit(cls, name='', **user_dict): @classmethod def create_user(cls, name='', **kwargs): - cls._create_user_without_commit(name, **kwargs) + user = cls._create_user_without_commit(name, **kwargs) model.Session.commit() + return user @classmethod def flag_for_deletion(cls, pkg_names=[], tag_names=[], group_names=[], diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index 6bc5b08d2c1..80c43a4aadd 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -228,6 +228,9 @@ def package_dictize(pkg, context): if not result: raise logic.NotFound result_dict = d.table_dictize(result, context) + #strip whitespace from title + if result_dict.get('title'): + result_dict['title'] = result_dict['title'].strip() #resources res_rev = model.resource_revision_table resource_group = model.resource_group_table diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index fceab6f1b6a..f5f0a7e197f 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -37,6 +37,7 @@ import ckan.lib.maintain as maintain import ckan.lib.datapreview as datapreview import ckan.logic as logic +import ckan.lib.uploader as uploader from ckan.common import ( _, ungettext, g, c, request, session, json, OrderedDict @@ -597,7 +598,8 @@ def get_facet_title(name): if config_title: return config_title - facet_titles = {'groups': _('Groups'), + facet_titles = {'organization': _('Organizations'), + 'groups': _('Groups'), 'tags': _('Tags'), 'res_format': _('Formats'), 'license': _('License'), } @@ -1664,10 +1666,84 @@ def new_activities(): return action({}, {}) def uploads_enabled(): - if config.get('ckan.storage_path'): + if uploader.get_storage_path(): return True return False +def get_featured_organizations(count=1): + '''Returns a list of favourite organization in the form + of organization_list action function + ''' + config_orgs = config.get('ckan.featured_orgs', '').split() + orgs = featured_group_org(get_action='organization_show', + list_action='organization_list', + count=count, + items=config_orgs) + return orgs + + +def get_featured_groups(count=1): + '''Returns a list of favourite group the form + of organization_list action function + ''' + config_groups = config.get('ckan.featured_groups', '').split() + groups = featured_group_org(get_action='group_show', + list_action='group_list', + count=count, + items=config_groups) + return groups + + +def featured_group_org(items, get_action, list_action, count): + def get_group(id): + context = {'ignore_auth': True, + 'limits': {'packages': 2}, + 'for_view': True} + data_dict = {'id': id} + + try: + out = logic.get_action(get_action)(context, data_dict) + except logic.ObjectNotFound: + return None + return out + + groups_data = [] + + extras = logic.get_action(list_action)({}, {}) + + # list of found ids to prevent duplicates + found = [] + for group_name in items + extras: + group = get_group(group_name) + if not group: + continue + # check if duplicate + if group['id'] in found: + continue + found.append(group['id']) + groups_data.append(group) + if len(groups_data) == count: + break + + return groups_data + + +def get_site_statistics(): + stats = {} + stats['dataset_count'] = logic.get_action('package_search')( + {}, {"rows": 1})['count'] + stats['group_count'] = len(logic.get_action('group_list')({}, {})) + stats['organization_count'] = len( + logic.get_action('organization_list')({}, {})) + result = model.Session.execute( + '''select count(*) from related r + left join related_dataset rd on r.id = rd.related_id + where rd.status = 'active' or rd.id is null''').first()[0] + stats['related_count'] = result + + return stats + + # these are the functions that will end up in `h` template helpers __allowed_functions__ = [ # functions defined in ckan.lib.helpers @@ -1766,4 +1842,7 @@ def uploads_enabled(): 'submit', 'asbool', 'uploads_enabled', + 'get_featured_organizations', + 'get_featured_groups', + 'get_site_statistics', ] diff --git a/ckan/lib/mailer.py b/ckan/lib/mailer.py index f0e1978ac2c..72ebe33e2e0 100644 --- a/ckan/lib/mailer.py +++ b/ckan/lib/mailer.py @@ -100,21 +100,37 @@ def mail_user(recipient, subject, body, headers={}): mail_recipient(recipient.display_name, recipient.email, subject, body, headers=headers) +def get_reset_link_body(user): + reset_link_message = _( + '''You have requested your password on %(site_title)s to be reset. -RESET_LINK_MESSAGE = _( -'''You have requested your password on %(site_title)s to be reset. + Please click the following link to confirm this request: -Please click the following link to confirm this request: + %(reset_link)s + ''') - %(reset_link)s -''') + d = { + 'reset_link': get_reset_link(user), + 'site_title': g.site_title + } + return reset_link_message % d -def make_key(): - return uuid.uuid4().hex[:10] +def get_invite_body(user): + invite_message = _( + '''You have been invited to %(site_title)s. A user has already been created to + you with the username %(user_name)s. You can change it later. -def create_reset_key(user): - user.reset_key = unicode(make_key()) - model.repo.commit_and_remove() + To accept this invite, please reset your password at: + + %(reset_link)s + ''') + + d = { + 'reset_link': get_reset_link(user), + 'site_title': g.site_title, + 'user_name': user.name, + } + return invite_message % d def get_reset_link(user): return urljoin(g.site_url, @@ -123,17 +139,24 @@ def get_reset_link(user): id=user.id, key=user.reset_key)) -def get_reset_link_body(user): - d = { - 'reset_link': get_reset_link(user), - 'site_title': g.site_title - } - return RESET_LINK_MESSAGE % d - def send_reset_link(user): create_reset_key(user) body = get_reset_link_body(user) - mail_user(user, _('Reset your password'), body) + subject = _('Reset your password') + mail_user(user, subject, body) + +def send_invite(user): + create_reset_key(user) + body = get_invite_body(user) + subject = _('Invite for {site_title}'.format(site_title=g.site_title)) + mail_user(user, subject, body) + +def create_reset_key(user): + user.reset_key = unicode(make_key()) + model.repo.commit_and_remove() + +def make_key(): + return uuid.uuid4().hex[:10] def verify_reset_link(user, key): if not key: diff --git a/ckan/lib/plugins.py b/ckan/lib/plugins.py index 97263dcf7f8..40a398bd3f4 100644 --- a/ckan/lib/plugins.py +++ b/ckan/lib/plugins.py @@ -53,7 +53,8 @@ def lookup_group_plugin(group_type=None): """ if group_type is None: return _default_group_plugin - return _group_plugins.get(group_type, _default_group_plugin) + return _group_plugins.get(group_type, _default_organization_plugin + if group_type == 'organization' else _default_group_plugin) def register_package_plugins(map): @@ -418,3 +419,39 @@ def setup_template_variables(self, context, data_dict): c.auth_for_change_state = True except logic.NotAuthorized: c.auth_for_change_state = False + + +class DefaultOrganizationForm(DefaultGroupForm): + def group_form(self): + return 'organization/new_organization_form.html' + + def setup_template_variables(self, context, data_dict): + pass + + def new_template(self): + return 'organization/new.html' + + def about_template(self): + return 'organization/about.html' + + def index_template(self): + return 'organization/index.html' + + def admins_template(self): + return 'organization/admins.html' + + def bulk_process_template(self): + return 'organization/bulk_process.html' + + def read_template(self): + return 'organization/read.html' + + # don't override history_template - use group template for history + + def edit_template(self): + return 'organization/edit.html' + + def activity_template(self): + return 'organization/activity_stream.html' + +_default_organization_plugin = DefaultOrganizationForm() diff --git a/ckan/lib/uploader.py b/ckan/lib/uploader.py index e4d4575e218..8991f497993 100644 --- a/ckan/lib/uploader.py +++ b/ckan/lib/uploader.py @@ -3,28 +3,83 @@ import pylons import datetime import ckan.lib.munge as munge +import logging +import ckan.logic as logic + +log = logging.getLogger(__name__) + +_storage_path = None + + +def get_storage_path(): + '''Function to cache storage path''' + global _storage_path + + #None means it has not been set. False means not in config. + if _storage_path is None: + storage_path = pylons.config.get('ckan.storage_path') + ofs_impl = pylons.config.get('ofs.impl') + ofs_storage_dir = pylons.config.get('ofs.storage_dir') + if storage_path: + _storage_path = storage_path + elif ofs_impl == 'pairtree' and ofs_storage_dir: + log.warn('''Please use config option ckan.storage_path instaed of + ofs.storage_path''') + _storage_path = ofs_storage_dir + return _storage_path + elif ofs_impl: + log.critical('''We only support local file storage form version 2.2 + of ckan please specify ckan.storage_path in your + config for your uploads''') + _storage_path = False + else: + log.critical('''Please specify a ckan.storage_path in your config + for your uploads''') + _storage_path = False + + return _storage_path + class Upload(object): def __init__(self, object_type, old_filename=None): - path = pylons.config.get('ckan.storage_path') + ''' Setup upload by creating a subdirectory of the storage directory + of name object_type. old_filename is the name of the file in the url + field last time''' + + self.storage_path = None + self.filename = None + self.filepath = None + path = get_storage_path() if not path: return - self.storage_path = os.path.join(path, 'storage', 'uploads', object_type) + self.storage_path = os.path.join(path, 'storage', + 'uploads', object_type) try: os.makedirs(self.storage_path) except OSError, e: - pass + ## errno 17 is file already exists + if e.errno != 17: + raise self.object_type = object_type self.old_filename = old_filename if old_filename: self.old_filepath = os.path.join(self.storage_path, old_filename) - self.filename = None - self.filepath = None def update_data_dict(self, data_dict, url_field, file_field, clear_field): + ''' Manipulate data from the data_dict. url_field is the name of the + field where the upload is going to be. file_field is name of the key + where the FieldStorage is kept (i.e the field where the file data + actually is). clear_field is the name of a boolean field which + requests the upload to be deleted. This needs to be called before + it reaches any validators''' + self.url = data_dict.get(url_field, '') self.clear = data_dict.pop(clear_field, None) - self.upload_field_storage = data_dict.pop(file_field, None) + self.file_field = file_field + self.upload_field_storage = data_dict.pop(file_field, None) + + if not self.storage_path: + return if isinstance(self.upload_field_storage, cgi.FieldStorage): self.filename = self.upload_field_storage.filename @@ -41,22 +96,35 @@ def update_data_dict(self, data_dict, url_field, file_field, clear_field): if self.clear and self.url == self.old_filename: data_dict[url_field] = '' + def upload(self, max_size=2): + ''' Actually upload the file. + This should happen just before a commit but after the data has + been validated and flushed to the db. This is so we do not store + anything unless the request is actually good. + max_size is size in MB maximum of the file''' - def upload(self): if self.filename: output_file = open(self.tmp_filepath, 'wb') self.upload_file.seek(0) + current_size = 0 while True: - data = self.upload_file.read(2 ** 20) #mb chuncks + current_size = current_size + 1 + # MB chuncks + data = self.upload_file.read(2 ** 20) if not data: break output_file.write(data) + if current_size > max_size: + os.remove(self.tmp_filepath) + raise logic.ValidationError( + {self.file_field: ['File upload too large']} + ) output_file.close() os.rename(self.tmp_filepath, self.filepath) self.clear = True if (self.clear and self.old_filename - and not self.old_filename.startswith('http')): + and not self.old_filename.startswith('http')): try: os.remove(self.old_filepath) except OSError, e: diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py index 209b9a2264e..97f56b689fb 100644 --- a/ckan/logic/action/create.py +++ b/ckan/logic/action/create.py @@ -1,6 +1,8 @@ '''API functions for adding data to CKAN.''' import logging +import random +import re from pylons import config import paste.deploy.converters @@ -16,6 +18,8 @@ import ckan.lib.dictization.model_save as model_save import ckan.lib.navl.dictization_functions import ckan.lib.uploader as uploader +import ckan.lib.navl.validators as validators +import ckan.lib.mailer as mailer from ckan.common import _ @@ -87,7 +91,7 @@ def package_create(context, data_dict): :param extras: the dataset's extras (optional), extras are arbitrary (key: value) metadata items that can be added to datasets, each extra dictionary should have keys ``'key'`` (a string), ``'value'`` (a - string), and optionally ``'deleted'`` + string) :type extras: list of dataset extra dictionaries :param relationships_as_object: see ``package_relationship_create()`` for the format of relationship dictionaries (optional) @@ -855,6 +859,65 @@ def user_create(context, data_dict): log.debug('Created user {name}'.format(name=user.name)) return user_dict + +def user_invite(context, data_dict): + '''Invite a new user. + + You must be authorized to create group members. + + :param email: the email of the user to be invited to the group + :type email: string + :param group_id: the id or name of the group + :type group_id: string + :param role: role of the user in the group. One of ``member``, ``editor``, + or ``admin`` + :type role: string + + :returns: the newly created yser + :rtype: dictionary + ''' + _check_access('user_invite', context, data_dict) + + schema = context.get('schema', + ckan.logic.schema.default_user_invite_schema()) + data, errors = _validate(data_dict, schema, context) + if errors: + raise ValidationError(errors) + + name = _get_random_username_from_email(data['email']) + password = str(random.SystemRandom().random()) + data['name'] = name + data['password'] = password + data['state'] = ckan.model.State.PENDING + user_dict = _get_action('user_create')(context, data) + user = ckan.model.User.get(user_dict['id']) + member_dict = { + 'username': user.id, + 'id': data['group_id'], + 'role': data['role'] + } + _get_action('group_member_create')(context, member_dict) + mailer.send_invite(user) + return model_dictize.user_dictize(user, context) + + +def _get_random_username_from_email(email): + localpart = email.split('@')[0] + cleaned_localpart = re.sub(r'[^\w]', '-', localpart) + + # if we can't create a unique user name within this many attempts + # then something else is probably wrong and we should give up + max_name_creation_attempts = 100 + + for i in range(max_name_creation_attempts): + random_number = random.SystemRandom().random() * 10000 + name = '%s-%d' % (cleaned_localpart, random_number) + if not ckan.model.User.get(name): + return name + + return cleaned_localpart + + ## Modifications for rest api def package_create_rest(context, data_dict): diff --git a/ckan/logic/action/delete.py b/ckan/logic/action/delete.py index 60ea1a5bed8..9e3ee3244ab 100644 --- a/ckan/logic/action/delete.py +++ b/ckan/logic/action/delete.py @@ -18,6 +18,29 @@ _get_or_bust = ckan.logic.get_or_bust _get_action = ckan.logic.get_action + +def user_delete(context, data_dict): + '''Delete a user. + + Only sysadmins can delete users. + + :param id: the id or usernamename of the user to delete + :type id: string + ''' + + _check_access('user_delete', context, data_dict) + + model = context['model'] + user_id = _get_or_bust(data_dict, 'id') + user = model.User.get(user_id) + + if user is None: + raise NotFound('User "{id}" was not found.'.format(id=user_id)) + + user.delete() + model.repo.commit() + + def package_delete(context, data_dict): '''Delete a dataset (package). diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 4f4b2fb57ee..ca46ac2ff57 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -15,6 +15,7 @@ import ckan.logic.schema import ckan.lib.dictization.model_dictize as model_dictize import ckan.lib.navl.dictization_functions +import ckan.model as model import ckan.model.misc as misc import ckan.plugins as plugins import ckan.lib.search as search @@ -90,8 +91,11 @@ def package_list(context, data_dict): col = (package_revision_table.c.id if api == 2 else package_revision_table.c.name) query = _select([col]) - query = query.where(_and_(package_revision_table.c.state=='active', - package_revision_table.c.current==True)) + query = query.where(_and_( + package_revision_table.c.state=='active', + package_revision_table.c.current==True, + package_revision_table.c.private==False, + )) query = query.order_by(col) limit = data_dict.get('limit') @@ -688,6 +692,9 @@ def user_list(context, data_dict): else_=model.User.fullname) ) + # Filter deleted users + query = query.filter(model.User.state != model.State.DELETED) + ## hack for pagination if context.get('return_query'): return query @@ -1266,7 +1273,9 @@ def user_autocomplete(context, data_dict): q = data_dict['q'] limit = data_dict.get('limit', 20) - query = model.User.search(q).limit(limit) + query = model.User.search(q) + query = query.filter(model.User.state != model.State.DELETED) + query = query.limit(limit) user_list = [] for user in query.all(): @@ -1314,8 +1323,9 @@ def package_search(context, data_dict): :param facet.mincount: the minimum counts for facet fields should be included in the results. :type facet.mincount: int - :param facet.limit: the maximum number of constraint counts that should be - returned for the facet fields. A negative value means unlimited + :param facet.limit: the maximum number of values the facet fields return. + A negative value means unlimited. This can be set instance-wide with + the :ref:`search.facets.limit` config option. Default is 50. :type facet.limit: int :param facet.field: the fields to facet upon. Default empty. If empty, then the returned facet information is empty. @@ -1861,11 +1871,11 @@ def task_status_show(context, data_dict): context['task_status'] = task_status + _check_access('task_status_show', context, data_dict) + if task_status is None: raise NotFound - _check_access('task_status_show', context, data_dict) - task_status_dict = model_dictize.task_status_dictize(task_status, context) return task_status_dict diff --git a/ckan/logic/action/update.py b/ckan/logic/action/update.py index d2bd52fc7c0..75955870cea 100644 --- a/ckan/logic/action/update.py +++ b/ckan/logic/action/update.py @@ -133,7 +133,7 @@ def related_update(context, data_dict): id = _get_or_bust(data_dict, "id") session = context['session'] - schema = context.get('schema') or schema_.default_related_schema() + schema = context.get('schema') or schema_.default_update_related_schema() related = model.Related.get(id) context["related"] = related diff --git a/ckan/logic/auth/create.py b/ckan/logic/auth/create.py index a18485c2c57..c43d24fcd9b 100644 --- a/ckan/logic/auth/create.py +++ b/ckan/logic/auth/create.py @@ -23,7 +23,7 @@ def package_create(context, data_dict=None): # If an organization is given are we able to add a dataset to it? data_dict = data_dict or {} - org_id = data_dict.get('organization_id') + org_id = data_dict.get('owner_org') if org_id and not new_authz.has_user_permission_for_group_or_org( org_id, user, 'create_dataset'): return {'success': False, 'msg': _('User %s not authorized to add dataset to this organization') % user} @@ -103,16 +103,26 @@ def rating_create(context, data_dict): # No authz check in the logic function return {'success': True} + @logic.auth_allow_anonymous_access def user_create(context, data_dict=None): - user = context['user'] - - if ('api_version' in context - and not new_authz.check_config_permission('create_user_via_api')): - return {'success': False, 'msg': _('User %s not authorized to create users') % user} - else: - return {'success': True} + using_api = 'api_version' in context + create_user_via_api = new_authz.check_config_permission( + 'create_user_via_api') + create_user_via_web = new_authz.check_config_permission( + 'create_user_via_web') + + if using_api and not create_user_via_api: + return {'success': False, 'msg': _('User {user} not authorized to ' + 'create users via the API').format(user=context.get('user'))} + if not using_api and not create_user_via_web: + return {'success': False, 'msg': _('Not authorized to ' + 'create users')} + return {'success': True} +def user_invite(context, data_dict=None): + context['id'] = context.get('group_id') + return group_member_create(context, data_dict) def _check_group_auth(context, data_dict): # FIXME This code is shared amoung other logic.auth files and should be diff --git a/ckan/logic/auth/delete.py b/ckan/logic/auth/delete.py index 61b44697407..08974144a71 100644 --- a/ckan/logic/auth/delete.py +++ b/ckan/logic/auth/delete.py @@ -4,6 +4,12 @@ from ckan.logic.auth import get_resource_object from ckan.lib.base import _ + +def user_delete(context, data_dict): + # sysadmins only + return {'success': False} + + def package_delete(context, data_dict): user = context['user'] package = get_package_object(context, data_dict) diff --git a/ckan/logic/schema.py b/ckan/logic/schema.py index 90465b7f7ec..761119ee569 100644 --- a/ckan/logic/schema.py +++ b/ckan/logic/schema.py @@ -85,6 +85,7 @@ def default_resource_schema(): 'cache_last_updated': [ignore_missing, isodate], 'webstore_last_updated': [ignore_missing, isodate], 'tracking_summary': [ignore_missing], + 'datastore_active': [ignore], '__extras': [ignore_missing, extras_unicode_convert, keep_extras], } @@ -332,6 +333,15 @@ def default_related_schema(): return schema +def default_update_related_schema(): + schema = default_related_schema() + schema['id'] = [not_empty, unicode] + schema['title'] = [ignore_missing, unicode] + schema['type'] = [ignore_missing, unicode] + schema['owner_id'] = [ignore_missing, unicode] + return schema + + def default_extras_schema(): schema = { @@ -396,6 +406,7 @@ def default_user_schema(): 'apikey': [ignore], 'reset_key': [ignore], 'activity_streams_email_notifications': [ignore_missing], + 'state': [ignore_missing], } return schema @@ -424,6 +435,14 @@ def default_update_user_schema(): return schema +def default_user_invite_schema(): + schema = { + 'email': [not_empty, unicode], + 'group_id': [not_empty], + 'role': [not_empty], + } + return schema + def default_task_status_schema(): schema = { 'id': [ignore], @@ -540,7 +559,7 @@ def default_package_search_schema(): 'qf': [ignore_missing, unicode], 'facet': [ignore_missing, unicode], 'facet.mincount': [ignore_missing, natural_number_validator], - 'facet.limit': [ignore_missing, natural_number_validator], + 'facet.limit': [ignore_missing, int_validator], 'facet.field': [ignore_missing, list_of_strings], 'extras': [ignore_missing] # Not used by Solr, but useful for extensions } diff --git a/ckan/migration/versions/071_add_state_column_to_user_table.py b/ckan/migration/versions/071_add_state_column_to_user_table.py new file mode 100644 index 00000000000..828ebfb5f44 --- /dev/null +++ b/ckan/migration/versions/071_add_state_column_to_user_table.py @@ -0,0 +1,9 @@ +import ckan.model + + +def upgrade(migrate_engine): + migrate_engine.execute( + ''' + ALTER TABLE "user" ADD COLUMN "state" text NOT NULL DEFAULT '%s' + ''' % ckan.model.State.ACTIVE + ) diff --git a/ckan/model/follower.py b/ckan/model/follower.py index 6e6096ed5cc..eb54cd9ca67 100644 --- a/ckan/model/follower.py +++ b/ckan/model/follower.py @@ -1,30 +1,28 @@ -import sqlalchemy import meta import datetime +import sqlalchemy + +import core +import ckan.model import domain_object -class UserFollowingUser(domain_object.DomainObject): - '''A many-many relationship between users. - A relationship between one user (the follower) and another (the object), - that means that the follower is currently following the object. - - ''' +class ModelFollowingModel(domain_object.DomainObject): def __init__(self, follower_id, object_id): self.follower_id = follower_id self.object_id = object_id self.datetime = datetime.datetime.now() @classmethod - def get(self, follower_id, object_id): - '''Return a UserFollowingUser object for the given follower_id and + def get(cls, follower_id, object_id): + '''Return a ModelFollowingModel object for the given follower_id and object_id, or None if no such follower exists. ''' - query = meta.Session.query(UserFollowingUser) - query = query.filter(UserFollowingUser.follower_id==follower_id) - query = query.filter(UserFollowingUser.object_id==object_id) - return query.first() + query = cls._get(follower_id, object_id) + following = cls._filter_following_objects(query) + if len(following) == 1: + return following[0] @classmethod def is_following(cls, follower_id, object_id): @@ -32,33 +30,78 @@ def is_following(cls, follower_id, object_id): otherwise. ''' - return UserFollowingUser.get(follower_id, object_id) is not None - + return cls.get(follower_id, object_id) is not None @classmethod def followee_count(cls, follower_id): - '''Return the number of users followed by a user.''' - return meta.Session.query(UserFollowingUser).filter( - UserFollowingUser.follower_id == follower_id).count() + '''Return the number of objects followed by the follower.''' + return cls._get_followees(follower_id).count() @classmethod def followee_list(cls, follower_id): - '''Return a list of users followed by a user.''' - return meta.Session.query(UserFollowingUser).filter( - UserFollowingUser.follower_id == follower_id).all() + '''Return a list of objects followed by the follower.''' + query = cls._get_followees(follower_id).all() + followees = cls._filter_following_objects(query) + return followees + @classmethod + def follower_count(cls, object_id): + '''Return the number of followers of the object.''' + return cls._get_followers(object_id).count() @classmethod - def follower_count(cls, user_id): - '''Return the number of followers of a user.''' - return meta.Session.query(UserFollowingUser).filter( - UserFollowingUser.object_id == user_id).count() + def follower_list(cls, object_id): + '''Return a list of followers of the object.''' + query = cls._get_followers(object_id).all() + followers = cls._filter_following_objects(query) + return followers + + @classmethod + def _filter_following_objects(cls, query): + return [q[0] for q in query] + + @classmethod + def _get_followees(cls, follower_id): + return cls._get(follower_id) @classmethod - def follower_list(cls, user_id): - '''Return a list of followers of a user.''' - return meta.Session.query(UserFollowingUser).filter( - UserFollowingUser.object_id == user_id).all() + def _get_followers(cls, object_id): + return cls._get(None, object_id) + + @classmethod + def _get(cls, follower_id=None, object_id=None): + follower_alias = sqlalchemy.orm.aliased(cls._follower_class()) + object_alias = sqlalchemy.orm.aliased(cls._object_class()) + + follower_id = follower_id or cls.follower_id + object_id = object_id or cls.object_id + + query = meta.Session.query(cls, follower_alias, object_alias)\ + .filter(sqlalchemy.and_( + follower_alias.id == follower_id, + cls.follower_id == follower_alias.id, + cls.object_id == object_alias.id, + follower_alias.state != core.State.DELETED, + object_alias.state != core.State.DELETED, + object_alias.id == object_id)) + + return query + + +class UserFollowingUser(ModelFollowingModel): + '''A many-many relationship between users. + + A relationship between one user (the follower) and another (the object), + that means that the follower is currently following the object. + + ''' + @classmethod + def _follower_class(cls): + return ckan.model.User + + @classmethod + def _object_class(cls): + return ckan.model.User user_following_user_table = sqlalchemy.Table('user_following_user', @@ -76,62 +119,20 @@ def follower_list(cls, user_id): meta.mapper(UserFollowingUser, user_following_user_table) -class UserFollowingDataset(domain_object.DomainObject): +class UserFollowingDataset(ModelFollowingModel): '''A many-many relationship between users and datasets (packages). A relationship between a user (the follower) and a dataset (the object), that means that the user is currently following the dataset. ''' - def __init__(self, follower_id, object_id): - self.follower_id = follower_id - self.object_id = object_id - self.datetime = datetime.datetime.now() - @classmethod - def get(self, follower_id, object_id): - '''Return a UserFollowingDataset object for the given follower_id and - object_id, or None if no such follower exists. - - ''' - query = meta.Session.query(UserFollowingDataset) - query = query.filter(UserFollowingDataset.follower_id==follower_id) - query = query.filter(UserFollowingDataset.object_id==object_id) - return query.first() + def _follower_class(cls): + return ckan.model.User @classmethod - def is_following(cls, follower_id, object_id): - '''Return True if follower_id is currently following object_id, False - otherwise. - - ''' - return UserFollowingDataset.get(follower_id, object_id) is not None - - - @classmethod - def followee_count(cls, follower_id): - '''Return the number of datasets followed by a user.''' - return meta.Session.query(UserFollowingDataset).filter( - UserFollowingDataset.follower_id == follower_id).count() - - @classmethod - def followee_list(cls, follower_id): - '''Return a list of datasets followed by a user.''' - return meta.Session.query(UserFollowingDataset).filter( - UserFollowingDataset.follower_id == follower_id).all() - - - @classmethod - def follower_count(cls, dataset_id): - '''Return the number of followers of a dataset.''' - return meta.Session.query(UserFollowingDataset).filter( - UserFollowingDataset.object_id == dataset_id).count() - - @classmethod - def follower_list(cls, dataset_id): - '''Return a list of followers of a dataset.''' - return meta.Session.query(UserFollowingDataset).filter( - UserFollowingDataset.object_id == dataset_id).all() + def _object_class(cls): + return ckan.model.Package user_following_dataset_table = sqlalchemy.Table('user_following_dataset', @@ -150,60 +151,20 @@ def follower_list(cls, dataset_id): meta.mapper(UserFollowingDataset, user_following_dataset_table) -class UserFollowingGroup(domain_object.DomainObject): +class UserFollowingGroup(ModelFollowingModel): '''A many-many relationship between users and groups. A relationship between a user (the follower) and a group (the object), that means that the user is currently following the group. ''' - def __init__(self, follower_id, object_id): - self.follower_id = follower_id - self.object_id = object_id - self.datetime = datetime.datetime.now() - - @classmethod - def get(self, follower_id, object_id): - '''Return a UserFollowingGroup object for the given follower_id and - object_id, or None if no such relationship exists. - - ''' - query = meta.Session.query(UserFollowingGroup) - query = query.filter(UserFollowingGroup.follower_id == follower_id) - query = query.filter(UserFollowingGroup.object_id == object_id) - return query.first() - - @classmethod - def is_following(cls, follower_id, object_id): - '''Return True if follower_id is currently following object_id, False - otherwise. - - ''' - return UserFollowingGroup.get(follower_id, object_id) is not None - - @classmethod - def followee_count(cls, follower_id): - '''Return the number of groups followed by a user.''' - return meta.Session.query(UserFollowingGroup).filter( - UserFollowingGroup.follower_id == follower_id).count() - @classmethod - def followee_list(cls, follower_id): - '''Return a list of groups followed by a user.''' - return meta.Session.query(UserFollowingGroup).filter( - UserFollowingGroup.follower_id == follower_id).all() + def _follower_class(cls): + return ckan.model.User @classmethod - def follower_count(cls, object_id): - '''Return the number of users following a group.''' - return meta.Session.query(UserFollowingGroup).filter( - UserFollowingGroup.object_id == object_id).count() - - @classmethod - def follower_list(cls, object_id): - '''Return a list of the users following a group.''' - return meta.Session.query(UserFollowingGroup).filter( - UserFollowingGroup.object_id == object_id).all() + def _object_class(cls): + return ckan.model.Group user_following_group_table = sqlalchemy.Table('user_following_group', meta.metadata, diff --git a/ckan/model/group.py b/ckan/model/group.py index c16424affee..86963983e9a 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -195,8 +195,8 @@ def packages(self, with_private=False, limit=None, query = meta.Session.query(_package.Package).\ filter( - or_(_package.Package.state == vdm.sqlalchemy.State.ACTIVE, - _package.Package.state == vdm.sqlalchemy.State.PENDING)). \ + or_(_package.Package.state == core.State.ACTIVE, + _package.Package.state == core.State.PENDING)). \ filter(group_table.c.id == self.id).\ filter(member_table.c.state == 'active') diff --git a/ckan/model/meta.py b/ckan/model/meta.py index 795094965c5..2b6cb4581ba 100644 --- a/ckan/model/meta.py +++ b/ckan/model/meta.py @@ -162,5 +162,5 @@ def engine_is_sqlite(sa_engine=None): def engine_is_pg(sa_engine=None): # Returns true iff the engine is connected to a postgresql database. # According to http://docs.sqlalchemy.org/en/latest/core/engines.html#postgresql - # all Postgres driver names start with `postgresql` - return (sa_engine or engine).url.drivername.startswith('postgresql') + # all Postgres driver names start with `postgres` + return (sa_engine or engine).url.drivername.startswith('postgres') diff --git a/ckan/model/user.py b/ckan/model/user.py index d3eb3d6f1c1..da92a57c11a 100644 --- a/ckan/model/user.py +++ b/ckan/model/user.py @@ -6,8 +6,10 @@ from sqlalchemy.sql.expression import or_ from sqlalchemy.orm import synonym from sqlalchemy import types, Column, Table +import vdm.sqlalchemy import meta +import core import types as _types import domain_object @@ -28,8 +30,11 @@ Column('sysadmin', types.Boolean, default=False), ) +vdm.sqlalchemy.make_table_stateful(user_table) -class User(domain_object.DomainObject): + +class User(vdm.sqlalchemy.StatefulObjectMixin, + domain_object.DomainObject): VALID_NAME = re.compile(r"^[a-zA-Z0-9_\-]{3,255}$") DOUBLE_SLASH = re.compile(':\/([^/])') @@ -39,6 +44,10 @@ def by_openid(cls, openid): obj = meta.Session.query(cls).autoflush(False) return obj.filter_by(openid=openid).first() + @classmethod + def by_email(cls, email): + return meta.Session.query(cls).filter_by(email=email).all() + @classmethod def get(cls, user_reference): # double slashes in an openid often get turned into single slashes @@ -162,20 +171,35 @@ def number_administered_packages(self): q = q.filter_by(user=self, role=model.Role.ADMIN) return q.count() - def is_in_group(self, group): - return group in self.get_group_ids() + def activate(self): + ''' Activate the user ''' + self.state = core.State.ACTIVE + + def set_pending(self): + ''' Set the user as pending ''' + self.state = core.State.PENDING + + def is_deleted(self): + return self.state == core.State.DELETED + + def is_pending(self): + return self.state == core.State.PENDING + + def is_in_group(self, group_id): + return group_id in self.get_group_ids() - def is_in_groups(self, groupids): + def is_in_groups(self, group_ids): ''' Given a list of group ids, returns True if this user is in any of those groups ''' guser = set(self.get_group_ids()) - gids = set(groupids) + gids = set(group_ids) return len(guser.intersection(gids)) > 0 - def get_group_ids(self, group_type=None): + def get_group_ids(self, group_type=None, capacity=None): ''' Returns a list of group ids that the current user belongs to ''' - return [g.id for g in self.get_groups(group_type=group_type)] + return [g.id for g in + self.get_groups(group_type=group_type, capacity=capacity)] def get_groups(self, group_type=None, capacity=None): import ckan.model as model diff --git a/ckan/new_authz.py b/ckan/new_authz.py index abeb8f9b57a..70c8d1c1ab4 100644 --- a/ckan/new_authz.py +++ b/ckan/new_authz.py @@ -96,6 +96,7 @@ def _build(self): def clear_auth_functions_cache(): _AuthFunctions.clear() + CONFIG_PERMISSIONS.clear() def auth_functions_list(): @@ -113,23 +114,24 @@ def clean_action_name(action_name): def is_sysadmin(username): - ''' returns True is username is a sysadmin ''' + ''' Returns True is username is a sysadmin ''' + user = _get_user(username) + return user and user.sysadmin + + +def _get_user(username): + ''' Try to get the user from c, if possible, and fallback to using the DB ''' if not username: - return False - # see if we can authorise without touching the database + return None + # See if we can get the user without touching the DB try: if c.userobj and c.userobj.name == username: - if c.userobj.sysadmin: - return True - return False + return c.userobj except TypeError: # c is not available pass - # get user from the database - user = model.User.get(username) - if user and user.sysadmin: - return True - return False + # Get user from the DB + return model.User.get(username) def get_group_or_org_admin_ids(group_id): @@ -158,12 +160,19 @@ def is_authorized(action, context, data_dict=None): action = clean_action_name(action) auth_function = _AuthFunctions.get(action) if auth_function: - # sysadmins can do anything unless the auth_sysadmins_check - # decorator was used in which case they are treated like all other - # users. - if is_sysadmin(context.get('user')): - if not getattr(auth_function, 'auth_sysadmins_check', False): - return {'success': True} + username = context.get('user') + user = _get_user(username) + + if user: + # deleted users are always unauthorized + if user.is_deleted(): + return {'success': False} + # sysadmins can do anything unless the auth_sysadmins_check + # decorator was used in which case they are treated like all other + # users. + elif user.sysadmin: + if not getattr(auth_function, 'auth_sysadmins_check', False): + return {'success': True} # If the auth function is flagged as not allowing anonymous access, # and an existing user object is not provided in the context, deny @@ -235,7 +244,10 @@ def has_user_permission_for_group_or_org(group_id, user_name, permission): ''' Check if the user has the given permission for the group ''' if not group_id: return False - group_id = model.Group.get(group_id).id + group = model.Group.get(group_id) + if not group: + return False + group_id = group.id # Sys admins can do anything if is_sysadmin(user_name): @@ -339,6 +351,7 @@ def get_user_id_for_username(user_name, allow_none=False): 'user_delete_groups': True, 'user_delete_organizations': True, 'create_user_via_api': False, + 'create_user_via_web': True, } CONFIG_PERMISSIONS = {} diff --git a/ckan/plugins/core.py b/ckan/plugins/core.py index 8f3752117ea..f39c8062917 100644 --- a/ckan/plugins/core.py +++ b/ckan/plugins/core.py @@ -18,7 +18,7 @@ 'PluginNotFoundException', 'Plugin', 'SingletonPlugin', 'load', 'load_all', 'unload', 'unload_all', 'get_plugin', 'plugins_update', - 'use_plugin', + 'use_plugin', 'plugin_loaded', ] log = logging.getLogger(__name__) @@ -210,6 +210,15 @@ def unload(*plugins): plugins_update() +def plugin_loaded(name): + ''' + See if a particular plugin is loaded. + ''' + if name in _PLUGINS: + return True + return False + + def find_system_plugins(): ''' Return all plugins in the ckan.system_plugins entry point group. diff --git a/ckan/plugins/interfaces.py b/ckan/plugins/interfaces.py index 74462835474..14d64b01146 100644 --- a/ckan/plugins/interfaces.py +++ b/ckan/plugins/interfaces.py @@ -443,7 +443,6 @@ def before_view(self, pkg_dict): class IResourceController(Interface): """ Hook into the resource controller. - (see IGroupController) """ def before_show(self, resource_dict): @@ -769,6 +768,15 @@ def read_template(self): The path should be relative to the plugin's templates dir, e.g. ``'package/read.html'``. + If the user requests the dataset in a format other than HTML + (CKAN supports returning datasets in RDF or N3 format by appending .rdf + or .n3 to the dataset read URL, see :doc:`linked-data-and-rdf`) then + CKAN will try to render + a template file with the same path as returned by this function, + but a different filename extension, e.g. ``'package/read.rdf'``. + If your extension doesn't have this RDF version of the template + file, the user will get a 404 error. + :rtype: string ''' diff --git a/ckan/plugins/toolkit.py b/ckan/plugins/toolkit.py index 4e4ac5da2d0..34e9d671cf1 100644 --- a/ckan/plugins/toolkit.py +++ b/ckan/plugins/toolkit.py @@ -348,7 +348,7 @@ def _requires_ckan_version(cls, min_version, max_version=None): else: error = 'Requires ckan version between %s and %s' % \ (min_version, max_version) - raise cls.CkanVersionException(error) + raise CkanVersionException(error) def __getattr__(self, name): ''' return the function/object requested ''' diff --git a/ckan/public/base/css/fuchsia.css b/ckan/public/base/css/fuchsia.css index 300d2981817..c87cd3b10a1 100644 --- a/ckan/public/base/css/fuchsia.css +++ b/ckan/public/base/css/fuchsia.css @@ -2055,6 +2055,14 @@ table th[class*="span"], .open > .dropdown-menu { display: block; } +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} .pull-right > .dropdown-menu { right: 0; left: auto; @@ -4890,16 +4898,6 @@ a.tag:hover { .module-heading:after { clear: both; } -.module-heading .action { - float: right; - color: #888888; - font-size: 12px; - line-height: 20px; - text-decoration: underline; -} -.module-heading .action:hover { - color: #444444; -} .module-content { padding: 0 25px; margin: 20px 0; @@ -5572,6 +5570,12 @@ textarea { .control-large input { height: 41px; } +.control-required { + color: #c6898b; +} +.form-actions .control-required-message { + float: left; +} .form-actions { background: none; margin-left: -25px; @@ -6116,6 +6120,34 @@ textarea { -moz-box-shadow: none; box-shadow: none; } +.add-member-form .control-label { + width: 100%; + text-align: left; +} +.add-member-form .controls { + margin-left: auto; +} +.add-member-or { + float: left; + margin-top: 75px; + width: 7%; + text-align: center; + text-transform: uppercase; + color: #999999; + font-weight: bold; +} +.add-member-form .row-fluid .control-group { + float: left; + width: 45%; +} +.add-member-form .row-fluid .select2-container, +.add-member-form .row-fluid input { + width: 100% !important; +} +#recaptcha_table { + table-layout: inherit; + line-height: 1; +} .dataset-item { border-bottom: 1px dotted #dddddd; padding-bottom: 20px; @@ -6904,842 +6936,6 @@ h4 small { height: 35px; background-position: -320px -62px; } -/* This is a modified version of the font-awesome core less document. */ -@font-face { - font-family: 'FontAwesome'; - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?v=3.0.1'); - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.woff?v=3.0.1') format('woff'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); - font-weight: normal; - font-style: normal; -} -[class^="icon-"], -[class*=" icon-"] { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - display: inline; - width: auto; - height: auto; - line-height: normal; - vertical-align: baseline; - background-image: none; - background-position: 0% 0%; - background-repeat: repeat; - margin-top: 0; -} -.icon-spin { - display: inline-block; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - } -} -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - } -} -@-o-keyframes spin { - 0% { - -o-transform: rotate(0deg); - } - 100% { - -o-transform: rotate(359deg); - } -} -@-ms-keyframes spin { - 0% { - -ms-transform: rotate(0deg); - } - 100% { - -ms-transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(359deg); - } -} -@-moz-document url-prefix() { - .icon-spin { - height: .9em; - } - .btn .icon-spin { - height: auto; - } - .icon-spin.icon-large { - height: 1.25em; - } - .btn .icon-spin.icon-large { - height: .75em; - } -} -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen - readers do not read off random characters that represent icons */ -.icon-glass:before { - content: "\f000"; -} -.icon-music:before { - content: "\f001"; -} -.icon-search:before { - content: "\f002"; -} -.icon-envelope:before { - content: "\f003"; -} -.icon-heart:before { - content: "\f004"; -} -.icon-star:before { - content: "\f005"; -} -.icon-star-empty:before { - content: "\f006"; -} -.icon-user:before { - content: "\f007"; -} -.icon-film:before { - content: "\f008"; -} -.icon-th-large:before { - content: "\f009"; -} -.icon-th:before { - content: "\f00a"; -} -.icon-th-list:before { - content: "\f00b"; -} -.icon-ok:before { - content: "\f00c"; -} -.icon-remove:before { - content: "\f00d"; -} -.icon-zoom-in:before { - content: "\f00e"; -} -.icon-zoom-out:before { - content: "\f010"; -} -.icon-off:before { - content: "\f011"; -} -.icon-signal:before { - content: "\f012"; -} -.icon-cog:before { - content: "\f013"; -} -.icon-trash:before { - content: "\f014"; -} -.icon-home:before { - content: "\f015"; -} -.icon-file:before { - content: "\f016"; -} -.icon-time:before { - content: "\f017"; -} -.icon-road:before { - content: "\f018"; -} -.icon-download-alt:before { - content: "\f019"; -} -.icon-download:before { - content: "\f01a"; -} -.icon-upload:before { - content: "\f01b"; -} -.icon-inbox:before { - content: "\f01c"; -} -.icon-play-circle:before { - content: "\f01d"; -} -.icon-repeat:before { - content: "\f01e"; -} -/* \f020 doesn't work in Safari. all shifted one down */ -.icon-refresh:before { - content: "\f021"; -} -.icon-list-alt:before { - content: "\f022"; -} -.icon-lock:before { - content: "\f023"; -} -.icon-flag:before { - content: "\f024"; -} -.icon-headphones:before { - content: "\f025"; -} -.icon-volume-off:before { - content: "\f026"; -} -.icon-volume-down:before { - content: "\f027"; -} -.icon-volume-up:before { - content: "\f028"; -} -.icon-qrcode:before { - content: "\f029"; -} -.icon-barcode:before { - content: "\f02a"; -} -.icon-tag:before { - content: "\f02b"; -} -.icon-tags:before { - content: "\f02c"; -} -.icon-book:before { - content: "\f02d"; -} -.icon-bookmark:before { - content: "\f02e"; -} -.icon-print:before { - content: "\f02f"; -} -.icon-camera:before { - content: "\f030"; -} -.icon-font:before { - content: "\f031"; -} -.icon-bold:before { - content: "\f032"; -} -.icon-italic:before { - content: "\f033"; -} -.icon-text-height:before { - content: "\f034"; -} -.icon-text-width:before { - content: "\f035"; -} -.icon-align-left:before { - content: "\f036"; -} -.icon-align-center:before { - content: "\f037"; -} -.icon-align-right:before { - content: "\f038"; -} -.icon-align-justify:before { - content: "\f039"; -} -.icon-list:before { - content: "\f03a"; -} -.icon-indent-left:before { - content: "\f03b"; -} -.icon-indent-right:before { - content: "\f03c"; -} -.icon-facetime-video:before { - content: "\f03d"; -} -.icon-picture:before { - content: "\f03e"; -} -.icon-pencil:before { - content: "\f040"; -} -.icon-map-marker:before { - content: "\f041"; -} -.icon-adjust:before { - content: "\f042"; -} -.icon-tint:before { - content: "\f043"; -} -.icon-edit:before { - content: "\f044"; -} -.icon-share:before { - content: "\f045"; -} -.icon-check:before { - content: "\f046"; -} -.icon-move:before { - content: "\f047"; -} -.icon-step-backward:before { - content: "\f048"; -} -.icon-fast-backward:before { - content: "\f049"; -} -.icon-backward:before { - content: "\f04a"; -} -.icon-play:before { - content: "\f04b"; -} -.icon-pause:before { - content: "\f04c"; -} -.icon-stop:before { - content: "\f04d"; -} -.icon-forward:before { - content: "\f04e"; -} -.icon-fast-forward:before { - content: "\f050"; -} -.icon-step-forward:before { - content: "\f051"; -} -.icon-eject:before { - content: "\f052"; -} -.icon-chevron-left:before { - content: "\f053"; -} -.icon-chevron-right:before { - content: "\f054"; -} -.icon-plus-sign:before { - content: "\f055"; -} -.icon-minus-sign:before { - content: "\f056"; -} -.icon-remove-sign:before { - content: "\f057"; -} -.icon-ok-sign:before { - content: "\f058"; -} -.icon-question-sign:before { - content: "\f059"; -} -.icon-info-sign:before { - content: "\f05a"; -} -.icon-screenshot:before { - content: "\f05b"; -} -.icon-remove-circle:before { - content: "\f05c"; -} -.icon-ok-circle:before { - content: "\f05d"; -} -.icon-ban-circle:before { - content: "\f05e"; -} -.icon-arrow-left:before { - content: "\f060"; -} -.icon-arrow-right:before { - content: "\f061"; -} -.icon-arrow-up:before { - content: "\f062"; -} -.icon-arrow-down:before { - content: "\f063"; -} -.icon-share-alt:before { - content: "\f064"; -} -.icon-resize-full:before { - content: "\f065"; -} -.icon-resize-small:before { - content: "\f066"; -} -.icon-plus:before { - content: "\f067"; -} -.icon-minus:before { - content: "\f068"; -} -.icon-asterisk:before { - content: "\f069"; -} -.icon-exclamation-sign:before { - content: "\f06a"; -} -.icon-gift:before { - content: "\f06b"; -} -.icon-leaf:before { - content: "\f06c"; -} -.icon-fire:before { - content: "\f06d"; -} -.icon-eye-open:before { - content: "\f06e"; -} -.icon-eye-close:before { - content: "\f070"; -} -.icon-warning-sign:before { - content: "\f071"; -} -.icon-plane:before { - content: "\f072"; -} -.icon-calendar:before { - content: "\f073"; -} -.icon-random:before { - content: "\f074"; -} -.icon-comment:before { - content: "\f075"; -} -.icon-magnet:before { - content: "\f076"; -} -.icon-chevron-up:before { - content: "\f077"; -} -.icon-chevron-down:before { - content: "\f078"; -} -.icon-retweet:before { - content: "\f079"; -} -.icon-shopping-cart:before { - content: "\f07a"; -} -.icon-folder-close:before { - content: "\f07b"; -} -.icon-folder-open:before { - content: "\f07c"; -} -.icon-resize-vertical:before { - content: "\f07d"; -} -.icon-resize-horizontal:before { - content: "\f07e"; -} -.icon-bar-chart:before { - content: "\f080"; -} -.icon-twitter-sign:before { - content: "\f081"; -} -.icon-facebook-sign:before { - content: "\f082"; -} -.icon-camera-retro:before { - content: "\f083"; -} -.icon-key:before { - content: "\f084"; -} -.icon-cogs:before { - content: "\f085"; -} -.icon-comments:before { - content: "\f086"; -} -.icon-thumbs-up:before { - content: "\f087"; -} -.icon-thumbs-down:before { - content: "\f088"; -} -.icon-star-half:before { - content: "\f089"; -} -.icon-heart-empty:before { - content: "\f08a"; -} -.icon-signout:before { - content: "\f08b"; -} -.icon-linkedin-sign:before { - content: "\f08c"; -} -.icon-pushpin:before { - content: "\f08d"; -} -.icon-external-link:before { - content: "\f08e"; -} -.icon-signin:before { - content: "\f090"; -} -.icon-trophy:before { - content: "\f091"; -} -.icon-github-sign:before { - content: "\f092"; -} -.icon-upload-alt:before { - content: "\f093"; -} -.icon-lemon:before { - content: "\f094"; -} -.icon-phone:before { - content: "\f095"; -} -.icon-check-empty:before { - content: "\f096"; -} -.icon-bookmark-empty:before { - content: "\f097"; -} -.icon-phone-sign:before { - content: "\f098"; -} -.icon-twitter:before { - content: "\f099"; -} -.icon-facebook:before { - content: "\f09a"; -} -.icon-github:before { - content: "\f09b"; -} -.icon-unlock:before { - content: "\f09c"; -} -.icon-credit-card:before { - content: "\f09d"; -} -.icon-rss:before { - content: "\f09e"; -} -.icon-hdd:before { - content: "\f0a0"; -} -.icon-bullhorn:before { - content: "\f0a1"; -} -.icon-bell:before { - content: "\f0a2"; -} -.icon-certificate:before { - content: "\f0a3"; -} -.icon-hand-right:before { - content: "\f0a4"; -} -.icon-hand-left:before { - content: "\f0a5"; -} -.icon-hand-up:before { - content: "\f0a6"; -} -.icon-hand-down:before { - content: "\f0a7"; -} -.icon-circle-arrow-left:before { - content: "\f0a8"; -} -.icon-circle-arrow-right:before { - content: "\f0a9"; -} -.icon-circle-arrow-up:before { - content: "\f0aa"; -} -.icon-circle-arrow-down:before { - content: "\f0ab"; -} -.icon-globe:before { - content: "\f0ac"; -} -.icon-wrench:before { - content: "\f0ad"; -} -.icon-tasks:before { - content: "\f0ae"; -} -.icon-filter:before { - content: "\f0b0"; -} -.icon-briefcase:before { - content: "\f0b1"; -} -.icon-fullscreen:before { - content: "\f0b2"; -} -.icon-group:before { - content: "\f0c0"; -} -.icon-link:before { - content: "\f0c1"; -} -.icon-cloud:before { - content: "\f0c2"; -} -.icon-beaker:before { - content: "\f0c3"; -} -.icon-cut:before { - content: "\f0c4"; -} -.icon-copy:before { - content: "\f0c5"; -} -.icon-paper-clip:before { - content: "\f0c6"; -} -.icon-save:before { - content: "\f0c7"; -} -.icon-sign-blank:before { - content: "\f0c8"; -} -.icon-reorder:before { - content: "\f0c9"; -} -.icon-list-ul:before { - content: "\f0ca"; -} -.icon-list-ol:before { - content: "\f0cb"; -} -.icon-strikethrough:before { - content: "\f0cc"; -} -.icon-underline:before { - content: "\f0cd"; -} -.icon-table:before { - content: "\f0ce"; -} -.icon-magic:before { - content: "\f0d0"; -} -.icon-truck:before { - content: "\f0d1"; -} -.icon-pinterest:before { - content: "\f0d2"; -} -.icon-pinterest-sign:before { - content: "\f0d3"; -} -.icon-google-plus-sign:before { - content: "\f0d4"; -} -.icon-google-plus:before { - content: "\f0d5"; -} -.icon-money:before { - content: "\f0d6"; -} -.icon-caret-down:before { - content: "\f0d7"; -} -.icon-caret-up:before { - content: "\f0d8"; -} -.icon-caret-left:before { - content: "\f0d9"; -} -.icon-caret-right:before { - content: "\f0da"; -} -.icon-columns:before { - content: "\f0db"; -} -.icon-sort:before { - content: "\f0dc"; -} -.icon-sort-down:before { - content: "\f0dd"; -} -.icon-sort-up:before { - content: "\f0de"; -} -.icon-envelope-alt:before { - content: "\f0e0"; -} -.icon-linkedin:before { - content: "\f0e1"; -} -.icon-undo:before { - content: "\f0e2"; -} -.icon-legal:before { - content: "\f0e3"; -} -.icon-dashboard:before { - content: "\f0e4"; -} -.icon-comment-alt:before { - content: "\f0e5"; -} -.icon-comments-alt:before { - content: "\f0e6"; -} -.icon-bolt:before { - content: "\f0e7"; -} -.icon-sitemap:before { - content: "\f0e8"; -} -.icon-umbrella:before { - content: "\f0e9"; -} -.icon-paste:before { - content: "\f0ea"; -} -.icon-lightbulb:before { - content: "\f0eb"; -} -.icon-exchange:before { - content: "\f0ec"; -} -.icon-cloud-download:before { - content: "\f0ed"; -} -.icon-cloud-upload:before { - content: "\f0ee"; -} -.icon-user-md:before { - content: "\f0f0"; -} -.icon-stethoscope:before { - content: "\f0f1"; -} -.icon-suitcase:before { - content: "\f0f2"; -} -.icon-bell-alt:before { - content: "\f0f3"; -} -.icon-coffee:before { - content: "\f0f4"; -} -.icon-food:before { - content: "\f0f5"; -} -.icon-file-alt:before { - content: "\f0f6"; -} -.icon-building:before { - content: "\f0f7"; -} -.icon-hospital:before { - content: "\f0f8"; -} -.icon-ambulance:before { - content: "\f0f9"; -} -.icon-medkit:before { - content: "\f0fa"; -} -.icon-fighter-jet:before { - content: "\f0fb"; -} -.icon-beer:before { - content: "\f0fc"; -} -.icon-h-sign:before { - content: "\f0fd"; -} -.icon-plus-sign-alt:before { - content: "\f0fe"; -} -.icon-double-angle-left:before { - content: "\f100"; -} -.icon-double-angle-right:before { - content: "\f101"; -} -.icon-double-angle-up:before { - content: "\f102"; -} -.icon-double-angle-down:before { - content: "\f103"; -} -.icon-angle-left:before { - content: "\f104"; -} -.icon-angle-right:before { - content: "\f105"; -} -.icon-angle-up:before { - content: "\f106"; -} -.icon-angle-down:before { - content: "\f107"; -} -.icon-desktop:before { - content: "\f108"; -} -.icon-laptop:before { - content: "\f109"; -} -.icon-tablet:before { - content: "\f10a"; -} -.icon-mobile-phone:before { - content: "\f10b"; -} -.icon-circle-blank:before { - content: "\f10c"; -} -.icon-quote-left:before { - content: "\f10d"; -} -.icon-quote-right:before { - content: "\f10e"; -} -.icon-spinner:before { - content: "\f110"; -} -.icon-circle:before { - content: "\f111"; -} -.icon-reply:before { - content: "\f112"; -} -.icon-github-alt:before { - content: "\f113"; -} -.icon-folder-close-alt:before { - content: "\f114"; -} -.icon-folder-open-alt:before { - content: "\f115"; -} [class^="icon-"], [class*=" icon-"] { display: inline-block; @@ -7963,45 +7159,39 @@ h4 small { .context-info.editing .module-content { margin-top: 0; } -.hero { - background: url("../../../base/images/background-tile.png"); +.homepage [role=main] { padding: 20px 0; min-height: 0; } -.hero > .container { +.homepage .row { position: relative; - padding-bottom: 0; } -.hero .search-giant { - margin-bottom: 10px; -} -.hero .search-giant input { - border-color: #003f52; -} -.hero .page-heading { - font-size: 18px; - margin-bottom: 0; -} -.hero .module-dark { +.homepage .module-search { padding: 5px; - margin-bottom: 0; + margin: 0; color: #ffffff; background: #ffffff; } -.hero .module-dark .module-content { +.homepage .module-search .search-giant { + margin-bottom: 10px; +} +.homepage .module-search .search-giant input { + border-color: #003f52; +} +.homepage .module-search .module-content { -webkit-border-radius: 3px 3px 0 0; -moz-border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0; background-color: #e73892; border-bottom: none; } -.hero .module-dark .module-content .heading { +.homepage .module-search .module-content .heading { margin-top: 0; margin-bottom: 7px; font-size: 24px; line-height: 40px; } -.hero .tags { +.homepage .module-search .tags { *zoom: 1; padding: 5px 10px 10px 10px; background-color: #d31979; @@ -8009,69 +7199,77 @@ h4 small { -moz-border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px; } -.hero .tags:before, -.hero .tags:after { +.homepage .module-search .tags:before, +.homepage .module-search .tags:after { display: table; content: ""; line-height: 0; } -.hero .tags:after { +.homepage .module-search .tags:after { clear: both; } -.hero .tags h3, -.hero .tags .tag { +.homepage .module-search .tags h3, +.homepage .module-search .tags .tag { display: block; float: left; margin: 5px 10px 0 0; } -.hero .tags h3 { +.homepage .module-search .tags h3 { font-size: 14px; line-height: 20px; padding: 2px 8px; } -.hero-primary, -.hero-secondary { +.homepage .group-list { + margin: 0; +} +.homepage .box .inner { + padding: 20px 25px; +} +.homepage .stats h3 { + margin: 0 0 10px 0; +} +.homepage .stats ul { + margin: 0; + list-style: none; + *zoom: 1; +} +.homepage .stats ul:before, +.homepage .stats ul:after { + display: table; + content: ""; + line-height: 0; +} +.homepage .stats ul:after { + clear: both; +} +.homepage .stats ul li { float: left; - margin-left: 20px; - width: 460px; + width: 25%; + font-weight: 300; } -.hero-primary { - margin-left: 0; - margin-bottom: 0; +.homepage .stats ul li a { + display: block; +} +.homepage .stats ul li a b { + display: block; + font-size: 35px; + line-height: 1.5; } -.hero-secondary { +.homepage .stats ul li a:hover { + text-decoration: none; +} +.homepage.layout-1 .row1 .col2 { position: absolute; bottom: 0; right: 0; } -.hero-secondary .hero-secondary-inner { +.homepage.layout-1 .row1 .col2 .module-search { bottom: 0; left: 0; right: 0; } -.main.homepage { - padding-top: 20px; - padding-bottom: 20px; - border-top: 1px solid #cccccc; -} -.main.homepage .module-heading .media-image { - margin-right: 15px; - max-height: 53px; - -webkit-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - -moz-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); -} -.main.homepage .group-listing .box { - min-height: 275px; -} -.main.homepage .group-list { - margin-bottom: 0; -} -.main.homepage .group-list .dataset-content { - min-height: 70px; -} -.main.homepage .box .module { - margin-top: 0; +.homepage.layout-2 .stats { + margin-top: 20px; } .account-masthead { *zoom: 1; @@ -8920,6 +8118,11 @@ iframe { padding-bottom: 20px !important; margin-bottom: 0 !important; } +.ie8 .lang-dropdown, +.ie7 .lang-dropdown { + position: relative !important; + top: -90px !important; +} .ie7 .alert { position: relative; } @@ -9061,11 +8264,6 @@ iframe { .ie7 .module-heading .media-content { position: relative; } -.ie7 .module-heading .action { - position: absolute; - top: 9px; - right: 10px; -} .ie7 .module-heading .media-image img { float: left; } diff --git a/ckan/public/base/css/green.css b/ckan/public/base/css/green.css index 1cc58fa82dc..6558ce82f8d 100644 --- a/ckan/public/base/css/green.css +++ b/ckan/public/base/css/green.css @@ -2055,6 +2055,14 @@ table th[class*="span"], .open > .dropdown-menu { display: block; } +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} .pull-right > .dropdown-menu { right: 0; left: auto; @@ -4890,16 +4898,6 @@ a.tag:hover { .module-heading:after { clear: both; } -.module-heading .action { - float: right; - color: #888888; - font-size: 12px; - line-height: 20px; - text-decoration: underline; -} -.module-heading .action:hover { - color: #444444; -} .module-content { padding: 0 25px; margin: 20px 0; @@ -5572,6 +5570,12 @@ textarea { .control-large input { height: 41px; } +.control-required { + color: #c6898b; +} +.form-actions .control-required-message { + float: left; +} .form-actions { background: none; margin-left: -25px; @@ -6116,6 +6120,34 @@ textarea { -moz-box-shadow: none; box-shadow: none; } +.add-member-form .control-label { + width: 100%; + text-align: left; +} +.add-member-form .controls { + margin-left: auto; +} +.add-member-or { + float: left; + margin-top: 75px; + width: 7%; + text-align: center; + text-transform: uppercase; + color: #999999; + font-weight: bold; +} +.add-member-form .row-fluid .control-group { + float: left; + width: 45%; +} +.add-member-form .row-fluid .select2-container, +.add-member-form .row-fluid input { + width: 100% !important; +} +#recaptcha_table { + table-layout: inherit; + line-height: 1; +} .dataset-item { border-bottom: 1px dotted #dddddd; padding-bottom: 20px; @@ -6904,842 +6936,6 @@ h4 small { height: 35px; background-position: -320px -62px; } -/* This is a modified version of the font-awesome core less document. */ -@font-face { - font-family: 'FontAwesome'; - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?v=3.0.1'); - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.woff?v=3.0.1') format('woff'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); - font-weight: normal; - font-style: normal; -} -[class^="icon-"], -[class*=" icon-"] { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - display: inline; - width: auto; - height: auto; - line-height: normal; - vertical-align: baseline; - background-image: none; - background-position: 0% 0%; - background-repeat: repeat; - margin-top: 0; -} -.icon-spin { - display: inline-block; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - } -} -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - } -} -@-o-keyframes spin { - 0% { - -o-transform: rotate(0deg); - } - 100% { - -o-transform: rotate(359deg); - } -} -@-ms-keyframes spin { - 0% { - -ms-transform: rotate(0deg); - } - 100% { - -ms-transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(359deg); - } -} -@-moz-document url-prefix() { - .icon-spin { - height: .9em; - } - .btn .icon-spin { - height: auto; - } - .icon-spin.icon-large { - height: 1.25em; - } - .btn .icon-spin.icon-large { - height: .75em; - } -} -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen - readers do not read off random characters that represent icons */ -.icon-glass:before { - content: "\f000"; -} -.icon-music:before { - content: "\f001"; -} -.icon-search:before { - content: "\f002"; -} -.icon-envelope:before { - content: "\f003"; -} -.icon-heart:before { - content: "\f004"; -} -.icon-star:before { - content: "\f005"; -} -.icon-star-empty:before { - content: "\f006"; -} -.icon-user:before { - content: "\f007"; -} -.icon-film:before { - content: "\f008"; -} -.icon-th-large:before { - content: "\f009"; -} -.icon-th:before { - content: "\f00a"; -} -.icon-th-list:before { - content: "\f00b"; -} -.icon-ok:before { - content: "\f00c"; -} -.icon-remove:before { - content: "\f00d"; -} -.icon-zoom-in:before { - content: "\f00e"; -} -.icon-zoom-out:before { - content: "\f010"; -} -.icon-off:before { - content: "\f011"; -} -.icon-signal:before { - content: "\f012"; -} -.icon-cog:before { - content: "\f013"; -} -.icon-trash:before { - content: "\f014"; -} -.icon-home:before { - content: "\f015"; -} -.icon-file:before { - content: "\f016"; -} -.icon-time:before { - content: "\f017"; -} -.icon-road:before { - content: "\f018"; -} -.icon-download-alt:before { - content: "\f019"; -} -.icon-download:before { - content: "\f01a"; -} -.icon-upload:before { - content: "\f01b"; -} -.icon-inbox:before { - content: "\f01c"; -} -.icon-play-circle:before { - content: "\f01d"; -} -.icon-repeat:before { - content: "\f01e"; -} -/* \f020 doesn't work in Safari. all shifted one down */ -.icon-refresh:before { - content: "\f021"; -} -.icon-list-alt:before { - content: "\f022"; -} -.icon-lock:before { - content: "\f023"; -} -.icon-flag:before { - content: "\f024"; -} -.icon-headphones:before { - content: "\f025"; -} -.icon-volume-off:before { - content: "\f026"; -} -.icon-volume-down:before { - content: "\f027"; -} -.icon-volume-up:before { - content: "\f028"; -} -.icon-qrcode:before { - content: "\f029"; -} -.icon-barcode:before { - content: "\f02a"; -} -.icon-tag:before { - content: "\f02b"; -} -.icon-tags:before { - content: "\f02c"; -} -.icon-book:before { - content: "\f02d"; -} -.icon-bookmark:before { - content: "\f02e"; -} -.icon-print:before { - content: "\f02f"; -} -.icon-camera:before { - content: "\f030"; -} -.icon-font:before { - content: "\f031"; -} -.icon-bold:before { - content: "\f032"; -} -.icon-italic:before { - content: "\f033"; -} -.icon-text-height:before { - content: "\f034"; -} -.icon-text-width:before { - content: "\f035"; -} -.icon-align-left:before { - content: "\f036"; -} -.icon-align-center:before { - content: "\f037"; -} -.icon-align-right:before { - content: "\f038"; -} -.icon-align-justify:before { - content: "\f039"; -} -.icon-list:before { - content: "\f03a"; -} -.icon-indent-left:before { - content: "\f03b"; -} -.icon-indent-right:before { - content: "\f03c"; -} -.icon-facetime-video:before { - content: "\f03d"; -} -.icon-picture:before { - content: "\f03e"; -} -.icon-pencil:before { - content: "\f040"; -} -.icon-map-marker:before { - content: "\f041"; -} -.icon-adjust:before { - content: "\f042"; -} -.icon-tint:before { - content: "\f043"; -} -.icon-edit:before { - content: "\f044"; -} -.icon-share:before { - content: "\f045"; -} -.icon-check:before { - content: "\f046"; -} -.icon-move:before { - content: "\f047"; -} -.icon-step-backward:before { - content: "\f048"; -} -.icon-fast-backward:before { - content: "\f049"; -} -.icon-backward:before { - content: "\f04a"; -} -.icon-play:before { - content: "\f04b"; -} -.icon-pause:before { - content: "\f04c"; -} -.icon-stop:before { - content: "\f04d"; -} -.icon-forward:before { - content: "\f04e"; -} -.icon-fast-forward:before { - content: "\f050"; -} -.icon-step-forward:before { - content: "\f051"; -} -.icon-eject:before { - content: "\f052"; -} -.icon-chevron-left:before { - content: "\f053"; -} -.icon-chevron-right:before { - content: "\f054"; -} -.icon-plus-sign:before { - content: "\f055"; -} -.icon-minus-sign:before { - content: "\f056"; -} -.icon-remove-sign:before { - content: "\f057"; -} -.icon-ok-sign:before { - content: "\f058"; -} -.icon-question-sign:before { - content: "\f059"; -} -.icon-info-sign:before { - content: "\f05a"; -} -.icon-screenshot:before { - content: "\f05b"; -} -.icon-remove-circle:before { - content: "\f05c"; -} -.icon-ok-circle:before { - content: "\f05d"; -} -.icon-ban-circle:before { - content: "\f05e"; -} -.icon-arrow-left:before { - content: "\f060"; -} -.icon-arrow-right:before { - content: "\f061"; -} -.icon-arrow-up:before { - content: "\f062"; -} -.icon-arrow-down:before { - content: "\f063"; -} -.icon-share-alt:before { - content: "\f064"; -} -.icon-resize-full:before { - content: "\f065"; -} -.icon-resize-small:before { - content: "\f066"; -} -.icon-plus:before { - content: "\f067"; -} -.icon-minus:before { - content: "\f068"; -} -.icon-asterisk:before { - content: "\f069"; -} -.icon-exclamation-sign:before { - content: "\f06a"; -} -.icon-gift:before { - content: "\f06b"; -} -.icon-leaf:before { - content: "\f06c"; -} -.icon-fire:before { - content: "\f06d"; -} -.icon-eye-open:before { - content: "\f06e"; -} -.icon-eye-close:before { - content: "\f070"; -} -.icon-warning-sign:before { - content: "\f071"; -} -.icon-plane:before { - content: "\f072"; -} -.icon-calendar:before { - content: "\f073"; -} -.icon-random:before { - content: "\f074"; -} -.icon-comment:before { - content: "\f075"; -} -.icon-magnet:before { - content: "\f076"; -} -.icon-chevron-up:before { - content: "\f077"; -} -.icon-chevron-down:before { - content: "\f078"; -} -.icon-retweet:before { - content: "\f079"; -} -.icon-shopping-cart:before { - content: "\f07a"; -} -.icon-folder-close:before { - content: "\f07b"; -} -.icon-folder-open:before { - content: "\f07c"; -} -.icon-resize-vertical:before { - content: "\f07d"; -} -.icon-resize-horizontal:before { - content: "\f07e"; -} -.icon-bar-chart:before { - content: "\f080"; -} -.icon-twitter-sign:before { - content: "\f081"; -} -.icon-facebook-sign:before { - content: "\f082"; -} -.icon-camera-retro:before { - content: "\f083"; -} -.icon-key:before { - content: "\f084"; -} -.icon-cogs:before { - content: "\f085"; -} -.icon-comments:before { - content: "\f086"; -} -.icon-thumbs-up:before { - content: "\f087"; -} -.icon-thumbs-down:before { - content: "\f088"; -} -.icon-star-half:before { - content: "\f089"; -} -.icon-heart-empty:before { - content: "\f08a"; -} -.icon-signout:before { - content: "\f08b"; -} -.icon-linkedin-sign:before { - content: "\f08c"; -} -.icon-pushpin:before { - content: "\f08d"; -} -.icon-external-link:before { - content: "\f08e"; -} -.icon-signin:before { - content: "\f090"; -} -.icon-trophy:before { - content: "\f091"; -} -.icon-github-sign:before { - content: "\f092"; -} -.icon-upload-alt:before { - content: "\f093"; -} -.icon-lemon:before { - content: "\f094"; -} -.icon-phone:before { - content: "\f095"; -} -.icon-check-empty:before { - content: "\f096"; -} -.icon-bookmark-empty:before { - content: "\f097"; -} -.icon-phone-sign:before { - content: "\f098"; -} -.icon-twitter:before { - content: "\f099"; -} -.icon-facebook:before { - content: "\f09a"; -} -.icon-github:before { - content: "\f09b"; -} -.icon-unlock:before { - content: "\f09c"; -} -.icon-credit-card:before { - content: "\f09d"; -} -.icon-rss:before { - content: "\f09e"; -} -.icon-hdd:before { - content: "\f0a0"; -} -.icon-bullhorn:before { - content: "\f0a1"; -} -.icon-bell:before { - content: "\f0a2"; -} -.icon-certificate:before { - content: "\f0a3"; -} -.icon-hand-right:before { - content: "\f0a4"; -} -.icon-hand-left:before { - content: "\f0a5"; -} -.icon-hand-up:before { - content: "\f0a6"; -} -.icon-hand-down:before { - content: "\f0a7"; -} -.icon-circle-arrow-left:before { - content: "\f0a8"; -} -.icon-circle-arrow-right:before { - content: "\f0a9"; -} -.icon-circle-arrow-up:before { - content: "\f0aa"; -} -.icon-circle-arrow-down:before { - content: "\f0ab"; -} -.icon-globe:before { - content: "\f0ac"; -} -.icon-wrench:before { - content: "\f0ad"; -} -.icon-tasks:before { - content: "\f0ae"; -} -.icon-filter:before { - content: "\f0b0"; -} -.icon-briefcase:before { - content: "\f0b1"; -} -.icon-fullscreen:before { - content: "\f0b2"; -} -.icon-group:before { - content: "\f0c0"; -} -.icon-link:before { - content: "\f0c1"; -} -.icon-cloud:before { - content: "\f0c2"; -} -.icon-beaker:before { - content: "\f0c3"; -} -.icon-cut:before { - content: "\f0c4"; -} -.icon-copy:before { - content: "\f0c5"; -} -.icon-paper-clip:before { - content: "\f0c6"; -} -.icon-save:before { - content: "\f0c7"; -} -.icon-sign-blank:before { - content: "\f0c8"; -} -.icon-reorder:before { - content: "\f0c9"; -} -.icon-list-ul:before { - content: "\f0ca"; -} -.icon-list-ol:before { - content: "\f0cb"; -} -.icon-strikethrough:before { - content: "\f0cc"; -} -.icon-underline:before { - content: "\f0cd"; -} -.icon-table:before { - content: "\f0ce"; -} -.icon-magic:before { - content: "\f0d0"; -} -.icon-truck:before { - content: "\f0d1"; -} -.icon-pinterest:before { - content: "\f0d2"; -} -.icon-pinterest-sign:before { - content: "\f0d3"; -} -.icon-google-plus-sign:before { - content: "\f0d4"; -} -.icon-google-plus:before { - content: "\f0d5"; -} -.icon-money:before { - content: "\f0d6"; -} -.icon-caret-down:before { - content: "\f0d7"; -} -.icon-caret-up:before { - content: "\f0d8"; -} -.icon-caret-left:before { - content: "\f0d9"; -} -.icon-caret-right:before { - content: "\f0da"; -} -.icon-columns:before { - content: "\f0db"; -} -.icon-sort:before { - content: "\f0dc"; -} -.icon-sort-down:before { - content: "\f0dd"; -} -.icon-sort-up:before { - content: "\f0de"; -} -.icon-envelope-alt:before { - content: "\f0e0"; -} -.icon-linkedin:before { - content: "\f0e1"; -} -.icon-undo:before { - content: "\f0e2"; -} -.icon-legal:before { - content: "\f0e3"; -} -.icon-dashboard:before { - content: "\f0e4"; -} -.icon-comment-alt:before { - content: "\f0e5"; -} -.icon-comments-alt:before { - content: "\f0e6"; -} -.icon-bolt:before { - content: "\f0e7"; -} -.icon-sitemap:before { - content: "\f0e8"; -} -.icon-umbrella:before { - content: "\f0e9"; -} -.icon-paste:before { - content: "\f0ea"; -} -.icon-lightbulb:before { - content: "\f0eb"; -} -.icon-exchange:before { - content: "\f0ec"; -} -.icon-cloud-download:before { - content: "\f0ed"; -} -.icon-cloud-upload:before { - content: "\f0ee"; -} -.icon-user-md:before { - content: "\f0f0"; -} -.icon-stethoscope:before { - content: "\f0f1"; -} -.icon-suitcase:before { - content: "\f0f2"; -} -.icon-bell-alt:before { - content: "\f0f3"; -} -.icon-coffee:before { - content: "\f0f4"; -} -.icon-food:before { - content: "\f0f5"; -} -.icon-file-alt:before { - content: "\f0f6"; -} -.icon-building:before { - content: "\f0f7"; -} -.icon-hospital:before { - content: "\f0f8"; -} -.icon-ambulance:before { - content: "\f0f9"; -} -.icon-medkit:before { - content: "\f0fa"; -} -.icon-fighter-jet:before { - content: "\f0fb"; -} -.icon-beer:before { - content: "\f0fc"; -} -.icon-h-sign:before { - content: "\f0fd"; -} -.icon-plus-sign-alt:before { - content: "\f0fe"; -} -.icon-double-angle-left:before { - content: "\f100"; -} -.icon-double-angle-right:before { - content: "\f101"; -} -.icon-double-angle-up:before { - content: "\f102"; -} -.icon-double-angle-down:before { - content: "\f103"; -} -.icon-angle-left:before { - content: "\f104"; -} -.icon-angle-right:before { - content: "\f105"; -} -.icon-angle-up:before { - content: "\f106"; -} -.icon-angle-down:before { - content: "\f107"; -} -.icon-desktop:before { - content: "\f108"; -} -.icon-laptop:before { - content: "\f109"; -} -.icon-tablet:before { - content: "\f10a"; -} -.icon-mobile-phone:before { - content: "\f10b"; -} -.icon-circle-blank:before { - content: "\f10c"; -} -.icon-quote-left:before { - content: "\f10d"; -} -.icon-quote-right:before { - content: "\f10e"; -} -.icon-spinner:before { - content: "\f110"; -} -.icon-circle:before { - content: "\f111"; -} -.icon-reply:before { - content: "\f112"; -} -.icon-github-alt:before { - content: "\f113"; -} -.icon-folder-close-alt:before { - content: "\f114"; -} -.icon-folder-open-alt:before { - content: "\f115"; -} [class^="icon-"], [class*=" icon-"] { display: inline-block; @@ -7963,45 +7159,39 @@ h4 small { .context-info.editing .module-content { margin-top: 0; } -.hero { - background: url("../../../base/images/background-tile.png"); +.homepage [role=main] { padding: 20px 0; min-height: 0; } -.hero > .container { +.homepage .row { position: relative; - padding-bottom: 0; } -.hero .search-giant { - margin-bottom: 10px; -} -.hero .search-giant input { - border-color: #003f52; -} -.hero .page-heading { - font-size: 18px; - margin-bottom: 0; -} -.hero .module-dark { +.homepage .module-search { padding: 5px; - margin-bottom: 0; + margin: 0; color: #ffffff; background: #ffffff; } -.hero .module-dark .module-content { +.homepage .module-search .search-giant { + margin-bottom: 10px; +} +.homepage .module-search .search-giant input { + border-color: #003f52; +} +.homepage .module-search .module-content { -webkit-border-radius: 3px 3px 0 0; -moz-border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0; background-color: #2f9b45; border-bottom: none; } -.hero .module-dark .module-content .heading { +.homepage .module-search .module-content .heading { margin-top: 0; margin-bottom: 7px; font-size: 24px; line-height: 40px; } -.hero .tags { +.homepage .module-search .tags { *zoom: 1; padding: 5px 10px 10px 10px; background-color: #237434; @@ -8009,69 +7199,77 @@ h4 small { -moz-border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px; } -.hero .tags:before, -.hero .tags:after { +.homepage .module-search .tags:before, +.homepage .module-search .tags:after { display: table; content: ""; line-height: 0; } -.hero .tags:after { +.homepage .module-search .tags:after { clear: both; } -.hero .tags h3, -.hero .tags .tag { +.homepage .module-search .tags h3, +.homepage .module-search .tags .tag { display: block; float: left; margin: 5px 10px 0 0; } -.hero .tags h3 { +.homepage .module-search .tags h3 { font-size: 14px; line-height: 20px; padding: 2px 8px; } -.hero-primary, -.hero-secondary { +.homepage .group-list { + margin: 0; +} +.homepage .box .inner { + padding: 20px 25px; +} +.homepage .stats h3 { + margin: 0 0 10px 0; +} +.homepage .stats ul { + margin: 0; + list-style: none; + *zoom: 1; +} +.homepage .stats ul:before, +.homepage .stats ul:after { + display: table; + content: ""; + line-height: 0; +} +.homepage .stats ul:after { + clear: both; +} +.homepage .stats ul li { float: left; - margin-left: 20px; - width: 460px; + width: 25%; + font-weight: 300; } -.hero-primary { - margin-left: 0; - margin-bottom: 0; +.homepage .stats ul li a { + display: block; +} +.homepage .stats ul li a b { + display: block; + font-size: 35px; + line-height: 1.5; } -.hero-secondary { +.homepage .stats ul li a:hover { + text-decoration: none; +} +.homepage.layout-1 .row1 .col2 { position: absolute; bottom: 0; right: 0; } -.hero-secondary .hero-secondary-inner { +.homepage.layout-1 .row1 .col2 .module-search { bottom: 0; left: 0; right: 0; } -.main.homepage { - padding-top: 20px; - padding-bottom: 20px; - border-top: 1px solid #cccccc; -} -.main.homepage .module-heading .media-image { - margin-right: 15px; - max-height: 53px; - -webkit-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - -moz-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); -} -.main.homepage .group-listing .box { - min-height: 275px; -} -.main.homepage .group-list { - margin-bottom: 0; -} -.main.homepage .group-list .dataset-content { - min-height: 70px; -} -.main.homepage .box .module { - margin-top: 0; +.homepage.layout-2 .stats { + margin-top: 20px; } .account-masthead { *zoom: 1; @@ -8920,6 +8118,11 @@ iframe { padding-bottom: 20px !important; margin-bottom: 0 !important; } +.ie8 .lang-dropdown, +.ie7 .lang-dropdown { + position: relative !important; + top: -90px !important; +} .ie7 .alert { position: relative; } @@ -9061,11 +8264,6 @@ iframe { .ie7 .module-heading .media-content { position: relative; } -.ie7 .module-heading .action { - position: absolute; - top: 9px; - right: 10px; -} .ie7 .module-heading .media-image img { float: left; } diff --git a/ckan/public/base/css/main.css b/ckan/public/base/css/main.css index 2ed06e1eb12..5e4887a0293 100644 --- a/ckan/public/base/css/main.css +++ b/ckan/public/base/css/main.css @@ -49,16 +49,12 @@ sub { } img { /* Responsive images (ensure images don't scale beyond their parents) */ - max-width: 100%; /* Part 1: Set a maxium relative to the parent */ - width: auto\9; /* IE7-8 need help adjusting responsive images */ - height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ - vertical-align: middle; border: 0; -ms-interpolation-mode: bicubic; @@ -153,7 +149,7 @@ textarea { img { max-width: 100% !important; } - @page { + @page { margin: 0.5cm; } p, @@ -693,7 +689,6 @@ ol.inline > li { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; padding-left: 5px; padding-right: 5px; @@ -972,7 +967,6 @@ input[type="color"]:focus, outline: 0; outline: thin dotted \9; /* IE6-9 */ - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); @@ -982,10 +976,8 @@ input[type="checkbox"] { margin: 4px 0 0; *margin-top: 0; /* IE7 */ - margin-top: 1px \9; /* IE8-9 */ - line-height: normal; } input[type="file"], @@ -1001,10 +993,8 @@ select, input[type="file"] { height: 30px; /* In IE7, the height of the select element cannot be changed by height, only font-size */ - *margin-top: 4px; /* For IE7, add top margin to align select with labels */ - line-height: 30px; } select { @@ -1402,7 +1392,6 @@ select:focus:invalid:focus { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; vertical-align: middle; padding-left: 5px; @@ -1553,7 +1542,6 @@ input.search-query { padding-left: 14px; padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ - margin-bottom: 0; -webkit-border-radius: 15px; -moz-border-radius: 15px; @@ -1610,7 +1598,6 @@ input.search-query { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; margin-bottom: 0; vertical-align: middle; @@ -2220,7 +2207,6 @@ button.close { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; padding: 4px 12px; margin-bottom: 0; @@ -2243,7 +2229,6 @@ button.close { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #eaeaea; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); border: 1px solid #cccccc; *border: 0; @@ -2379,7 +2364,6 @@ input[type="button"].btn-block { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #085871; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .btn-primary:hover, @@ -2411,7 +2395,6 @@ input[type="button"].btn-block { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #f89406; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .btn-warning:hover, @@ -2443,7 +2426,6 @@ input[type="button"].btn-block { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #bd362f; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .btn-danger:hover, @@ -2475,7 +2457,6 @@ input[type="button"].btn-block { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #51a351; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .btn-success:hover, @@ -2507,7 +2488,6 @@ input[type="button"].btn-block { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #2f96b4; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .btn-info:hover, @@ -2539,7 +2519,6 @@ input[type="button"].btn-block { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #222222; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .btn-inverse:hover, @@ -2614,7 +2593,6 @@ input[type="submit"].btn.btn-mini { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; font-size: 0; vertical-align: middle; @@ -2790,7 +2768,6 @@ input[type="submit"].btn.btn-mini { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; } .btn-group-vertical > .btn { @@ -3487,7 +3464,6 @@ input[type="submit"].btn.btn-mini { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #e5e5e5; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075); -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075); @@ -3721,7 +3697,6 @@ input[type="submit"].btn.btn-mini { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #040404; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .navbar-inverse .btn-navbar:hover, @@ -3751,7 +3726,6 @@ input[type="submit"].btn.btn-mini { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; text-shadow: 0 1px 0 #ffffff; } @@ -3769,7 +3743,6 @@ input[type="submit"].btn.btn-mini { display: inline-block; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; margin-left: 0; margin-bottom: 0; @@ -3970,7 +3943,6 @@ input[type="submit"].btn.btn-mini { border: 1px solid rgba(0, 0, 0, 0.3); *border: 1px solid #999; /* IE6-7 */ - -webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px; @@ -4898,16 +4870,6 @@ a.tag:hover { .module-heading:after { clear: both; } -.module-heading .action { - float: right; - color: #888888; - font-size: 12px; - line-height: 20px; - text-decoration: underline; -} -.module-heading .action:hover { - color: #444444; -} .module-content { padding: 0 25px; margin: 20px 0; @@ -5828,7 +5790,6 @@ textarea { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); *background-color: #085871; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .control-custom.disabled .checkbox.btn:hover, @@ -6107,7 +6068,6 @@ textarea { outline: 0; outline: thin dotted \9; /* IE6-9 */ - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6); @@ -6130,6 +6090,70 @@ textarea { -moz-box-shadow: none; box-shadow: none; } +.js .image-upload #field-image-upload { + cursor: pointer; + position: absolute; + z-index: 1; + opacity: 0; + filter: alpha(opacity=0); +} +.js .image-upload .controls { + position: relative; +} +.js .image-upload .btn { + position: relative; + top: 0; + margin-right: 10px; +} +.js .image-upload .btn.hover { + color: #333333; + text-decoration: none; + background-position: 0 -15px; + -webkit-transition: background-position 0.1s linear; + -moz-transition: background-position 0.1s linear; + -o-transition: background-position 0.1s linear; + transition: background-position 0.1s linear; +} +.js .image-upload .btn-remove-url { + position: absolute; + margin-right: 0; + top: 4px; + right: 5px; + padding: 0 4px; + -webkit-border-radius: 100px; + -moz-border-radius: 100px; + border-radius: 100px; +} +.js .image-upload .btn-remove-url .icon-remove { + margin-right: 0; +.add-member-form .control-label { + width: 100%; + text-align: left; +} +.add-member-form .controls { + margin-left: auto; +} +.add-member-or { + float: left; + margin-top: 75px; + width: 7%; + text-align: center; + text-transform: uppercase; + color: #999999; + font-weight: bold; +} +.add-member-form .row-fluid .control-group { + float: left; + width: 45%; +} +.add-member-form .row-fluid .select2-container, +.add-member-form .row-fluid input { + width: 100% !important; +} +#recaptcha_table { + table-layout: inherit; + line-height: 1; +} .dataset-item { border-bottom: 1px dotted #dddddd; padding-bottom: 20px; @@ -6516,7 +6540,6 @@ textarea { margin-right: 5px; *display: inline; /* IE7 inline-block hack */ - *zoom: 1; } .actions li:last-of-type { @@ -7141,45 +7164,39 @@ h4 small { .context-info.editing .module-content { margin-top: 0; } -.hero { - background: url("../../../base/images/background-tile.png"); +.homepage [role=main] { padding: 20px 0; min-height: 0; } -.hero > .container { +.homepage .row { position: relative; - padding-bottom: 0; } -.hero .search-giant { - margin-bottom: 10px; -} -.hero .search-giant input { - border-color: #003f52; -} -.hero .page-heading { - font-size: 18px; - margin-bottom: 0; -} -.hero .module-dark { +.homepage .module-search { padding: 5px; - margin-bottom: 0; + margin: 0; color: #ffffff; background: #ffffff; } -.hero .module-dark .module-content { +.homepage .module-search .search-giant { + margin-bottom: 10px; +} +.homepage .module-search .search-giant input { + border-color: #003f52; +} +.homepage .module-search .module-content { -webkit-border-radius: 3px 3px 0 0; -moz-border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0; background-color: #005d7a; border-bottom: none; } -.hero .module-dark .module-content .heading { +.homepage .module-search .module-content .heading { margin-top: 0; margin-bottom: 7px; font-size: 24px; line-height: 40px; } -.hero .tags { +.homepage .module-search .tags { *zoom: 1; padding: 5px 10px 10px 10px; background-color: #003647; @@ -7187,69 +7204,77 @@ h4 small { -moz-border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px; } -.hero .tags:before, -.hero .tags:after { +.homepage .module-search .tags:before, +.homepage .module-search .tags:after { display: table; content: ""; line-height: 0; } -.hero .tags:after { +.homepage .module-search .tags:after { clear: both; } -.hero .tags h3, -.hero .tags .tag { +.homepage .module-search .tags h3, +.homepage .module-search .tags .tag { display: block; float: left; margin: 5px 10px 0 0; } -.hero .tags h3 { +.homepage .module-search .tags h3 { font-size: 14px; line-height: 20px; padding: 2px 8px; } -.hero-primary, -.hero-secondary { +.homepage .group-list { + margin: 0; +} +.homepage .box .inner { + padding: 20px 25px; +} +.homepage .stats h3 { + margin: 0 0 10px 0; +} +.homepage .stats ul { + margin: 0; + list-style: none; + *zoom: 1; +} +.homepage .stats ul:before, +.homepage .stats ul:after { + display: table; + content: ""; + line-height: 0; +} +.homepage .stats ul:after { + clear: both; +} +.homepage .stats ul li { float: left; - margin-left: 20px; - width: 460px; + width: 25%; + font-weight: 300; } -.hero-primary { - margin-left: 0; - margin-bottom: 0; +.homepage .stats ul li a { + display: block; } -.hero-secondary { +.homepage .stats ul li a b { + display: block; + font-size: 35px; + line-height: 1.5; +} +.homepage .stats ul li a:hover { + text-decoration: none; +} +.homepage.layout-1 .row1 .col2 { position: absolute; bottom: 0; right: 0; } -.hero-secondary .hero-secondary-inner { +.homepage.layout-1 .row1 .col2 .module-search { bottom: 0; left: 0; right: 0; } -.main.homepage { - padding-top: 20px; - padding-bottom: 20px; - border-top: 1px solid #cccccc; -} -.main.homepage .module-heading .media-image { - margin-right: 15px; - max-height: 53px; - -webkit-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - -moz-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); -} -.main.homepage .group-listing .box { - min-height: 275px; -} -.main.homepage .group-list { - margin-bottom: 0; -} -.main.homepage .group-list .dataset-content { - min-height: 70px; -} -.main.homepage .box .module { - margin-top: 0; +.homepage.layout-2 .stats { + margin-top: 20px; } .account-masthead { *zoom: 1; @@ -7925,9 +7950,9 @@ h4 small { -webkit-border-radius: 100px; -moz-border-radius: 100px; border-radius: 100px; - -webkit-box-shadow: inset 0 1px 2 x rgba(0, 0, 0, 0.2); - -moz-box-shadow: inset 0 1px 2 x rgba(0, 0, 0, 0.2); - box-shadow: inset 0 1px 2 x rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 2x rgba(0, 0, 0, 0.2); + -moz-box-shadow: inset 0 1px 2x rgba(0, 0, 0, 0.2); + box-shadow: inset 0 1px 2x rgba(0, 0, 0, 0.2); } .popover-followee .nav li a:hover i { background-color: #000; @@ -8162,7 +8187,6 @@ iframe { .ie7 .control-custom .checkbox { *display: inline; /* IE7 inline-block hack */ - *zoom: 1; } .ie7 .stages { @@ -8196,7 +8220,6 @@ iframe { .ie7 .masthead nav { *display: inline; /* IE7 inline-block hack */ - *zoom: 1; } .ie7 .masthead .header-image { @@ -8244,11 +8267,6 @@ iframe { .ie7 .module-heading .media-content { position: relative; } -.ie7 .module-heading .action { - position: absolute; - top: 9px; - right: 10px; -} .ie7 .module-heading .media-image img { float: left; } diff --git a/ckan/public/base/css/maroon.css b/ckan/public/base/css/maroon.css index c052d27ea74..4de9736ff0e 100644 --- a/ckan/public/base/css/maroon.css +++ b/ckan/public/base/css/maroon.css @@ -2055,6 +2055,14 @@ table th[class*="span"], .open > .dropdown-menu { display: block; } +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} .pull-right > .dropdown-menu { right: 0; left: auto; @@ -4890,16 +4898,6 @@ a.tag:hover { .module-heading:after { clear: both; } -.module-heading .action { - float: right; - color: #888888; - font-size: 12px; - line-height: 20px; - text-decoration: underline; -} -.module-heading .action:hover { - color: #444444; -} .module-content { padding: 0 25px; margin: 20px 0; @@ -5572,6 +5570,12 @@ textarea { .control-large input { height: 41px; } +.control-required { + color: #c6898b; +} +.form-actions .control-required-message { + float: left; +} .form-actions { background: none; margin-left: -25px; @@ -6116,6 +6120,34 @@ textarea { -moz-box-shadow: none; box-shadow: none; } +.add-member-form .control-label { + width: 100%; + text-align: left; +} +.add-member-form .controls { + margin-left: auto; +} +.add-member-or { + float: left; + margin-top: 75px; + width: 7%; + text-align: center; + text-transform: uppercase; + color: #999999; + font-weight: bold; +} +.add-member-form .row-fluid .control-group { + float: left; + width: 45%; +} +.add-member-form .row-fluid .select2-container, +.add-member-form .row-fluid input { + width: 100% !important; +} +#recaptcha_table { + table-layout: inherit; + line-height: 1; +} .dataset-item { border-bottom: 1px dotted #dddddd; padding-bottom: 20px; @@ -6904,842 +6936,6 @@ h4 small { height: 35px; background-position: -320px -62px; } -/* This is a modified version of the font-awesome core less document. */ -@font-face { - font-family: 'FontAwesome'; - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?v=3.0.1'); - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.woff?v=3.0.1') format('woff'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); - font-weight: normal; - font-style: normal; -} -[class^="icon-"], -[class*=" icon-"] { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - display: inline; - width: auto; - height: auto; - line-height: normal; - vertical-align: baseline; - background-image: none; - background-position: 0% 0%; - background-repeat: repeat; - margin-top: 0; -} -.icon-spin { - display: inline-block; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - } -} -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - } -} -@-o-keyframes spin { - 0% { - -o-transform: rotate(0deg); - } - 100% { - -o-transform: rotate(359deg); - } -} -@-ms-keyframes spin { - 0% { - -ms-transform: rotate(0deg); - } - 100% { - -ms-transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(359deg); - } -} -@-moz-document url-prefix() { - .icon-spin { - height: .9em; - } - .btn .icon-spin { - height: auto; - } - .icon-spin.icon-large { - height: 1.25em; - } - .btn .icon-spin.icon-large { - height: .75em; - } -} -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen - readers do not read off random characters that represent icons */ -.icon-glass:before { - content: "\f000"; -} -.icon-music:before { - content: "\f001"; -} -.icon-search:before { - content: "\f002"; -} -.icon-envelope:before { - content: "\f003"; -} -.icon-heart:before { - content: "\f004"; -} -.icon-star:before { - content: "\f005"; -} -.icon-star-empty:before { - content: "\f006"; -} -.icon-user:before { - content: "\f007"; -} -.icon-film:before { - content: "\f008"; -} -.icon-th-large:before { - content: "\f009"; -} -.icon-th:before { - content: "\f00a"; -} -.icon-th-list:before { - content: "\f00b"; -} -.icon-ok:before { - content: "\f00c"; -} -.icon-remove:before { - content: "\f00d"; -} -.icon-zoom-in:before { - content: "\f00e"; -} -.icon-zoom-out:before { - content: "\f010"; -} -.icon-off:before { - content: "\f011"; -} -.icon-signal:before { - content: "\f012"; -} -.icon-cog:before { - content: "\f013"; -} -.icon-trash:before { - content: "\f014"; -} -.icon-home:before { - content: "\f015"; -} -.icon-file:before { - content: "\f016"; -} -.icon-time:before { - content: "\f017"; -} -.icon-road:before { - content: "\f018"; -} -.icon-download-alt:before { - content: "\f019"; -} -.icon-download:before { - content: "\f01a"; -} -.icon-upload:before { - content: "\f01b"; -} -.icon-inbox:before { - content: "\f01c"; -} -.icon-play-circle:before { - content: "\f01d"; -} -.icon-repeat:before { - content: "\f01e"; -} -/* \f020 doesn't work in Safari. all shifted one down */ -.icon-refresh:before { - content: "\f021"; -} -.icon-list-alt:before { - content: "\f022"; -} -.icon-lock:before { - content: "\f023"; -} -.icon-flag:before { - content: "\f024"; -} -.icon-headphones:before { - content: "\f025"; -} -.icon-volume-off:before { - content: "\f026"; -} -.icon-volume-down:before { - content: "\f027"; -} -.icon-volume-up:before { - content: "\f028"; -} -.icon-qrcode:before { - content: "\f029"; -} -.icon-barcode:before { - content: "\f02a"; -} -.icon-tag:before { - content: "\f02b"; -} -.icon-tags:before { - content: "\f02c"; -} -.icon-book:before { - content: "\f02d"; -} -.icon-bookmark:before { - content: "\f02e"; -} -.icon-print:before { - content: "\f02f"; -} -.icon-camera:before { - content: "\f030"; -} -.icon-font:before { - content: "\f031"; -} -.icon-bold:before { - content: "\f032"; -} -.icon-italic:before { - content: "\f033"; -} -.icon-text-height:before { - content: "\f034"; -} -.icon-text-width:before { - content: "\f035"; -} -.icon-align-left:before { - content: "\f036"; -} -.icon-align-center:before { - content: "\f037"; -} -.icon-align-right:before { - content: "\f038"; -} -.icon-align-justify:before { - content: "\f039"; -} -.icon-list:before { - content: "\f03a"; -} -.icon-indent-left:before { - content: "\f03b"; -} -.icon-indent-right:before { - content: "\f03c"; -} -.icon-facetime-video:before { - content: "\f03d"; -} -.icon-picture:before { - content: "\f03e"; -} -.icon-pencil:before { - content: "\f040"; -} -.icon-map-marker:before { - content: "\f041"; -} -.icon-adjust:before { - content: "\f042"; -} -.icon-tint:before { - content: "\f043"; -} -.icon-edit:before { - content: "\f044"; -} -.icon-share:before { - content: "\f045"; -} -.icon-check:before { - content: "\f046"; -} -.icon-move:before { - content: "\f047"; -} -.icon-step-backward:before { - content: "\f048"; -} -.icon-fast-backward:before { - content: "\f049"; -} -.icon-backward:before { - content: "\f04a"; -} -.icon-play:before { - content: "\f04b"; -} -.icon-pause:before { - content: "\f04c"; -} -.icon-stop:before { - content: "\f04d"; -} -.icon-forward:before { - content: "\f04e"; -} -.icon-fast-forward:before { - content: "\f050"; -} -.icon-step-forward:before { - content: "\f051"; -} -.icon-eject:before { - content: "\f052"; -} -.icon-chevron-left:before { - content: "\f053"; -} -.icon-chevron-right:before { - content: "\f054"; -} -.icon-plus-sign:before { - content: "\f055"; -} -.icon-minus-sign:before { - content: "\f056"; -} -.icon-remove-sign:before { - content: "\f057"; -} -.icon-ok-sign:before { - content: "\f058"; -} -.icon-question-sign:before { - content: "\f059"; -} -.icon-info-sign:before { - content: "\f05a"; -} -.icon-screenshot:before { - content: "\f05b"; -} -.icon-remove-circle:before { - content: "\f05c"; -} -.icon-ok-circle:before { - content: "\f05d"; -} -.icon-ban-circle:before { - content: "\f05e"; -} -.icon-arrow-left:before { - content: "\f060"; -} -.icon-arrow-right:before { - content: "\f061"; -} -.icon-arrow-up:before { - content: "\f062"; -} -.icon-arrow-down:before { - content: "\f063"; -} -.icon-share-alt:before { - content: "\f064"; -} -.icon-resize-full:before { - content: "\f065"; -} -.icon-resize-small:before { - content: "\f066"; -} -.icon-plus:before { - content: "\f067"; -} -.icon-minus:before { - content: "\f068"; -} -.icon-asterisk:before { - content: "\f069"; -} -.icon-exclamation-sign:before { - content: "\f06a"; -} -.icon-gift:before { - content: "\f06b"; -} -.icon-leaf:before { - content: "\f06c"; -} -.icon-fire:before { - content: "\f06d"; -} -.icon-eye-open:before { - content: "\f06e"; -} -.icon-eye-close:before { - content: "\f070"; -} -.icon-warning-sign:before { - content: "\f071"; -} -.icon-plane:before { - content: "\f072"; -} -.icon-calendar:before { - content: "\f073"; -} -.icon-random:before { - content: "\f074"; -} -.icon-comment:before { - content: "\f075"; -} -.icon-magnet:before { - content: "\f076"; -} -.icon-chevron-up:before { - content: "\f077"; -} -.icon-chevron-down:before { - content: "\f078"; -} -.icon-retweet:before { - content: "\f079"; -} -.icon-shopping-cart:before { - content: "\f07a"; -} -.icon-folder-close:before { - content: "\f07b"; -} -.icon-folder-open:before { - content: "\f07c"; -} -.icon-resize-vertical:before { - content: "\f07d"; -} -.icon-resize-horizontal:before { - content: "\f07e"; -} -.icon-bar-chart:before { - content: "\f080"; -} -.icon-twitter-sign:before { - content: "\f081"; -} -.icon-facebook-sign:before { - content: "\f082"; -} -.icon-camera-retro:before { - content: "\f083"; -} -.icon-key:before { - content: "\f084"; -} -.icon-cogs:before { - content: "\f085"; -} -.icon-comments:before { - content: "\f086"; -} -.icon-thumbs-up:before { - content: "\f087"; -} -.icon-thumbs-down:before { - content: "\f088"; -} -.icon-star-half:before { - content: "\f089"; -} -.icon-heart-empty:before { - content: "\f08a"; -} -.icon-signout:before { - content: "\f08b"; -} -.icon-linkedin-sign:before { - content: "\f08c"; -} -.icon-pushpin:before { - content: "\f08d"; -} -.icon-external-link:before { - content: "\f08e"; -} -.icon-signin:before { - content: "\f090"; -} -.icon-trophy:before { - content: "\f091"; -} -.icon-github-sign:before { - content: "\f092"; -} -.icon-upload-alt:before { - content: "\f093"; -} -.icon-lemon:before { - content: "\f094"; -} -.icon-phone:before { - content: "\f095"; -} -.icon-check-empty:before { - content: "\f096"; -} -.icon-bookmark-empty:before { - content: "\f097"; -} -.icon-phone-sign:before { - content: "\f098"; -} -.icon-twitter:before { - content: "\f099"; -} -.icon-facebook:before { - content: "\f09a"; -} -.icon-github:before { - content: "\f09b"; -} -.icon-unlock:before { - content: "\f09c"; -} -.icon-credit-card:before { - content: "\f09d"; -} -.icon-rss:before { - content: "\f09e"; -} -.icon-hdd:before { - content: "\f0a0"; -} -.icon-bullhorn:before { - content: "\f0a1"; -} -.icon-bell:before { - content: "\f0a2"; -} -.icon-certificate:before { - content: "\f0a3"; -} -.icon-hand-right:before { - content: "\f0a4"; -} -.icon-hand-left:before { - content: "\f0a5"; -} -.icon-hand-up:before { - content: "\f0a6"; -} -.icon-hand-down:before { - content: "\f0a7"; -} -.icon-circle-arrow-left:before { - content: "\f0a8"; -} -.icon-circle-arrow-right:before { - content: "\f0a9"; -} -.icon-circle-arrow-up:before { - content: "\f0aa"; -} -.icon-circle-arrow-down:before { - content: "\f0ab"; -} -.icon-globe:before { - content: "\f0ac"; -} -.icon-wrench:before { - content: "\f0ad"; -} -.icon-tasks:before { - content: "\f0ae"; -} -.icon-filter:before { - content: "\f0b0"; -} -.icon-briefcase:before { - content: "\f0b1"; -} -.icon-fullscreen:before { - content: "\f0b2"; -} -.icon-group:before { - content: "\f0c0"; -} -.icon-link:before { - content: "\f0c1"; -} -.icon-cloud:before { - content: "\f0c2"; -} -.icon-beaker:before { - content: "\f0c3"; -} -.icon-cut:before { - content: "\f0c4"; -} -.icon-copy:before { - content: "\f0c5"; -} -.icon-paper-clip:before { - content: "\f0c6"; -} -.icon-save:before { - content: "\f0c7"; -} -.icon-sign-blank:before { - content: "\f0c8"; -} -.icon-reorder:before { - content: "\f0c9"; -} -.icon-list-ul:before { - content: "\f0ca"; -} -.icon-list-ol:before { - content: "\f0cb"; -} -.icon-strikethrough:before { - content: "\f0cc"; -} -.icon-underline:before { - content: "\f0cd"; -} -.icon-table:before { - content: "\f0ce"; -} -.icon-magic:before { - content: "\f0d0"; -} -.icon-truck:before { - content: "\f0d1"; -} -.icon-pinterest:before { - content: "\f0d2"; -} -.icon-pinterest-sign:before { - content: "\f0d3"; -} -.icon-google-plus-sign:before { - content: "\f0d4"; -} -.icon-google-plus:before { - content: "\f0d5"; -} -.icon-money:before { - content: "\f0d6"; -} -.icon-caret-down:before { - content: "\f0d7"; -} -.icon-caret-up:before { - content: "\f0d8"; -} -.icon-caret-left:before { - content: "\f0d9"; -} -.icon-caret-right:before { - content: "\f0da"; -} -.icon-columns:before { - content: "\f0db"; -} -.icon-sort:before { - content: "\f0dc"; -} -.icon-sort-down:before { - content: "\f0dd"; -} -.icon-sort-up:before { - content: "\f0de"; -} -.icon-envelope-alt:before { - content: "\f0e0"; -} -.icon-linkedin:before { - content: "\f0e1"; -} -.icon-undo:before { - content: "\f0e2"; -} -.icon-legal:before { - content: "\f0e3"; -} -.icon-dashboard:before { - content: "\f0e4"; -} -.icon-comment-alt:before { - content: "\f0e5"; -} -.icon-comments-alt:before { - content: "\f0e6"; -} -.icon-bolt:before { - content: "\f0e7"; -} -.icon-sitemap:before { - content: "\f0e8"; -} -.icon-umbrella:before { - content: "\f0e9"; -} -.icon-paste:before { - content: "\f0ea"; -} -.icon-lightbulb:before { - content: "\f0eb"; -} -.icon-exchange:before { - content: "\f0ec"; -} -.icon-cloud-download:before { - content: "\f0ed"; -} -.icon-cloud-upload:before { - content: "\f0ee"; -} -.icon-user-md:before { - content: "\f0f0"; -} -.icon-stethoscope:before { - content: "\f0f1"; -} -.icon-suitcase:before { - content: "\f0f2"; -} -.icon-bell-alt:before { - content: "\f0f3"; -} -.icon-coffee:before { - content: "\f0f4"; -} -.icon-food:before { - content: "\f0f5"; -} -.icon-file-alt:before { - content: "\f0f6"; -} -.icon-building:before { - content: "\f0f7"; -} -.icon-hospital:before { - content: "\f0f8"; -} -.icon-ambulance:before { - content: "\f0f9"; -} -.icon-medkit:before { - content: "\f0fa"; -} -.icon-fighter-jet:before { - content: "\f0fb"; -} -.icon-beer:before { - content: "\f0fc"; -} -.icon-h-sign:before { - content: "\f0fd"; -} -.icon-plus-sign-alt:before { - content: "\f0fe"; -} -.icon-double-angle-left:before { - content: "\f100"; -} -.icon-double-angle-right:before { - content: "\f101"; -} -.icon-double-angle-up:before { - content: "\f102"; -} -.icon-double-angle-down:before { - content: "\f103"; -} -.icon-angle-left:before { - content: "\f104"; -} -.icon-angle-right:before { - content: "\f105"; -} -.icon-angle-up:before { - content: "\f106"; -} -.icon-angle-down:before { - content: "\f107"; -} -.icon-desktop:before { - content: "\f108"; -} -.icon-laptop:before { - content: "\f109"; -} -.icon-tablet:before { - content: "\f10a"; -} -.icon-mobile-phone:before { - content: "\f10b"; -} -.icon-circle-blank:before { - content: "\f10c"; -} -.icon-quote-left:before { - content: "\f10d"; -} -.icon-quote-right:before { - content: "\f10e"; -} -.icon-spinner:before { - content: "\f110"; -} -.icon-circle:before { - content: "\f111"; -} -.icon-reply:before { - content: "\f112"; -} -.icon-github-alt:before { - content: "\f113"; -} -.icon-folder-close-alt:before { - content: "\f114"; -} -.icon-folder-open-alt:before { - content: "\f115"; -} [class^="icon-"], [class*=" icon-"] { display: inline-block; @@ -7963,45 +7159,39 @@ h4 small { .context-info.editing .module-content { margin-top: 0; } -.hero { - background: url("../../../base/images/background-tile.png"); +.homepage [role=main] { padding: 20px 0; min-height: 0; } -.hero > .container { +.homepage .row { position: relative; - padding-bottom: 0; } -.hero .search-giant { - margin-bottom: 10px; -} -.hero .search-giant input { - border-color: #003f52; -} -.hero .page-heading { - font-size: 18px; - margin-bottom: 0; -} -.hero .module-dark { +.homepage .module-search { padding: 5px; - margin-bottom: 0; + margin: 0; color: #ffffff; background: #ffffff; } -.hero .module-dark .module-content { +.homepage .module-search .search-giant { + margin-bottom: 10px; +} +.homepage .module-search .search-giant input { + border-color: #003f52; +} +.homepage .module-search .module-content { -webkit-border-radius: 3px 3px 0 0; -moz-border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0; background-color: #810606; border-bottom: none; } -.hero .module-dark .module-content .heading { +.homepage .module-search .module-content .heading { margin-top: 0; margin-bottom: 7px; font-size: 24px; line-height: 40px; } -.hero .tags { +.homepage .module-search .tags { *zoom: 1; padding: 5px 10px 10px 10px; background-color: #500404; @@ -8009,69 +7199,77 @@ h4 small { -moz-border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px; } -.hero .tags:before, -.hero .tags:after { +.homepage .module-search .tags:before, +.homepage .module-search .tags:after { display: table; content: ""; line-height: 0; } -.hero .tags:after { +.homepage .module-search .tags:after { clear: both; } -.hero .tags h3, -.hero .tags .tag { +.homepage .module-search .tags h3, +.homepage .module-search .tags .tag { display: block; float: left; margin: 5px 10px 0 0; } -.hero .tags h3 { +.homepage .module-search .tags h3 { font-size: 14px; line-height: 20px; padding: 2px 8px; } -.hero-primary, -.hero-secondary { +.homepage .group-list { + margin: 0; +} +.homepage .box .inner { + padding: 20px 25px; +} +.homepage .stats h3 { + margin: 0 0 10px 0; +} +.homepage .stats ul { + margin: 0; + list-style: none; + *zoom: 1; +} +.homepage .stats ul:before, +.homepage .stats ul:after { + display: table; + content: ""; + line-height: 0; +} +.homepage .stats ul:after { + clear: both; +} +.homepage .stats ul li { float: left; - margin-left: 20px; - width: 460px; + width: 25%; + font-weight: 300; } -.hero-primary { - margin-left: 0; - margin-bottom: 0; +.homepage .stats ul li a { + display: block; +} +.homepage .stats ul li a b { + display: block; + font-size: 35px; + line-height: 1.5; } -.hero-secondary { +.homepage .stats ul li a:hover { + text-decoration: none; +} +.homepage.layout-1 .row1 .col2 { position: absolute; bottom: 0; right: 0; } -.hero-secondary .hero-secondary-inner { +.homepage.layout-1 .row1 .col2 .module-search { bottom: 0; left: 0; right: 0; } -.main.homepage { - padding-top: 20px; - padding-bottom: 20px; - border-top: 1px solid #cccccc; -} -.main.homepage .module-heading .media-image { - margin-right: 15px; - max-height: 53px; - -webkit-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - -moz-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); -} -.main.homepage .group-listing .box { - min-height: 275px; -} -.main.homepage .group-list { - margin-bottom: 0; -} -.main.homepage .group-list .dataset-content { - min-height: 70px; -} -.main.homepage .box .module { - margin-top: 0; +.homepage.layout-2 .stats { + margin-top: 20px; } .account-masthead { *zoom: 1; @@ -8920,6 +8118,11 @@ iframe { padding-bottom: 20px !important; margin-bottom: 0 !important; } +.ie8 .lang-dropdown, +.ie7 .lang-dropdown { + position: relative !important; + top: -90px !important; +} .ie7 .alert { position: relative; } @@ -9061,11 +8264,6 @@ iframe { .ie7 .module-heading .media-content { position: relative; } -.ie7 .module-heading .action { - position: absolute; - top: 9px; - right: 10px; -} .ie7 .module-heading .media-image img { float: left; } diff --git a/ckan/public/base/css/red.css b/ckan/public/base/css/red.css index 26f22422970..4b1a9181328 100644 --- a/ckan/public/base/css/red.css +++ b/ckan/public/base/css/red.css @@ -2055,6 +2055,14 @@ table th[class*="span"], .open > .dropdown-menu { display: block; } +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} .pull-right > .dropdown-menu { right: 0; left: auto; @@ -4890,16 +4898,6 @@ a.tag:hover { .module-heading:after { clear: both; } -.module-heading .action { - float: right; - color: #888888; - font-size: 12px; - line-height: 20px; - text-decoration: underline; -} -.module-heading .action:hover { - color: #444444; -} .module-content { padding: 0 25px; margin: 20px 0; @@ -5572,6 +5570,12 @@ textarea { .control-large input { height: 41px; } +.control-required { + color: #c6898b; +} +.form-actions .control-required-message { + float: left; +} .form-actions { background: none; margin-left: -25px; @@ -6116,6 +6120,34 @@ textarea { -moz-box-shadow: none; box-shadow: none; } +.add-member-form .control-label { + width: 100%; + text-align: left; +} +.add-member-form .controls { + margin-left: auto; +} +.add-member-or { + float: left; + margin-top: 75px; + width: 7%; + text-align: center; + text-transform: uppercase; + color: #999999; + font-weight: bold; +} +.add-member-form .row-fluid .control-group { + float: left; + width: 45%; +} +.add-member-form .row-fluid .select2-container, +.add-member-form .row-fluid input { + width: 100% !important; +} +#recaptcha_table { + table-layout: inherit; + line-height: 1; +} .dataset-item { border-bottom: 1px dotted #dddddd; padding-bottom: 20px; @@ -6904,842 +6936,6 @@ h4 small { height: 35px; background-position: -320px -62px; } -/* This is a modified version of the font-awesome core less document. */ -@font-face { - font-family: 'FontAwesome'; - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?v=3.0.1'); - src: url('../../../base/vendor/font-awesome/font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.woff?v=3.0.1') format('woff'), url('../../../base/vendor/font-awesome/font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); - font-weight: normal; - font-style: normal; -} -[class^="icon-"], -[class*=" icon-"] { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - display: inline; - width: auto; - height: auto; - line-height: normal; - vertical-align: baseline; - background-image: none; - background-position: 0% 0%; - background-repeat: repeat; - margin-top: 0; -} -.icon-spin { - display: inline-block; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - } -} -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - } -} -@-o-keyframes spin { - 0% { - -o-transform: rotate(0deg); - } - 100% { - -o-transform: rotate(359deg); - } -} -@-ms-keyframes spin { - 0% { - -ms-transform: rotate(0deg); - } - 100% { - -ms-transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(359deg); - } -} -@-moz-document url-prefix() { - .icon-spin { - height: .9em; - } - .btn .icon-spin { - height: auto; - } - .icon-spin.icon-large { - height: 1.25em; - } - .btn .icon-spin.icon-large { - height: .75em; - } -} -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen - readers do not read off random characters that represent icons */ -.icon-glass:before { - content: "\f000"; -} -.icon-music:before { - content: "\f001"; -} -.icon-search:before { - content: "\f002"; -} -.icon-envelope:before { - content: "\f003"; -} -.icon-heart:before { - content: "\f004"; -} -.icon-star:before { - content: "\f005"; -} -.icon-star-empty:before { - content: "\f006"; -} -.icon-user:before { - content: "\f007"; -} -.icon-film:before { - content: "\f008"; -} -.icon-th-large:before { - content: "\f009"; -} -.icon-th:before { - content: "\f00a"; -} -.icon-th-list:before { - content: "\f00b"; -} -.icon-ok:before { - content: "\f00c"; -} -.icon-remove:before { - content: "\f00d"; -} -.icon-zoom-in:before { - content: "\f00e"; -} -.icon-zoom-out:before { - content: "\f010"; -} -.icon-off:before { - content: "\f011"; -} -.icon-signal:before { - content: "\f012"; -} -.icon-cog:before { - content: "\f013"; -} -.icon-trash:before { - content: "\f014"; -} -.icon-home:before { - content: "\f015"; -} -.icon-file:before { - content: "\f016"; -} -.icon-time:before { - content: "\f017"; -} -.icon-road:before { - content: "\f018"; -} -.icon-download-alt:before { - content: "\f019"; -} -.icon-download:before { - content: "\f01a"; -} -.icon-upload:before { - content: "\f01b"; -} -.icon-inbox:before { - content: "\f01c"; -} -.icon-play-circle:before { - content: "\f01d"; -} -.icon-repeat:before { - content: "\f01e"; -} -/* \f020 doesn't work in Safari. all shifted one down */ -.icon-refresh:before { - content: "\f021"; -} -.icon-list-alt:before { - content: "\f022"; -} -.icon-lock:before { - content: "\f023"; -} -.icon-flag:before { - content: "\f024"; -} -.icon-headphones:before { - content: "\f025"; -} -.icon-volume-off:before { - content: "\f026"; -} -.icon-volume-down:before { - content: "\f027"; -} -.icon-volume-up:before { - content: "\f028"; -} -.icon-qrcode:before { - content: "\f029"; -} -.icon-barcode:before { - content: "\f02a"; -} -.icon-tag:before { - content: "\f02b"; -} -.icon-tags:before { - content: "\f02c"; -} -.icon-book:before { - content: "\f02d"; -} -.icon-bookmark:before { - content: "\f02e"; -} -.icon-print:before { - content: "\f02f"; -} -.icon-camera:before { - content: "\f030"; -} -.icon-font:before { - content: "\f031"; -} -.icon-bold:before { - content: "\f032"; -} -.icon-italic:before { - content: "\f033"; -} -.icon-text-height:before { - content: "\f034"; -} -.icon-text-width:before { - content: "\f035"; -} -.icon-align-left:before { - content: "\f036"; -} -.icon-align-center:before { - content: "\f037"; -} -.icon-align-right:before { - content: "\f038"; -} -.icon-align-justify:before { - content: "\f039"; -} -.icon-list:before { - content: "\f03a"; -} -.icon-indent-left:before { - content: "\f03b"; -} -.icon-indent-right:before { - content: "\f03c"; -} -.icon-facetime-video:before { - content: "\f03d"; -} -.icon-picture:before { - content: "\f03e"; -} -.icon-pencil:before { - content: "\f040"; -} -.icon-map-marker:before { - content: "\f041"; -} -.icon-adjust:before { - content: "\f042"; -} -.icon-tint:before { - content: "\f043"; -} -.icon-edit:before { - content: "\f044"; -} -.icon-share:before { - content: "\f045"; -} -.icon-check:before { - content: "\f046"; -} -.icon-move:before { - content: "\f047"; -} -.icon-step-backward:before { - content: "\f048"; -} -.icon-fast-backward:before { - content: "\f049"; -} -.icon-backward:before { - content: "\f04a"; -} -.icon-play:before { - content: "\f04b"; -} -.icon-pause:before { - content: "\f04c"; -} -.icon-stop:before { - content: "\f04d"; -} -.icon-forward:before { - content: "\f04e"; -} -.icon-fast-forward:before { - content: "\f050"; -} -.icon-step-forward:before { - content: "\f051"; -} -.icon-eject:before { - content: "\f052"; -} -.icon-chevron-left:before { - content: "\f053"; -} -.icon-chevron-right:before { - content: "\f054"; -} -.icon-plus-sign:before { - content: "\f055"; -} -.icon-minus-sign:before { - content: "\f056"; -} -.icon-remove-sign:before { - content: "\f057"; -} -.icon-ok-sign:before { - content: "\f058"; -} -.icon-question-sign:before { - content: "\f059"; -} -.icon-info-sign:before { - content: "\f05a"; -} -.icon-screenshot:before { - content: "\f05b"; -} -.icon-remove-circle:before { - content: "\f05c"; -} -.icon-ok-circle:before { - content: "\f05d"; -} -.icon-ban-circle:before { - content: "\f05e"; -} -.icon-arrow-left:before { - content: "\f060"; -} -.icon-arrow-right:before { - content: "\f061"; -} -.icon-arrow-up:before { - content: "\f062"; -} -.icon-arrow-down:before { - content: "\f063"; -} -.icon-share-alt:before { - content: "\f064"; -} -.icon-resize-full:before { - content: "\f065"; -} -.icon-resize-small:before { - content: "\f066"; -} -.icon-plus:before { - content: "\f067"; -} -.icon-minus:before { - content: "\f068"; -} -.icon-asterisk:before { - content: "\f069"; -} -.icon-exclamation-sign:before { - content: "\f06a"; -} -.icon-gift:before { - content: "\f06b"; -} -.icon-leaf:before { - content: "\f06c"; -} -.icon-fire:before { - content: "\f06d"; -} -.icon-eye-open:before { - content: "\f06e"; -} -.icon-eye-close:before { - content: "\f070"; -} -.icon-warning-sign:before { - content: "\f071"; -} -.icon-plane:before { - content: "\f072"; -} -.icon-calendar:before { - content: "\f073"; -} -.icon-random:before { - content: "\f074"; -} -.icon-comment:before { - content: "\f075"; -} -.icon-magnet:before { - content: "\f076"; -} -.icon-chevron-up:before { - content: "\f077"; -} -.icon-chevron-down:before { - content: "\f078"; -} -.icon-retweet:before { - content: "\f079"; -} -.icon-shopping-cart:before { - content: "\f07a"; -} -.icon-folder-close:before { - content: "\f07b"; -} -.icon-folder-open:before { - content: "\f07c"; -} -.icon-resize-vertical:before { - content: "\f07d"; -} -.icon-resize-horizontal:before { - content: "\f07e"; -} -.icon-bar-chart:before { - content: "\f080"; -} -.icon-twitter-sign:before { - content: "\f081"; -} -.icon-facebook-sign:before { - content: "\f082"; -} -.icon-camera-retro:before { - content: "\f083"; -} -.icon-key:before { - content: "\f084"; -} -.icon-cogs:before { - content: "\f085"; -} -.icon-comments:before { - content: "\f086"; -} -.icon-thumbs-up:before { - content: "\f087"; -} -.icon-thumbs-down:before { - content: "\f088"; -} -.icon-star-half:before { - content: "\f089"; -} -.icon-heart-empty:before { - content: "\f08a"; -} -.icon-signout:before { - content: "\f08b"; -} -.icon-linkedin-sign:before { - content: "\f08c"; -} -.icon-pushpin:before { - content: "\f08d"; -} -.icon-external-link:before { - content: "\f08e"; -} -.icon-signin:before { - content: "\f090"; -} -.icon-trophy:before { - content: "\f091"; -} -.icon-github-sign:before { - content: "\f092"; -} -.icon-upload-alt:before { - content: "\f093"; -} -.icon-lemon:before { - content: "\f094"; -} -.icon-phone:before { - content: "\f095"; -} -.icon-check-empty:before { - content: "\f096"; -} -.icon-bookmark-empty:before { - content: "\f097"; -} -.icon-phone-sign:before { - content: "\f098"; -} -.icon-twitter:before { - content: "\f099"; -} -.icon-facebook:before { - content: "\f09a"; -} -.icon-github:before { - content: "\f09b"; -} -.icon-unlock:before { - content: "\f09c"; -} -.icon-credit-card:before { - content: "\f09d"; -} -.icon-rss:before { - content: "\f09e"; -} -.icon-hdd:before { - content: "\f0a0"; -} -.icon-bullhorn:before { - content: "\f0a1"; -} -.icon-bell:before { - content: "\f0a2"; -} -.icon-certificate:before { - content: "\f0a3"; -} -.icon-hand-right:before { - content: "\f0a4"; -} -.icon-hand-left:before { - content: "\f0a5"; -} -.icon-hand-up:before { - content: "\f0a6"; -} -.icon-hand-down:before { - content: "\f0a7"; -} -.icon-circle-arrow-left:before { - content: "\f0a8"; -} -.icon-circle-arrow-right:before { - content: "\f0a9"; -} -.icon-circle-arrow-up:before { - content: "\f0aa"; -} -.icon-circle-arrow-down:before { - content: "\f0ab"; -} -.icon-globe:before { - content: "\f0ac"; -} -.icon-wrench:before { - content: "\f0ad"; -} -.icon-tasks:before { - content: "\f0ae"; -} -.icon-filter:before { - content: "\f0b0"; -} -.icon-briefcase:before { - content: "\f0b1"; -} -.icon-fullscreen:before { - content: "\f0b2"; -} -.icon-group:before { - content: "\f0c0"; -} -.icon-link:before { - content: "\f0c1"; -} -.icon-cloud:before { - content: "\f0c2"; -} -.icon-beaker:before { - content: "\f0c3"; -} -.icon-cut:before { - content: "\f0c4"; -} -.icon-copy:before { - content: "\f0c5"; -} -.icon-paper-clip:before { - content: "\f0c6"; -} -.icon-save:before { - content: "\f0c7"; -} -.icon-sign-blank:before { - content: "\f0c8"; -} -.icon-reorder:before { - content: "\f0c9"; -} -.icon-list-ul:before { - content: "\f0ca"; -} -.icon-list-ol:before { - content: "\f0cb"; -} -.icon-strikethrough:before { - content: "\f0cc"; -} -.icon-underline:before { - content: "\f0cd"; -} -.icon-table:before { - content: "\f0ce"; -} -.icon-magic:before { - content: "\f0d0"; -} -.icon-truck:before { - content: "\f0d1"; -} -.icon-pinterest:before { - content: "\f0d2"; -} -.icon-pinterest-sign:before { - content: "\f0d3"; -} -.icon-google-plus-sign:before { - content: "\f0d4"; -} -.icon-google-plus:before { - content: "\f0d5"; -} -.icon-money:before { - content: "\f0d6"; -} -.icon-caret-down:before { - content: "\f0d7"; -} -.icon-caret-up:before { - content: "\f0d8"; -} -.icon-caret-left:before { - content: "\f0d9"; -} -.icon-caret-right:before { - content: "\f0da"; -} -.icon-columns:before { - content: "\f0db"; -} -.icon-sort:before { - content: "\f0dc"; -} -.icon-sort-down:before { - content: "\f0dd"; -} -.icon-sort-up:before { - content: "\f0de"; -} -.icon-envelope-alt:before { - content: "\f0e0"; -} -.icon-linkedin:before { - content: "\f0e1"; -} -.icon-undo:before { - content: "\f0e2"; -} -.icon-legal:before { - content: "\f0e3"; -} -.icon-dashboard:before { - content: "\f0e4"; -} -.icon-comment-alt:before { - content: "\f0e5"; -} -.icon-comments-alt:before { - content: "\f0e6"; -} -.icon-bolt:before { - content: "\f0e7"; -} -.icon-sitemap:before { - content: "\f0e8"; -} -.icon-umbrella:before { - content: "\f0e9"; -} -.icon-paste:before { - content: "\f0ea"; -} -.icon-lightbulb:before { - content: "\f0eb"; -} -.icon-exchange:before { - content: "\f0ec"; -} -.icon-cloud-download:before { - content: "\f0ed"; -} -.icon-cloud-upload:before { - content: "\f0ee"; -} -.icon-user-md:before { - content: "\f0f0"; -} -.icon-stethoscope:before { - content: "\f0f1"; -} -.icon-suitcase:before { - content: "\f0f2"; -} -.icon-bell-alt:before { - content: "\f0f3"; -} -.icon-coffee:before { - content: "\f0f4"; -} -.icon-food:before { - content: "\f0f5"; -} -.icon-file-alt:before { - content: "\f0f6"; -} -.icon-building:before { - content: "\f0f7"; -} -.icon-hospital:before { - content: "\f0f8"; -} -.icon-ambulance:before { - content: "\f0f9"; -} -.icon-medkit:before { - content: "\f0fa"; -} -.icon-fighter-jet:before { - content: "\f0fb"; -} -.icon-beer:before { - content: "\f0fc"; -} -.icon-h-sign:before { - content: "\f0fd"; -} -.icon-plus-sign-alt:before { - content: "\f0fe"; -} -.icon-double-angle-left:before { - content: "\f100"; -} -.icon-double-angle-right:before { - content: "\f101"; -} -.icon-double-angle-up:before { - content: "\f102"; -} -.icon-double-angle-down:before { - content: "\f103"; -} -.icon-angle-left:before { - content: "\f104"; -} -.icon-angle-right:before { - content: "\f105"; -} -.icon-angle-up:before { - content: "\f106"; -} -.icon-angle-down:before { - content: "\f107"; -} -.icon-desktop:before { - content: "\f108"; -} -.icon-laptop:before { - content: "\f109"; -} -.icon-tablet:before { - content: "\f10a"; -} -.icon-mobile-phone:before { - content: "\f10b"; -} -.icon-circle-blank:before { - content: "\f10c"; -} -.icon-quote-left:before { - content: "\f10d"; -} -.icon-quote-right:before { - content: "\f10e"; -} -.icon-spinner:before { - content: "\f110"; -} -.icon-circle:before { - content: "\f111"; -} -.icon-reply:before { - content: "\f112"; -} -.icon-github-alt:before { - content: "\f113"; -} -.icon-folder-close-alt:before { - content: "\f114"; -} -.icon-folder-open-alt:before { - content: "\f115"; -} [class^="icon-"], [class*=" icon-"] { display: inline-block; @@ -7963,45 +7159,39 @@ h4 small { .context-info.editing .module-content { margin-top: 0; } -.hero { - background: url("../../../base/images/background-tile.png"); +.homepage [role=main] { padding: 20px 0; min-height: 0; } -.hero > .container { +.homepage .row { position: relative; - padding-bottom: 0; } -.hero .search-giant { - margin-bottom: 10px; -} -.hero .search-giant input { - border-color: #003f52; -} -.hero .page-heading { - font-size: 18px; - margin-bottom: 0; -} -.hero .module-dark { +.homepage .module-search { padding: 5px; - margin-bottom: 0; + margin: 0; color: #ffffff; background: #ffffff; } -.hero .module-dark .module-content { +.homepage .module-search .search-giant { + margin-bottom: 10px; +} +.homepage .module-search .search-giant input { + border-color: #003f52; +} +.homepage .module-search .module-content { -webkit-border-radius: 3px 3px 0 0; -moz-border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0; background-color: #c14531; border-bottom: none; } -.hero .module-dark .module-content .heading { +.homepage .module-search .module-content .heading { margin-top: 0; margin-bottom: 7px; font-size: 24px; line-height: 40px; } -.hero .tags { +.homepage .module-search .tags { *zoom: 1; padding: 5px 10px 10px 10px; background-color: #983627; @@ -8009,69 +7199,77 @@ h4 small { -moz-border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px; } -.hero .tags:before, -.hero .tags:after { +.homepage .module-search .tags:before, +.homepage .module-search .tags:after { display: table; content: ""; line-height: 0; } -.hero .tags:after { +.homepage .module-search .tags:after { clear: both; } -.hero .tags h3, -.hero .tags .tag { +.homepage .module-search .tags h3, +.homepage .module-search .tags .tag { display: block; float: left; margin: 5px 10px 0 0; } -.hero .tags h3 { +.homepage .module-search .tags h3 { font-size: 14px; line-height: 20px; padding: 2px 8px; } -.hero-primary, -.hero-secondary { +.homepage .group-list { + margin: 0; +} +.homepage .box .inner { + padding: 20px 25px; +} +.homepage .stats h3 { + margin: 0 0 10px 0; +} +.homepage .stats ul { + margin: 0; + list-style: none; + *zoom: 1; +} +.homepage .stats ul:before, +.homepage .stats ul:after { + display: table; + content: ""; + line-height: 0; +} +.homepage .stats ul:after { + clear: both; +} +.homepage .stats ul li { float: left; - margin-left: 20px; - width: 460px; + width: 25%; + font-weight: 300; } -.hero-primary { - margin-left: 0; - margin-bottom: 0; +.homepage .stats ul li a { + display: block; +} +.homepage .stats ul li a b { + display: block; + font-size: 35px; + line-height: 1.5; } -.hero-secondary { +.homepage .stats ul li a:hover { + text-decoration: none; +} +.homepage.layout-1 .row1 .col2 { position: absolute; bottom: 0; right: 0; } -.hero-secondary .hero-secondary-inner { +.homepage.layout-1 .row1 .col2 .module-search { bottom: 0; left: 0; right: 0; } -.main.homepage { - padding-top: 20px; - padding-bottom: 20px; - border-top: 1px solid #cccccc; -} -.main.homepage .module-heading .media-image { - margin-right: 15px; - max-height: 53px; - -webkit-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - -moz-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5); -} -.main.homepage .group-listing .box { - min-height: 275px; -} -.main.homepage .group-list { - margin-bottom: 0; -} -.main.homepage .group-list .dataset-content { - min-height: 70px; -} -.main.homepage .box .module { - margin-top: 0; +.homepage.layout-2 .stats { + margin-top: 20px; } .account-masthead { *zoom: 1; @@ -8920,6 +8118,11 @@ iframe { padding-bottom: 20px !important; margin-bottom: 0 !important; } +.ie8 .lang-dropdown, +.ie7 .lang-dropdown { + position: relative !important; + top: -90px !important; +} .ie7 .alert { position: relative; } @@ -9061,11 +8264,6 @@ iframe { .ie7 .module-heading .media-content { position: relative; } -.ie7 .module-heading .action { - position: absolute; - top: 9px; - right: 10px; -} .ie7 .module-heading .media-image img { float: left; } diff --git a/ckan/public/base/javascript/client.js b/ckan/public/base/javascript/client.js index 45940c0086d..e1c60ccf24e 100644 --- a/ckan/public/base/javascript/client.js +++ b/ckan/public/base/javascript/client.js @@ -370,7 +370,7 @@ }); ckan.sandbox.setup(function (instance) { - instance.client = new Client({endpoint: ckan.API_ROOT}); + instance.client = new Client({endpoint: ckan.SITE_ROOT}); }); ckan.Client = Client; diff --git a/ckan/public/base/javascript/main.js b/ckan/public/base/javascript/main.js index e307fe8403c..c5f36ef7176 100644 --- a/ckan/public/base/javascript/main.js +++ b/ckan/public/base/javascript/main.js @@ -29,13 +29,13 @@ this.ckan = this.ckan || {}; ckan.SITE_ROOT = getRootFromData('siteRoot'); ckan.LOCALE_ROOT = getRootFromData('localeRoot'); - ckan.API_ROOT = getRootFromData('apiRoot'); // Load the localisations before instantiating the modules. ckan.sandbox().client.getLocaleData(locale).done(function (data) { ckan.i18n.load(data); ckan.module.initialize(); }); + jQuery('[data-target="popover"]').popover(); }; /* Returns a full url for the current site with the provided path appended. diff --git a/ckan/public/base/javascript/modules/autocomplete.js b/ckan/public/base/javascript/modules/autocomplete.js index 768a708c9c3..648fe5960d0 100644 --- a/ckan/public/base/javascript/modules/autocomplete.js +++ b/ckan/public/base/javascript/modules/autocomplete.js @@ -86,6 +86,8 @@ this.ckan.module('autocomplete', function (jQuery, _) { $('.select2-choice', select2.container).on('click', function() { return false; }); + + this._select2 = select2; }, /* Looks up the completions for the current search term and passes them @@ -140,29 +142,28 @@ this.ckan.module('autocomplete', function (jQuery, _) { // old data. this._lastTerm = string; + // Kills previous timeout + clearTimeout(this._debounced); + + // OK, wipe the dropdown before we start ajaxing the completions + fn({results:[]}); + if (string) { - if (!this._debounced) { - // Set a timer to prevent the search lookup occurring too often. - this._debounced = setTimeout(function () { - var term = module._lastTerm; + // Set a timer to prevent the search lookup occurring too often. + this._debounced = setTimeout(function () { + var term = module._lastTerm; - delete module._debounced; + // Cancel the previous request if it hasn't yet completed. + if (module._last && typeof module._last.abort == 'function') { + module._last.abort(); + } - // Cancel the previous request if it hasn't yet completed. - if (module._last) { - module._last.abort(); - } + module._last = module.getCompletions(term, fn); + }, this.options.interval); - module._last = module.getCompletions(term, function (terms) { - fn(module._lastResults = terms); - }); - }, this.options.interval); - } else { - // Re-use the last set of terms. - fn(this._lastResults || {results: []}); - } - } else { - fn({results: []}); + // This forces the ajax throbber to appear, because we've called the + // callback already and that hides the throbber + $('.select2-search input', this._select2.dropdown).addClass('select2-active'); } }, diff --git a/ckan/public/base/javascript/modules/image-upload.js b/ckan/public/base/javascript/modules/image-upload.js new file mode 100644 index 00000000000..96308ed5764 --- /dev/null +++ b/ckan/public/base/javascript/modules/image-upload.js @@ -0,0 +1,184 @@ +/* Image Upload + * + */ +this.ckan.module('image-upload', function($, _) { + return { + /* options object can be extended using data-module-* attributes */ + options: { + is_url: true, + has_image: false, + field_upload: 'input[name="image_upload"]', + field_url: 'input[name="image_url"]', + field_clear: 'input[name="clear_upload"]', + i18n: { + upload: _('From computer'), + url: _('From web'), + remove: _('Remove'), + label: _('Upload image'), + label_url: _('Image URL'), + remove_tooltip: _('Reset this') + }, + template: [ + '' + ].join("\n") + }, + + state: { + attached: 1, + blank: 2, + web: 3 + }, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function () { + $.proxyAll(this, /_on/); + var options = this.options; + + // firstly setup the fields + this.input = $(options.field_upload, this.el); + this.field_url = $(options.field_url, this.el).parents('.control-group'); + this.field_image = this.input.parents('.control-group'); + + // Is there a clear checkbox on the form already? + var checkbox = $(options.field_clear, this.el); + if (checkbox.length > 0) { + options.has_image = true; + checkbox.parents('.control-group').remove(); + } + + // Adds the hidden clear input to the form + this.field_clear = $('') + .appendTo(this.el); + + // Button to set the field to be a URL + this.button_url = $(' '+this.i18n('url')+'') + .on('click', this._onFromWeb) + .insertAfter(this.input); + + // Button to attach local file to the form + this.button_upload = $(''+this.i18n('upload')+'') + .insertAfter(this.input); + + // Button to reset the form back to the first from when there is a image uploaded + this.button_remove = $('') + .text(this.i18n('remove')) + .on('click', this._onRemove) + .insertAfter(this.button_upload); + + // Button for resetting the form when there is a URL set + $('') + .prop('title', this.i18n('remove_tooltip')) + .on('click', this._onRemove) + .insertBefore($('input', this.field_url)); + + // Update the main label + $('label[for="field-image-upload"]').text(this.i18n('label')); + + // Setup the file input + this.input + .on('mouseover', this._onInputMouseOver) + .on('mouseout', this._onInputMouseOut) + .on('change', this._onInputChange) + .css('width', this.button_upload.outerWidth()); + + // Fields storage. Used in this.changeState + this.fields = $('') + .add(this.button_remove) + .add(this.button_upload) + .add(this.button_url) + .add(this.input) + .add(this.field_url) + .add(this.field_image); + + // Setup the initial state + if (options.is_url) { + this.changeState(this.state.web); + } else if (options.has_image) { + this.changeState(this.state.attached); + } else { + this.changeState(this.state.blank); + } + + }, + + /* Method to change the display state of the image fields + * + * state - Pseudo constant for passing the state we should be in now + * + * Examples + * + * this.changeState(this.state.web); // Sets the state in URL mode + * + * Returns nothing. + */ + changeState: function(state) { + this.fields.hide(); + if (state == this.state.blank) { + this.button_upload + .add(this.field_image) + .add(this.button_url) + .add(this.input) + .show(); + } else if (state == this.state.attached) { + this.button_remove + .add(this.field_image) + .show(); + } else if (state == this.state.web) { + this.field_url + .show(); + } + }, + + /* Event listener for when someone sets the field to URL mode + * + * Returns nothing. + */ + _onFromWeb: function() { + this.changeState(this.state.web); + $('input', this.field_url).focus(); + if (this.options.has_image) { + this.field_clear.val('true'); + } + }, + + /* Event listener for resetting the field back to the blank state + * + * Returns nothing. + */ + _onRemove: function() { + this.changeState(this.state.blank); + $('input', this.field_url).val(''); + this.field_clear.val('true'); + }, + + /* Event listener for when someone chooses a file to upload + * + * Returns nothing. + */ + _onInputChange: function() { + this.file_name = this.input.val(); + this.field_clear.val(''); + this.changeState(this.state.attached); + }, + + /* Event listener for when a user mouseovers the hidden file input + * + * Returns nothing. + */ + _onInputMouseOver: function() { + this.button_upload.addClass('hover'); + }, + + /* Event listener for when a user mouseouts the hidden file input + * + * Returns nothing. + */ + _onInputMouseOut: function() { + this.button_upload.removeClass('hover'); + } + + } +}); diff --git a/ckan/public/base/javascript/resource.config b/ckan/public/base/javascript/resource.config index b1a7302d9e8..564da811a6e 100644 --- a/ckan/public/base/javascript/resource.config +++ b/ckan/public/base/javascript/resource.config @@ -38,6 +38,7 @@ ckan = modules/table-toggle-more.js modules/dataset-visibility.js modules/media-grid.js + modules/image-upload.js main = apply_html_class diff --git a/ckan/public/base/less/activity.less b/ckan/public/base/less/activity.less index dc75ede5e37..6071fabc5f0 100644 --- a/ckan/public/base/less/activity.less +++ b/ckan/public/base/less/activity.less @@ -56,6 +56,9 @@ .border-radius(100px); .box-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); } + &.no-avatar p { + margin-left: 40px; + } } .load-less { margin-bottom: 15px; @@ -76,11 +79,26 @@ float: right; text-decoration: none; } + .popover-content { + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @layoutTextColor; + word-break: break-all; + dl { + margin: 0; + dd { + margin-left: 0; + margin-bottom: 10px; + } + } + } } // colors .activity .item { & .icon { background-color: @activityColorBlank; } // Non defined + &.failure .icon { background-color: @activityColorDelete; } + &.success .icon { background-color: @activityColorNew; } &.added-tag .icon { background-color: spin(@activityColorNew, 60); } &.changed-group .icon { background-color: @activityColorModify; } &.changed-package .icon { background-color: spin(@activityColorModify, 20); } diff --git a/ckan/public/base/less/ckan.less b/ckan/public/base/less/ckan.less index f7c108f8734..f842bd6dbae 100644 --- a/ckan/public/base/less/ckan.less +++ b/ckan/public/base/less/ckan.less @@ -20,6 +20,7 @@ @import "activity.less"; @import "dropdown.less"; @import "dashboard.less"; +@import "datapusher.less"; body { // Using the masthead/footer gradient prevents the color from changing diff --git a/ckan/public/base/less/datapusher.less b/ckan/public/base/less/datapusher.less new file mode 100644 index 00000000000..f147c0c3f6c --- /dev/null +++ b/ckan/public/base/less/datapusher.less @@ -0,0 +1,18 @@ +.datapusher-status-link:hover { + text-decoration: none; +} + +.datapusher-status { + &.status-unknown { + color: #bbb; + } + &.status-pending { + color: #FFCC00; + } + &.status-error { + color: red; + } + &.status-complete { + color: #009900; + } +} \ No newline at end of file diff --git a/ckan/public/base/less/forms.less b/ckan/public/base/less/forms.less index 289439c291a..ba02fb6b49e 100644 --- a/ckan/public/base/less/forms.less +++ b/ckan/public/base/less/forms.less @@ -670,3 +670,71 @@ textarea { .box-shadow(none); } } + +.js .image-upload { + #field-image-upload { + cursor: pointer; + position: absolute; + z-index: 1; + .opacity(0); + } + .controls { + position: relative; + } + .btn { + position: relative; + top: 0; + margin-right: 10px; + &.hover { + color: @grayDark; + text-decoration: none; + background-position: 0 -15px; + .transition(background-position .1s linear); + } + } + .btn-remove-url { + position: absolute; + margin-right: 0; + top: 4px; + right: 5px; + padding: 0 4px; + .border-radius(100px); + .icon-remove { + margin-right: 0; + } + } +} +.add-member-form .control-label { + width: 100%; + text-align: left; +} + +.add-member-form .controls { + margin-left: auto; +} + +.add-member-or { + float: left; + margin-top: 75px; + width: 7%; + text-align: center; + text-transform: uppercase; + color: @grayLight; + font-weight: bold; +} + +.add-member-form .row-fluid .control-group { + float: left; + width: 45%; +} + +.add-member-form .row-fluid { + .select2-container, input { + width: 100% !important; + } +} + +#recaptcha_table { + table-layout: inherit; + line-height: 1; +} diff --git a/ckan/public/base/less/homepage.less b/ckan/public/base/less/homepage.less index 0aaa8a839ab..3315dfd7772 100644 --- a/ckan/public/base/less/homepage.less +++ b/ckan/public/base/less/homepage.less @@ -1,26 +1,25 @@ -.hero { - background: url("@{imagePath}/background-tile.png"); - padding: 20px 0; - min-height: 0; - > .container { - position: relative; - padding-bottom: 0; - } - .search-giant { - margin-bottom: 10px; - input { - border-color: darken(@mastheadBackgroundColorEnd, 5); - } +.homepage { + + [role=main] { + padding: 20px 0; + min-height: 0; } - .page-heading { - font-size: 18px; - margin-bottom: 0; + + .row { + position: relative; } - .module-dark { + + .module-search { padding: 5px; - margin-bottom: 0; + margin: 0; color: @mastheadTextColor; background: @layoutTrimBackgroundColor; + .search-giant { + margin-bottom: 10px; + input { + border-color: darken(@mastheadBackgroundColorEnd, 5); + } + } .module-content { .border-radius(3px 3px 0 0); background-color: @mastheadBackgroundColor; @@ -32,66 +31,72 @@ line-height: 40px; } } - } - .tags { - .clearfix(); - padding: 5px 10px 10px 10px; - background-color: darken(@mastheadBackgroundColor, 10%); - .border-radius(0 0 3px 3px); - h3, - .tag { - display: block; - float: left; - margin: 5px 10px 0 0; - } - h3 { - font-size: @baseFontSize; - line-height: @baseLineHeight; - padding: 2px 8px; + .tags { + .clearfix(); + padding: 5px 10px 10px 10px; + background-color: darken(@mastheadBackgroundColor, 10%); + .border-radius(0 0 3px 3px); + h3, + .tag { + display: block; + float: left; + margin: 5px 10px 0 0; + } + h3 { + font-size: @baseFontSize; + line-height: @baseLineHeight; + padding: 2px 8px; + } } } -} - -.hero-primary, -.hero-secondary { - .makeColumn(6); -} -.hero-primary { - margin-left: 0; // Remove grid margin. - margin-bottom: 0; -} - -.hero-secondary { - position: absolute; - bottom: 0; - right: 0; - .hero-secondary-inner { - bottom: 0; - left: 0; - right: 0; + .group-list { + margin: 0; } -} -.main.homepage { - padding-top: 20px; - padding-bottom: 20px; - border-top: 1px solid @layoutTrimBorderColor; - .module-heading .media-image { - margin-right: 15px; - max-height: 53px; - .box-shadow(0 0 0 2px rgba(255, 255, 255, 0.5)); + .box .inner { + padding: @gutterY @gutterX; } - .group-listing .box { - min-height: 275px; + + .stats { + h3 { + margin: 0 0 10px 0; + } + ul { + .unstyled; + .clearfix; + li { + float: left; + width: 25%; + font-weight: 300; + a { + display: block; + b { + display: block; + font-size: @fontSizeLarge*2; + line-height: 1.5; + } + &:hover { + text-decoration: none; + } + } + } + } } - .group-list { - margin-bottom: 0; - .dataset-content { - min-height: 70px; + + &.layout-1 .row1 .col2 { + position: absolute; + bottom: 0; + right: 0; + .module-search { + bottom: 0; + left: 0; + right: 0; } } - .box .module { - margin-top: 0; + + &.layout-2 .stats { + margin-top: @gutterY; } + } diff --git a/ckan/public/base/less/iehacks.less b/ckan/public/base/less/iehacks.less index 232ee72ee9c..7d9bc5cc274 100644 --- a/ckan/public/base/less/iehacks.less +++ b/ckan/public/base/less/iehacks.less @@ -200,11 +200,6 @@ .media-content { position: relative; } - .action { - position: absolute; - top: 9px; - right: 10px; - } .media-image img { float: left; } diff --git a/ckan/public/base/less/module.less b/ckan/public/base/less/module.less index a575abcbda4..aa964cf5ea3 100644 --- a/ckan/public/base/less/module.less +++ b/ckan/public/base/less/module.less @@ -13,17 +13,6 @@ border-bottom: 1px solid @moduleHeadingBorderColor; } -.module-heading .action { - float: right; - color: @moduleHeadingActionTextColor; - font-size: 12px; - line-height: @baseLineHeight; - text-decoration: underline; - &:hover { - color: @layoutTextColor; - } -} - .module-content { padding: 0 @gutterX; margin: 20px 0; diff --git a/ckan/public/base/test/spec/modules/autocomplete.spec.js b/ckan/public/base/test/spec/modules/autocomplete.spec.js index 2a9c63ab45c..93a8fb57a54 100644 --- a/ckan/public/base/test/spec/modules/autocomplete.spec.js +++ b/ckan/public/base/test/spec/modules/autocomplete.spec.js @@ -152,10 +152,11 @@ describe('ckan.modules.AutocompleteModule()', function () { beforeEach(function () { sinon.stub(this.module, 'getCompletions'); this.target = sinon.spy(); + this.module.setupAutoComplete(); }); it('should set the _lastTerm property', function () { - this.module.lookup('term'); + this.module.lookup('term', this.target); assert.equal(this.module._lastTerm, 'term'); }); diff --git a/ckan/templates/admin/config.html b/ckan/templates/admin/config.html index 4f516b95ac8..bfa9b3a2ee5 100644 --- a/ckan/templates/admin/config.html +++ b/ckan/templates/admin/config.html @@ -4,7 +4,9 @@ {% block primary_content_inner %}
- {{ autoform.generate(form_items, data, errors) }} + {% block admin_form %} + {{ autoform.generate(form_items, data, errors) }} + {% endblock %}
{% set locale = h.dump_json({'content': _('Are you sure you want to reset the config?')}) %} {{ _('Reset') }} @@ -20,6 +22,7 @@

{{ _('CKAN config options') }}

+ {% block admin_form_help %} {% set about_url = h.url_for(controller='home', action='about') %} {% set home_url = h.url_for(controller='home', action='index') %} {% set docs_url = "http://docs.ckan.org/en/{0}/theming.html".format(g.ckan_doc_version) %} @@ -38,7 +41,10 @@

<head> tag of every page. If you wish to customize the templates more fully we recommend reading the documentation.

+

Homepage: This is for choosing a predefined layout for + the modules that appear on your homepage.

{% endtrans %} + {% endblock %}

{% endblock %} diff --git a/ckan/templates/base.html b/ckan/templates/base.html index 6012bf65928..b6290af0ad0 100644 --- a/ckan/templates/base.html +++ b/ckan/templates/base.html @@ -85,7 +85,7 @@ {# Allows custom attributes to be added to the tag #} - + {# The page block allows you to add content to the page. Most of the time it is diff --git a/ckan/templates/development/snippets/facet.html b/ckan/templates/development/snippets/facet.html index ac9b9df1a0c..7280bf9a3d2 100644 --- a/ckan/templates/development/snippets/facet.html +++ b/ckan/templates/development/snippets/facet.html @@ -1,6 +1,6 @@
{% with items=(("First", true), ("Second", false), ("Third", true), ("Fourth", false), ("Last", false)) %} -

Facet List Clear All

+

Facet List

diff --git a/ckan/templates/home/index.html b/ckan/templates/home/index.html index 324a607ec0b..b28ac82c05d 100644 --- a/ckan/templates/home/index.html +++ b/ckan/templates/home/index.html @@ -1,99 +1,18 @@ {% extends "page.html" %} +{% set homepage_style = ( g.homepage_style or '1' ) %} {% block subtitle %}{{ _("Welcome") }}{% endblock %} {% block maintag %}{% endblock %} +{% block toolbar %}{% endblock %} {% block content %} -
-
- {{ self.flash() }} - {{ self.primary_content() }} -
-
-
+
- {{ self.secondary_content() }} -
-
-{% endblock %} - -{% block primary_content %} -
-
- {% block home_primary %} -
- {% if g.site_intro_text %} - {{ h.render_markdown(g.site_intro_text) }} - {% else %} - {% block home_primary_content %} -

{% block home_primary_heading %}{{ _("Welcome to CKAN") }}{% endblock %}

-

- {% block home_primary_text %} - {% trans %}This is a nice introductory paragraph about CKAN or the site - in general. We don't have any copy to go here yet but soon we will - {% endtrans %} - {% endblock %} -

- {% endblock %} - {% endif %} -
- {% endblock %} - - {% block home_image %} - - {% endblock %} -
-
-
-
- {% block home_secondary_content %} -
- {% block home_search %} - -

{{ _("Search Your Data") }}

-
- - -
- - {% endblock %} - {% block home_tags %} -
-

{{ _('Popular Tags') }}

- {% set tags = h.get_facet_items_dict('tags', limit=3) %} - {% for tag in tags %} - {{ h.truncate(tag.display_name, 22) }} - {% endfor %} -
- {% endblock %} -
- {% endblock %} + {{ self.flash() }}
+ {% block primary_content %} + {% snippet "home/layout{0}.html".format(homepage_style) %} + {% endblock %}
{% endblock %} - -{% block secondary_content %} -
- {% for group in c.group_package_stuff %} -
-
- {% snippet 'snippets/group_item.html', group=group.group_dict, truncate=50, truncate_title=35 %} -
-
- {% endfor %} -
-{% endblock %} - -{# Remove the toolbar. #} -{% block toolbar %}{% endblock %} diff --git a/ckan/templates/home/layout1.html b/ckan/templates/home/layout1.html new file mode 100644 index 00000000000..53ac0d390bb --- /dev/null +++ b/ckan/templates/home/layout1.html @@ -0,0 +1,24 @@ +
+
+
+
+ {% snippet 'home/snippets/promoted.html' %} +
+
+ {% snippet 'home/snippets/search.html' %} +
+
+
+
+
+
+
+
+ {% snippet 'home/snippets/featured_group.html' %} +
+
+ {% snippet 'home/snippets/featured_organization.html' %} +
+
+
+
diff --git a/ckan/templates/home/layout2.html b/ckan/templates/home/layout2.html new file mode 100644 index 00000000000..e1b62bea09f --- /dev/null +++ b/ckan/templates/home/layout2.html @@ -0,0 +1,25 @@ +
+
+
+
+ {% snippet 'home/snippets/search.html' %} + {% snippet 'home/snippets/stats.html' %} +
+
+ {% snippet 'home/snippets/promoted.html' %} +
+
+
+
+
+
+
+
+ {% snippet 'home/snippets/featured_organization.html' %} +
+
+ {% snippet 'home/snippets/featured_group.html' %} +
+
+
+
diff --git a/ckan/templates/home/layout3.html b/ckan/templates/home/layout3.html new file mode 100644 index 00000000000..80c10c22ba0 --- /dev/null +++ b/ckan/templates/home/layout3.html @@ -0,0 +1,17 @@ +
+
+ {% snippet 'home/snippets/search.html' %} +
+
+
+
+
+
+ {% snippet 'home/snippets/promoted.html' %} +
+
+ {% snippet 'home/snippets/stats.html' %} +
+
+
+
diff --git a/ckan/templates/home/snippets/featured_group.html b/ckan/templates/home/snippets/featured_group.html new file mode 100644 index 00000000000..d4069faf618 --- /dev/null +++ b/ckan/templates/home/snippets/featured_group.html @@ -0,0 +1,7 @@ +{% set groups = h.get_featured_groups() %} + +{% for group in groups %} +
+ {% snippet 'snippets/group_item.html', group=group, truncate=50, truncate_title=35 %} +
+{% endfor %} diff --git a/ckan/templates/home/snippets/featured_organization.html b/ckan/templates/home/snippets/featured_organization.html new file mode 100644 index 00000000000..7cf9ddb7482 --- /dev/null +++ b/ckan/templates/home/snippets/featured_organization.html @@ -0,0 +1,7 @@ +{% set organizations = h.get_featured_organizations() %} + +{% for organization in organizations %} +
+ {% snippet 'snippets/organization_item.html', organization=organization, truncate=50, truncate_title=35 %} +
+{% endfor %} diff --git a/ckan/templates/home/snippets/promoted.html b/ckan/templates/home/snippets/promoted.html new file mode 100644 index 00000000000..fc09d9a6300 --- /dev/null +++ b/ckan/templates/home/snippets/promoted.html @@ -0,0 +1,22 @@ +{% set intro = g.site_intro_text %} + +
+
+ {% if intro %} + {{ h.render_markdown(intro) }} + {% else %} +

{{ _("Welcome to CKAN") }}

+

+ {% trans %}This is a nice introductory paragraph about CKAN or the site + in general. We don't have any copy to go here yet but soon we will + {% endtrans %} +

+ {% endif %} +
+ +
diff --git a/ckan/templates/home/snippets/search.html b/ckan/templates/home/snippets/search.html new file mode 100644 index 00000000000..e4a72c7e074 --- /dev/null +++ b/ckan/templates/home/snippets/search.html @@ -0,0 +1,21 @@ +{% set tags = h.get_facet_items_dict('tags', limit=3) %} +{% set placeholder = _('eg. Gold Prices') %} + + diff --git a/ckan/templates/home/snippets/stats.html b/ckan/templates/home/snippets/stats.html new file mode 100644 index 00000000000..9c5c7fa6970 --- /dev/null +++ b/ckan/templates/home/snippets/stats.html @@ -0,0 +1,33 @@ +{% set stats = h.get_site_statistics() %} + + diff --git a/ckan/templates/macros/form.html b/ckan/templates/macros/form.html index 497dea6bf02..db93f046961 100644 --- a/ckan/templates/macros/form.html +++ b/ckan/templates/macros/form.html @@ -395,3 +395,30 @@

{% endmacro %} +{# +Builds a file upload for input + +Example + {% import 'macros/form.html' as form %} + {{ form.image_upload(data, errors, is_upload_enabled=true) }} + +#} +{% macro image_upload(data, errors, field_url='image_url', field_upload='image_upload', field_clear='clear_upload', image_url=false, is_upload_enabled=false, placeholder=false) %} + {% set placeholder = placeholder if placeholder else _('http://example.com/my-image.jpg') %} + {% set has_uploaded_data = data.get(field_url) and not data[field_url].startswith('http') %} + {% set is_url = data.get(field_url) and data[field_url].startswith('http') %} + + {% if is_upload_enabled %}
{% endif %} + + {{ input(field_url, label=_('Image URL'), id='field-image-url', placeholder=placeholder, value=data.get(field_url), error=errors.get(field_url), classes=['control-full']) }} + + {% if is_upload_enabled %} + {{ input(field_upload, label=_('Image Upload'), id='field-image-upload', type='file', placeholder='', value='', error='', classes=['control-full']) }} + {% if has_uploaded_data %} + {{ checkbox(field_clear, label=_('Clear Upload'), id='field-clear-upload', value='true', error='', classes=['control-full']) }} + {% endif %} + {% endif %} + + {% if is_upload_enabled %}
{% endif %} + +{% endmacro %} diff --git a/ckan/templates/organization/member_new.html b/ckan/templates/organization/member_new.html index 8c1a21c6751..a121549dab4 100644 --- a/ckan/templates/organization/member_new.html +++ b/ckan/templates/organization/member_new.html @@ -12,15 +12,42 @@

{% block page_heading %}{{ _('Edit Member') if user else _('Add Member') }}{% endblock %}

{% block form %} -
- {% if user %} - - {% set format_attrs = {'disabled': true} %} - {{ form.input('username', label=_('User'), value=user.name, classes=['control-medium'], attrs=format_attrs) }} - {% else %} - {% set format_attrs = {'data-module': 'autocomplete', 'data-module-source': '/api/2/util/user/autocomplete?q=?'} %} - {{ form.input('username', id='field-username', label=_('User'), placeholder=_('Username'), value='', error='', classes=['control-medium'], attrs=format_attrs) }} - {% endif %} + +
+
+ + + {{ _('If you wish to add an existing user, search for their username below.') }} + +
+ {% if user %} + + + {% else %} + + {% endif %} +
+
+
+ {{ _('or') }} +
+
+ + + {{ _('If you wish to invite a new user, enter their email address.') }} + +
+ +
+
+
{% set format_attrs = {'data-module': 'autocomplete'} %} {{ form.select('role', label=_('Role'), options=c.roles, selected=c.user_role, error='', attrs=format_attrs) }}
diff --git a/ckan/templates/organization/snippets/helper.html b/ckan/templates/organization/snippets/helper.html index f0acbd46d73..6ec71c0d67e 100644 --- a/ckan/templates/organization/snippets/helper.html +++ b/ckan/templates/organization/snippets/helper.html @@ -4,14 +4,12 @@

{{ _('What are Organizations?') }}

- {% trans %} -

Organizations act like publishing departments for datasets (for - example, the Department of Health). This means that datasets can be - published by and belong to a department instead of an individual - user.

-

Within organizations, admins can assign roles and authorisation its - members, giving individual users the right to publish datasets from - that particular organisation (e.g. Office of National Statistics).

- {% endtrans %} +

+ {% trans %} + CKAN Organizations are used to create, manage and publish collections + of datasets. Users can have different roles within an Organization, + depending on their level of authorisation to create, edit and publish. + {% endtrans %} +

\ No newline at end of file diff --git a/ckan/templates/organization/snippets/organization_form.html b/ckan/templates/organization/snippets/organization_form.html index 0a2134e89f7..0d62e36cac1 100644 --- a/ckan/templates/organization/snippets/organization_form.html +++ b/ckan/templates/organization/snippets/organization_form.html @@ -19,14 +19,7 @@ {{ form.markdown('description', label=_('Description'), id='field-description', placeholder=_('A little information about my organization...'), value=data.description, error=errors.description) }} - {{ form.input('image_url', label=_('Image URL'), id='field-image-url', placeholder=_('http://example.com/my-image.jpg'), value=data.image_url, error=errors.image_url, classes=['control-full']) }} - - {% if h.uploads_enabled() %} - {{ form.input('image_upload', label=_('Image Upload'), id='field-image-upload', type='file', placeholder='', value=data.image_url, error='', classes=['control-full']) }} - {% if data.image_url and not data.image_url.startswith('http') %} - {{ form.checkbox('clear_upload', label=_('Clear Upload'), id='field-clear-upload', value='true', error='', classes=['control-full']) }} - {% endif %} - {% endif %} + {{ form.image_upload(data, errors, image_url=c.group_dict.image_display_url, is_upload_enabled=h.uploads_enabled()) }} {% endblock %} @@ -78,8 +71,8 @@ {% endblock %} #} -
{{ form.required_message() }} +
{% block delete_button %} {% if h.check_access('organization_delete', {'id': data.id}) %} {% set locale = h.dump_json({'content': _('Are you sure you want to delete this Organization? This will delete all the public and private datasets belonging to this organization.')}) %} diff --git a/ckan/templates/organization/snippets/organization_item.html b/ckan/templates/organization/snippets/organization_item.html index 8118fa9a1c6..5d774cb7f39 100644 --- a/ckan/templates/organization/snippets/organization_item.html +++ b/ckan/templates/organization/snippets/organization_item.html @@ -14,7 +14,7 @@ {% set url = h.url_for(organization.type ~ '_read', action='read', id=organization.name) %}
  • {% block image %} - {{ organization.name }} + {{ organization.name }} {% endblock %} {% block title %}

    {{ organization.display_name }}

    diff --git a/ckan/templates/package/base_form_page.html b/ckan/templates/package/base_form_page.html index 049d83b1ebb..b4d7dc440c0 100644 --- a/ckan/templates/package/base_form_page.html +++ b/ckan/templates/package/base_form_page.html @@ -2,8 +2,11 @@ {% block primary_content %}
    + {% block page_header %}{% endblock %}
    - {% block form %}{{ c.form | safe }}{% endblock %} + {% block primary_content_inner %} + {% block form %}{{ c.form | safe }}{% endblock %} + {% endblock %}
    {% endblock %} @@ -15,9 +18,9 @@

    {{ _('What are dataset

    {% trans %} - Datasets are simply used to group related pieces of data. These - can then be found under a single url with a description and - licensing information. + A CKAN Dataset is a collection of data resources (such as files), + together with a description and other information, at a fixed URL. + Datasets are what users see when searching for data. {% endtrans %}

    diff --git a/ckan/templates/package/edit.html b/ckan/templates/package/edit.html index 4f15985d033..c3a7a5074e5 100644 --- a/ckan/templates/package/edit.html +++ b/ckan/templates/package/edit.html @@ -1,28 +1,5 @@ -{% extends 'package/base_form_page.html' %} +{% extends 'package/edit_base.html' %} -{% set pkg = c.pkg_dict %} - -{% block breadcrumb_content_selected %}{% endblock %} - -{% block breadcrumb_content %} - {{ super() }} -
  • {% link_for _('Edit'), controller='package', action='edit', id=pkg.name %}
  • -{% endblock %} - -{% block resources_module %} - {% snippet 'package/snippets/resources.html', pkg=pkg, action='resource_edit' %} -{% endblock %} - -{% block content_action %} - {% link_for _('View dataset'), controller='package', action='read', id=pkg.name, class_='btn', icon='eye-open' %} +{% block primary_content_inner %} + {% block form %}{{ c.form | safe }}{% endblock %} {% endblock %} - -{% block secondary_content %} - {% snippet 'package/snippets/info.html', pkg=pkg, action='package_edit' %} -{% endblock %} - -{% block primary_content %} - - {{ super() }} -{% endblock %} - diff --git a/ckan/templates/package/edit_base.html b/ckan/templates/package/edit_base.html new file mode 100644 index 00000000000..10c1aa977ee --- /dev/null +++ b/ckan/templates/package/edit_base.html @@ -0,0 +1,24 @@ +{% extends 'package/base.html' %} + +{% set pkg = c.pkg_dict %} +{% set pkg_dict = c.pkg_dict %} + +{% block breadcrumb_content_selected %}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} +
  • {% link_for _('Edit'), controller='package', action='edit', id=pkg.name %}
  • +{% endblock %} + +{% block content_action %} + {% link_for _('View dataset'), controller='package', action='read', id=pkg.name, class_='btn', icon='eye-open' %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon('dataset_edit', _('Edit metadata'), id=pkg.name) }} + {{ h.build_nav_icon('dataset_resources', _('Resources'), id=pkg.name) }} +{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/info.html', pkg=pkg, hide_follow_button=true %} +{% endblock %} diff --git a/ckan/templates/package/new_resource.html b/ckan/templates/package/new_resource.html index 13cce195453..edd34bb5213 100644 --- a/ckan/templates/package/new_resource.html +++ b/ckan/templates/package/new_resource.html @@ -1,10 +1,6 @@ {% extends "package/base_form_page.html" %} -{% if c.userobj %} - {% set logged_in = true %} -{% else %} - {% set logged_in = false %} -{% endif %} +{% set logged_in = true if c.userobj else false %} {% block subtitle %}{{ _('Add data to the dataset') }}{% endblock %} @@ -17,26 +13,10 @@ {% block form %}{% snippet 'package/snippets/resource_form.html', data=data, errors=errors, error_summary=error_summary, include_metadata=false, pkg_name=pkg_name, stage=stage, allow_upload=g.ofs_impl and logged_in %}{% endblock %} {% block secondary_content %} - {% if pkg_dict and pkg_dict.state != 'draft' %} - {% snippet 'package/snippets/info.html', pkg=pkg_dict, action='resource_new' %} - {% else %} -
    -

    {{ _('What\'s a resource?') }}

    -
    -

    {{ _('A resource can be any file or link to a file containing useful data.') }}

    -
    -
    - {% endif %} + {% snippet 'package/snippets/resource_help.html' %} {% endblock %} {% block scripts %} {{ super() }} {% resource 'vendor/fileupload' %} {% endblock %} - -{% block primary_content %} - {% if pkg_dict and pkg_dict.state != 'draft' %} - - {% endif %} - {{ super() }} -{% endblock %} diff --git a/ckan/templates/package/new_resource_not_draft.html b/ckan/templates/package/new_resource_not_draft.html new file mode 100644 index 00000000000..7e1638c51e7 --- /dev/null +++ b/ckan/templates/package/new_resource_not_draft.html @@ -0,0 +1,20 @@ +{% extends "package/resource_edit_base.html" %} + +{% block subtitle %}{{ _('Add resource') }} - {{ h.dataset_display_name(pkg) }}{% endblock %} +{% block form_title %}{{ _('Add resource') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ _('Add New Resource') }}
  • +{% endblock %} + +{% block form %} + {% snippet 'package/snippets/resource_form.html', data=data, errors=errors, error_summary=error_summary, include_metadata=false, pkg_name=pkg_name, stage=stage, allow_upload=g.ofs_impl and logged_in %} +{% endblock %} + +{% block content_primary_nav %} +
  • {{ _('New resource') }}
  • +{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/resource_help.html' %} +{% endblock %} diff --git a/ckan/templates/package/read_base.html b/ckan/templates/package/read_base.html index 9cf73a5c5a1..f0a58a21018 100644 --- a/ckan/templates/package/read_base.html +++ b/ckan/templates/package/read_base.html @@ -50,20 +50,7 @@ {% block secondary_help_content %}{% endblock %} {% block package_info %} -
    -
    -

    {{ pkg.title or pkg.name }}

    -
    -
    -
    {{ _('Followers') }}
    -
    {{ h.SI_number_span(h.get_action('dataset_follower_count', {'id': pkg.id})) }}
    -
    -
    - -
    -
    + {% snippet 'package/snippets/info.html', pkg=pkg %} {% endblock %} {% block package_organization %} diff --git a/ckan/templates/package/resource_data.html b/ckan/templates/package/resource_data.html new file mode 100644 index 00000000000..05bd66fdfee --- /dev/null +++ b/ckan/templates/package/resource_data.html @@ -0,0 +1,68 @@ +{% extends "package/resource_edit_base.html" %} + +{% block subtitle %}{{ h.dataset_display_name(pkg) }} - {{ h.resource_display_name(res) }}{% endblock %} + +{% block primary_content_inner %} + + {% set action = h.url_for(controller='ckanext.datapusher.plugin:ResourceDataController', action='resource_data', id=pkg.name, resource_id=res.id) %} + {% set show_table = true %} + + + + + + {% if status.error and status.error.message %} + {% set show_table = false %} +
    + {{ _('Upload error:') }} {{ status.error.message }} +
    + {% elif status.task_info and status.task_info.error %} + {% set show_table = false %} +
    + {{ _('Error:') }} {{ status.task_info.error }} +
    + {% endif %} + + + + + + + + + + + + + + +
    {{ _('Status') }}{{ status.status.capitalize() if status.status else _('Not Uploaded Yet') }}
    {{ _('Last updated') }}{{ h.time_ago_from_timestamp(status.last_updated) if status.status else _('Never') }}
    + + {% if status.status and status.task_info and show_table %} +

    {{ _('Upload Log') }}

    +
      + {% for item in status.task_info.logs %} + {% set icon = 'ok' if item.level == 'INFO' else 'exclamation' %} + {% set class = ' failure' if icon == 'exclamation' else ' success' %} + {% set popover_content = 'test' %} +
    • + +

      + {{ item.message | urlize }}
      + + {{ h.time_ago_from_timestamp(item.timestamp) }} + more info + +

      +
    • + {% endfor %} +
    • + +

      {{ _('End of log') }}

      +
    • +
    + {% endif %} + +{% endblock %} diff --git a/ckan/templates/package/resource_edit.html b/ckan/templates/package/resource_edit.html index 52ac2ab4722..5e335d9114b 100644 --- a/ckan/templates/package/resource_edit.html +++ b/ckan/templates/package/resource_edit.html @@ -1,24 +1,7 @@ -{% extends "package/new_resource.html" %} +{% extends "package/resource_edit_base.html" %} -{% set pkg_dict = c.pkg_dict %} -{% set res = c.resource %} +{% block subtitle %}{{ _('Edit') }} - {{ h.resource_display_name(res) }} - {{ h.dataset_display_name(pkg) }}{% endblock %} -{% block subtitle %}{{ h.dataset_display_name(pkg_dict) }} - {{ h.resource_display_name(res) }}{% endblock %} - -{% block breadcrumb_content_selected %}{% endblock %} - -{% block breadcrumb_content %} - {{ super() }} -
  • {{ _('Edit') }}
  • -{% endblock %} - -{% block content_action %} - {% link_for _('View resource'), controller='package', action='resource_read', id=pkg_dict.name, resource_id=res.id, class_='btn', icon='eye-open' %} -{% endblock %} - -{# logged_in is defined in new_resource.html #} -{% block form %}{{ h.snippet('package/snippets/resource_edit_form.html', data=data, errors=errors, error_summary=error_summary, pkg_name=pkg_dict.name, form_action=c.form_action, allow_upload=g.ofs_impl and logged_in) }}{% endblock %} - -{% block secondary_content %} - {% snippet 'package/snippets/info.html', pkg=pkg_dict, active=data.id, action='resource_edit' %} +{% block form %} + {% snippet 'package/snippets/resource_edit_form.html', data=data, errors=errors, error_summary=error_summary, pkg_name=pkg.name, form_action=c.form_action, allow_upload=g.ofs_impl and logged_in %} {% endblock %} diff --git a/ckan/templates/package/resource_edit_base.html b/ckan/templates/package/resource_edit_base.html new file mode 100644 index 00000000000..0682cbbf014 --- /dev/null +++ b/ckan/templates/package/resource_edit_base.html @@ -0,0 +1,40 @@ +{% extends "package/base.html" %} + +{% set logged_in = true if c.userobj else false %} +{% set res = c.resource %} + +{% block breadcrumb_content_selected %}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} +
  • {% link_for h.resource_display_name(res)|truncate(30), controller='package', action='resource_read', id=pkg.name, resource_id=res.id %}
  • +
  • {{ _('Edit') }}
  • +{% endblock %} + +{% block content_action %} + {% link_for _('All resources'), controller='package', action='resources', id=pkg.name, class_='btn', icon='arrow-left' %} + {% if res %} + {% link_for _('View resource'), controller='package', action='resource_read', id=pkg.name, resource_id=res.id, class_='btn', icon='eye-open' %} + {% endif %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon('resource_edit', _('Edit resource'), id=pkg.name, resource_id=res.id) }} + {% if 'datapusher' in g.plugins %} + {{ h.build_nav_icon('resource_data', _('Resource Data'), id=pkg.name, resource_id=res.id) }} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {% block form_title %}{{ _('Edit resource') }}{% endblock %}

    + {% block form %}{% endblock %} +{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/resource_info.html', res=res %} +{% endblock %} + +{% block scripts %} + {{ super() }} + {% resource 'vendor/fileupload' %} +{% endblock %} diff --git a/ckan/templates/package/resources.html b/ckan/templates/package/resources.html new file mode 100644 index 00000000000..e353d03f10b --- /dev/null +++ b/ckan/templates/package/resources.html @@ -0,0 +1,21 @@ +{% extends "package/edit_base.html" %} + +{% block subtitle %}{{ _('Resources') }} - {{ h.dataset_display_name(pkg) }}{% endblock %} + +{% block page_primary_action %} + {% link_for _('Add new resource'), controller='package', action='new_resource', id=c.pkg_dict.name, class_='btn btn-primary', icon='plus' %} +{% endblock %} + +{% block primary_content_inner %} + {% if pkg.resources %} +
      + {% for resource in pkg.resources %} + {% snippet 'package/snippets/resource_item.html', pkg=pkg, res=resource, url_is_edit=true %} + {% endfor %} +
    + {% else %} + {% trans url=h.url_for(controller='package', action='new_resource', id=pkg.name) %} +

    This dataset has no data, why not add some?

    + {% endtrans %} + {% endif %} +{% endblock %} diff --git a/ckan/templates/package/snippets/info.html b/ckan/templates/package/snippets/info.html index 49efa7b8fc7..39ad092ff94 100644 --- a/ckan/templates/package/snippets/info.html +++ b/ckan/templates/package/snippets/info.html @@ -2,33 +2,29 @@ Displays a sidebard module with information for given package pkg - The package dict that owns the resources. -active - The active resource. -action - The action that this is coming from. Example: {% snippet "package/snippets/info.html", pkg=pkg %} #} -{% if pkg and h.check_access('package_update', {'id':pkg.id }) %} +{% if pkg %}
    -

    {{ _("Edit Dataset") }}

    - - {% set resources = pkg.resources or [] %} -

    {{ _("Edit Resources") }}

    - +
    +
    +

    {{ pkg.title or pkg.name }}

    +
    +
    +
    {{ _('Followers') }}
    +
    {{ h.SI_number_span(h.get_action('dataset_follower_count', {'id': pkg.id})) }}
    +
    +
    + {% if not hide_follow_button %} + + {% endif %} +
    +
    {% endif %} diff --git a/ckan/templates/package/snippets/package_basic_fields.html b/ckan/templates/package/snippets/package_basic_fields.html index 044b9ebe5bc..d1ac7e25577 100644 --- a/ckan/templates/package/snippets/package_basic_fields.html +++ b/ckan/templates/package/snippets/package_basic_fields.html @@ -86,8 +86,8 @@
    diff --git a/ckan/templates/package/snippets/package_metadata_fields.html b/ckan/templates/package/snippets/package_metadata_fields.html index ac841ddd4d0..4805e6637f9 100644 --- a/ckan/templates/package/snippets/package_metadata_fields.html +++ b/ckan/templates/package/snippets/package_metadata_fields.html @@ -2,6 +2,14 @@ {% block package_metadata_fields %} + {% block package_metadata_fields_url %} + {{ form.input('url', label=_('Source'), id='field-url', placeholder=_('http://example.com/dataset.json'), value=data.url, error=errors.url, classes=['control-medium']) }} + {% endblock %} + + {% block package_metadata_fields_version %} + {{ form.input('version', label=_('Version'), id='field-version', placeholder=_('1.0'), value=data.version, error=errors.version, classes=['control-medium']) }} + {% endblock %} + {% block package_metadata_author %} {{ form.input('author', label=_('Author'), id='field-author', placeholder=_('Joe Bloggs'), value=data.author, error=errors.author, classes=['control-medium']) }} diff --git a/ckan/templates/package/snippets/resource_help.html b/ckan/templates/package/snippets/resource_help.html new file mode 100644 index 00000000000..d1724956909 --- /dev/null +++ b/ckan/templates/package/snippets/resource_help.html @@ -0,0 +1,6 @@ +
    +

    {{ _('What\'s a resource?') }}

    +
    +

    {{ _('A resource can be any file or link to a file containing useful data.') }}

    +
    +
    diff --git a/ckan/templates/package/snippets/resource_info.html b/ckan/templates/package/snippets/resource_info.html new file mode 100644 index 00000000000..d8ada30d83a --- /dev/null +++ b/ckan/templates/package/snippets/resource_info.html @@ -0,0 +1,21 @@ +{# +Displays a sidebard module with information for given resource + +res - The respource dict + +Example: + + {% snippet "package/snippets/resrouce_info.html", res=res %} + +#} +
    +
    +

    {{ res.name or res.id }}

    +
    +
    +
    {{ _('Format') }}
    +
    {{ res.format }}
    +
    +
    +
    +
    diff --git a/ckan/templates/package/snippets/resource_item.html b/ckan/templates/package/snippets/resource_item.html index fccd94ff4d2..c5b22521d64 100644 --- a/ckan/templates/package/snippets/resource_item.html +++ b/ckan/templates/package/snippets/resource_item.html @@ -1,4 +1,7 @@ -{% set url = h.url_for(controller='package', action='resource_read', id=pkg.name, resource_id=res.id) %} +{% set can_edit = h.check_access('package_update', {'id':pkg.id }) %} +{% set url_action = 'resource_edit' if url_is_edit and can_edit else 'resource_read' %} +{% set url = h.url_for(controller='package', action=url_action, id=pkg.name, resource_id=res.id) %} +
  • {% block resource_item_title %} @@ -14,6 +17,7 @@ {% endif %}

    {% block resource_item_explore %} + {% if not url_is_edit %}
  • + {% if can_edit %} +
  • + + + {{ _('Edit') }} + +
  • + {% endif %} {% endblock %}
    + {% endif %} {% endblock %} diff --git a/ckan/templates/related/snippets/related_item.html b/ckan/templates/related/snippets/related_item.html index 39231a7a217..df1c1300025 100644 --- a/ckan/templates/related/snippets/related_item.html +++ b/ckan/templates/related/snippets/related_item.html @@ -11,11 +11,11 @@ #} {% set placeholder_map = { -'application':'/base/images/placeholder-application.png' +'application': h.url_for_static('/base/images/placeholder-application.png') } %} {% set tooltip = _('Go to {related_item_type}').format(related_item_type=related.type|replace('_', ' ')|title) %}