diff --git a/ckanext/justicehub_theme/controllers/__init__.py b/ckanext/justicehub_theme/controllers/__init__.py index e69de29..78d539b 100644 --- a/ckanext/justicehub_theme/controllers/__init__.py +++ b/ckanext/justicehub_theme/controllers/__init__.py @@ -0,0 +1 @@ +from controller import JHOrgController, SubscribeController diff --git a/ckanext/justicehub_theme/controllers/controller.py b/ckanext/justicehub_theme/controllers/controller.py new file mode 100644 index 0000000..a27b1a1 --- /dev/null +++ b/ckanext/justicehub_theme/controllers/controller.py @@ -0,0 +1,105 @@ +import json + +import requests +from ckanext.justicehub_theme.lib import helpers +from pylons import config + +import ckan.controllers.organization as org +import ckan.lib.base as base +import ckan.lib.navl.dictization_functions as dict_fns +import ckan.lib.plugins as plugins +import ckan.logic as logic +import ckan.model as model +from ckan.common import c, request + +clean_dict = logic.clean_dict +parse_params = logic.parse_params +tuplize_dict = logic.tuplize_dict + + +class JHOrgController(org.OrganizationController): + def members(self, id): + ''' + Modified core method from 'group' controller. + Enable view for non signed in user. + + :param id: id of the organization for which the member list is requested + :type id: string + :return: the rendered template + :rtype: unicode + ''' + group_type = self._ensure_controller_matches_group_type(id) + + context = {'model': model, 'session': model.Session, + 'user': c.user} + + data_dict = {'id': id} + try: + c.members = self._action('member_list')( + context, {'id': id, 'object_type': 'user'} + ) + data_dict['include_datasets'] = False + c.group_dict = self._action('group_show')(context, data_dict) + except NotFound: + abort(404, _('Oraganization not found')) + + return self._render_template('organization/jh_members.html', group_type) + + def org_stats(self, id): + group_type = self._ensure_controller_matches_group_type(id) + + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'schema': self._db_to_form_schema(group_type=group_type), + 'for_view': True} + data_dict = {'id': id, 'type': group_type} + # unicode format (decoded from utf8) + c.q = '' + + try: + data_dict['include_datasets'] = True + c.group_dict = self._action('group_show')(context, data_dict) + c.group = context['group'] + except NotFound: + abort(404, _('Organization not found')) + self._read(id, 999, group_type) + downloads = 0 + visits = 0 + for package in c.page.items: + downloads += helpers.get_package_avg_downloads(package) + visits += helpers.get_package_visits(package)['total'] + c.group_dict.update({'downloads':downloads, 'visits': visits}) + return plugins.toolkit.render('organization/stats.html', extra_vars={'group_type': group_type, 'downloads': downloads}) + + +class SubscribeController(base.BaseController): + def subscribe(self): + basic_auth_key = config.get("mailchimp_api_key", "") + subscriber_list_id = config.get("mailchimp_list_id", "") + subscriber_tag_id = config.get("mailchimp_tag_id", "") + mailchimp_base_url = config.get("mailchimp_base_url", "") + url = mailchimp_base_url + subscriber_list_id + "/members" + + request_body = clean_dict(dict_fns.unflatten( + tuplize_dict(parse_params(request.params)))) + + payload = { + "email_address": str(request_body.get("email", "")), + "status": "subscribed" + } + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Basic '+ basic_auth_key + } + + response = requests.request("POST", url, headers=headers, data = json.dumps(payload)) + + tag_url = mailchimp_base_url + subscriber_list_id + "/segments/" + subscriber_tag_id + "/members" + + payload = { + "email_address": str(request_body.get("email", "")) + } + + tag_response = requests.request("POST", tag_url, headers=headers, data = json.dumps(payload)) + + return response.text.encode('utf8') diff --git a/ckanext/justicehub_theme/controllers/jhorg_controller.py b/ckanext/justicehub_theme/controllers/jhorg_controller.py deleted file mode 100644 index 50c3421..0000000 --- a/ckanext/justicehub_theme/controllers/jhorg_controller.py +++ /dev/null @@ -1,32 +0,0 @@ - -import ckan.controllers.organization as org -import ckan.model as model -from ckan.common import c - -class JHOrgController(org.OrganizationController): - def members(self, id): - ''' - Modified core method from 'group' controller. - Enable view for non signed in user. - - :param id: id of the organization for which the member list is requested - :type id: string - :return: the rendered template - :rtype: unicode - ''' - group_type = self._ensure_controller_matches_group_type(id) - - context = {'model': model, 'session': model.Session, - 'user': c.user} - - data_dict = {'id': id} - try: - c.members = self._action('member_list')( - context, {'id': id, 'object_type': 'user'} - ) - data_dict['include_datasets'] = False - c.group_dict = self._action('group_show')(context, data_dict) - except NotFound: - abort(404, _('Group not found')) - - return self._render_template('group/members.html', group_type) \ No newline at end of file diff --git a/ckanext/justicehub_theme/controllers/package.py b/ckanext/justicehub_theme/controllers/package.py deleted file mode 100644 index 6c70633..0000000 --- a/ckanext/justicehub_theme/controllers/package.py +++ /dev/null @@ -1,1620 +0,0 @@ -# encoding: utf-8 - -import logging -from urllib import urlencode -import datetime -import mimetypes -import cgi - -from ckan.common import config -from paste.deploy.converters import asbool -import paste.fileapp -from six import string_types, text_type - -import ckan.logic as logic -import ckan.lib.base as base -import ckan.lib.i18n as i18n -import ckan.lib.maintain as maintain -import ckan.lib.navl.dictization_functions as dict_fns -import ckan.lib.helpers as h -import ckan.model as model -import ckan.lib.datapreview as datapreview -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, response -from home import CACHE_PARAMETERS - -log = logging.getLogger(__name__) - -render = base.render -abort = base.abort - -NotFound = logic.NotFound -NotAuthorized = logic.NotAuthorized -ValidationError = logic.ValidationError -check_access = logic.check_access -get_action = logic.get_action -tuplize_dict = logic.tuplize_dict -clean_dict = logic.clean_dict -parse_params = logic.parse_params -flatten_to_string_key = logic.flatten_to_string_key - -lookup_package_plugin = ckan.lib.plugins.lookup_package_plugin - - -def _encode_params(params): - return [(k, v.encode('utf-8') if isinstance(v, string_types) else str(v)) - for k, v in params] - - -def url_with_params(url, params): - params = _encode_params(params) - return url + u'?' + urlencode(params) - - -def search_url(params, package_type=None): - if not package_type or package_type == 'dataset': - url = h.url_for(controller='package', action='search') - else: - url = h.url_for('{0}_search'.format(package_type)) - return url_with_params(url, params) - - -class PackageController(base.BaseController): - - def _package_form(self, package_type=None): - return lookup_package_plugin(package_type).package_form() - - def _setup_template_variables(self, context, data_dict, package_type=None): - return lookup_package_plugin(package_type).\ - setup_template_variables(context, data_dict) - - def _new_template(self, package_type): - return lookup_package_plugin(package_type).new_template() - - def _edit_template(self, package_type): - return lookup_package_plugin(package_type).edit_template() - - def _search_template(self, package_type): - return lookup_package_plugin(package_type).search_template() - - def _read_template(self, package_type): - return lookup_package_plugin(package_type).read_template() - - def _history_template(self, package_type): - return lookup_package_plugin(package_type).history_template() - - def _resource_form(self, package_type): - # backwards compatibility with plugins not inheriting from - # DefaultDatasetPlugin and not implmenting resource_form - plugin = lookup_package_plugin(package_type) - if hasattr(plugin, 'resource_form'): - result = plugin.resource_form() - if result is not None: - return result - return lookup_package_plugin().resource_form() - - def _resource_template(self, package_type): - # backwards compatibility with plugins not inheriting from - # DefaultDatasetPlugin and not implmenting resource_template - plugin = lookup_package_plugin(package_type) - if hasattr(plugin, 'resource_template'): - result = plugin.resource_template() - if result is not None: - return result - return lookup_package_plugin().resource_template() - - def _guess_package_type(self, expecting_name=False): - """ - Guess the type of package from the URL handling the case - where there is a prefix on the URL (such as /data/package) - """ - - # Special case: if the rot URL '/' has been redirected to the package - # controller (e.g. by an IRoutes extension) then there's nothing to do - # here. - if request.path == '/': - return 'dataset' - - parts = [x for x in request.path.split('/') if x] - - idx = -1 - if expecting_name: - idx = -2 - - pt = parts[idx] - if pt == 'package': - pt = 'dataset' - - return pt - - def search(self): - from ckan.lib.search import SearchError, SearchQueryError - - package_type = self._guess_package_type() - - try: - context = {'model': model, 'user': c.user, - 'auth_user_obj': c.userobj} - check_access('site_read', context) - except NotAuthorized: - abort(403, _('Not authorized to see this page')) - - # unicode format (decoded from utf8) - q = c.q = request.params.get('q', u'') - c.query_error = False - page = h.get_page_number(request.params) - - limit = int(config.get('ckan.datasets_per_page', 20)) - - # most search operations should reset the page counter: - params_nopage = [(k, v) for k, v in request.params.items() - if k != 'page'] - - def drill_down_url(alternative_url=None, **by): - return h.add_url_param(alternative_url=alternative_url, - controller='package', action='search', - new_params=by) - - c.drill_down_url = drill_down_url - - def remove_field(key, value=None, replace=None): - return h.remove_url_param(key, value=value, replace=replace, - controller='package', action='search', - alternative_url=package_type) - - c.remove_field = remove_field - - sort_by = request.params.get('sort', None) - params_nosort = [(k, v) for k, v in params_nopage if k != 'sort'] - - def _sort_by(fields): - """ - Sort by the given list of fields. - - Each entry in the list is a 2-tuple: (fieldname, sort_order) - - eg - [('metadata_modified', 'desc'), ('name', 'asc')] - - If fields is empty, then the default ordering is used. - """ - params = params_nosort[:] - - if fields: - sort_string = ', '.join('%s %s' % f for f in fields) - params.append(('sort', sort_string)) - return search_url(params, package_type) - - c.sort_by = _sort_by - if not sort_by: - c.sort_by_fields = [] - else: - c.sort_by_fields = [field.split()[0] - for field in sort_by.split(',')] - - def pager_url(q=None, page=None): - params = list(params_nopage) - params.append(('page', page)) - return search_url(params, package_type) - - c.search_url_params = urlencode(_encode_params(params_nopage)) - - try: - c.fields = [] - # c.fields_grouped will contain a dict of params containing - # a list of values eg {'tags':['tag1', 'tag2']} - c.fields_grouped = {} - search_extras = {} - fq = '' - for (param, value) in request.params.items(): - if param not in ['q', 'page', 'sort'] \ - and len(value) and not param.startswith('_'): - if not param.startswith('ext_'): - c.fields.append((param, value)) - fq += ' %s:"%s"' % (param, value) - if param not in c.fields_grouped: - c.fields_grouped[param] = [value] - else: - c.fields_grouped[param].append(value) - else: - search_extras[param] = value - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - - # Unless changed via config options, don't show other dataset - # types any search page. Potential alternatives are do show them - # on the default search page (dataset) or on one other search page - search_all_type = config.get( - 'ckan.search.show_all_types', 'dataset') - search_all = False - - try: - # If the "type" is set to True or False, convert to bool - # and we know that no type was specified, so use traditional - # behaviour of applying this only to dataset type - search_all = asbool(search_all_type) - search_all_type = 'dataset' - # Otherwise we treat as a string representing a type - except ValueError: - search_all = True - - if not package_type: - package_type = 'dataset' - - if not search_all or package_type != search_all_type: - # Only show datasets of this particular type - fq += ' +dataset_type:{type}'.format(type=package_type) - - facets = OrderedDict() - - default_facet_titles = { - 'organization': _('Organizations'), - 'groups': _('Groups'), - 'tags': _('Tags'), - 'res_format': _('Formats'), - 'license_id': _('Licenses'), - } - - for facet in h.facets(): - if facet in default_facet_titles: - facets[facet] = default_facet_titles[facet] - else: - facets[facet] = facet - - # Facet titles - for plugin in p.PluginImplementations(p.IFacets): - facets = plugin.dataset_facets(facets, package_type) - - c.facet_titles = facets - - data_dict = { - 'q': q, - 'fq': fq.strip(), - 'facet.field': facets.keys(), - 'rows': limit, - 'start': (page - 1) * limit, - 'sort': sort_by, - 'extras': search_extras, - 'include_private': asbool(config.get( - 'ckan.search.default_include_private', True)), - } - - query = get_action('package_search')(context, data_dict) - c.sort_by_selected = query['sort'] - - c.page = h.Page( - collection=query['results'], - page=page, - url=pager_url, - item_count=query['count'], - items_per_page=limit - ) - c.search_facets = query['search_facets'] - c.page.items = query['results'] - except SearchQueryError as se: - # User's search parameters are invalid, in such a way that is not - # achievable with the web interface, so return a proper error to - # discourage spiders which are the main cause of this. - log.info('Dataset search query rejected: %r', se.args) - abort(400, _('Invalid search query: {error_message}') - .format(error_message=str(se))) - except SearchError as se: - # May be bad input from the user, but may also be more serious like - # bad code causing a SOLR syntax error, or a problem connecting to - # SOLR - log.error('Dataset search error: %r', se.args) - c.query_error = True - c.search_facets = {} - c.page = h.Page(collection=[]) - except NotAuthorized: - abort(403, _('Not authorized to see this page')) - - c.search_facets_limits = {} - for facet in c.search_facets.keys(): - try: - limit = int(request.params.get('_%s_limit' % facet, - int(config.get('search.facets.default', 10)))) - except ValueError: - abort(400, _('Parameter "{parameter_name}" is not ' - 'an integer').format( - parameter_name='_%s_limit' % facet)) - c.search_facets_limits[facet] = limit - - self._setup_template_variables(context, {}, - package_type=package_type) - - return render(self._search_template(package_type), - extra_vars={'dataset_type': package_type}) - - def resources(self, id): - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - data_dict = {'id': id, 'include_tracking': True} - - try: - check_access('package_update', context, data_dict) - except NotFound: - abort(404, _('Dataset not found')) - except NotAuthorized: - abort(403, _('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, NotAuthorized): - abort(404, _('Dataset not found')) - - package_type = c.pkg_dict['type'] or 'dataset' - self._setup_template_variables(context, {'id': id}, - package_type=package_type) - - return render('package/resources.html', - extra_vars={'dataset_type': package_type}) - - def read(self, id): - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - data_dict = {'id': id, 'include_tracking': True} - - # interpret @ or @ suffix - split = id.split('@') - if len(split) == 2: - data_dict['id'], revision_ref = split - if model.is_id(revision_ref): - context['revision_id'] = revision_ref - else: - try: - date = h.date_str_to_datetime(revision_ref) - context['revision_date'] = date - except TypeError as e: - abort(400, _('Invalid revision format: %r') % e.args) - except ValueError as e: - abort(400, _('Invalid revision format: %r') % e.args) - elif len(split) > 2: - abort(400, _('Invalid revision format: %r') % - 'Too many "@" symbols') - - # check if package exists - try: - c.pkg_dict = get_action('package_show')(context, data_dict) - c.pkg = context['package'] - c.package_activity_stream = get_action( - 'package_activity_list_html')( - context, {'id': c.pkg_dict['id']}) - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - - # used by disqus plugin - c.current_package_id = c.pkg.id - - # can the resources be previewed? - for resource in c.pkg_dict['resources']: - # Backwards compatibility with preview interface - resource['can_be_previewed'] = self._resource_preview( - {'resource': resource, 'package': c.pkg_dict}) - - resource_views = get_action('resource_view_list')( - context, {'id': resource['id']}) - resource['has_views'] = len(resource_views) > 0 - - package_type = c.pkg_dict['type'] or 'dataset' - self._setup_template_variables(context, {'id': id}, - package_type=package_type) - - template = self._read_template(package_type) - try: - return render(template, - extra_vars={'dataset_type': package_type}) - except ckan.lib.render.TemplateNotFound as e: - msg = _( - "Viewing datasets of type \"{package_type}\" is " - "not supported ({file_!r}).".format( - package_type=package_type, - file_=e.message - ) - ) - abort(404, msg) - - assert False, "We should never get here" - - def history(self, id): - - if 'diff' in request.params or 'selected1' in request.params: - try: - params = {'id': request.params.getone('pkg_name'), - 'diff': request.params.getone('selected1'), - 'oldid': request.params.getone('selected2'), - } - except KeyError: - if 'pkg_name' in dict(request.params): - id = request.params.getone('pkg_name') - c.error = \ - _('Select two revisions before doing the comparison.') - else: - params['diff_entity'] = 'package' - h.redirect_to(controller='revision', action='diff', **params) - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj, - 'for_view': True} - data_dict = {'id': id} - try: - c.pkg_dict = get_action('package_show')(context, data_dict) - c.pkg_revisions = get_action('package_revision_list')(context, - data_dict) - # TODO: remove - # Still necessary for the authz check in group/layout.html - c.pkg = context['package'] - - except NotAuthorized: - abort(403, _('Unauthorized to read package %s') % '') - except NotFound: - abort(404, _('Dataset not found')) - - format = request.params.get('format', '') - if format == 'atom': - # Generate and return Atom 1.0 document. - from webhelpers.feedgenerator import Atom1Feed - feed = Atom1Feed( - title=_(u'CKAN Dataset Revision History'), - link=h.url_for(controller='revision', action='read', - id=c.pkg_dict['name']), - description=_(u'Recent changes to CKAN Dataset: ') + - (c.pkg_dict['title'] or ''), - language=text_type(i18n.get_lang()), - ) - for revision_dict in c.pkg_revisions: - revision_date = h.date_str_to_datetime( - revision_dict['timestamp']) - try: - dayHorizon = int(request.params.get('days')) - except: - dayHorizon = 30 - dayAge = (datetime.datetime.now() - revision_date).days - if dayAge >= dayHorizon: - break - if revision_dict['message']: - item_title = u'%s' % revision_dict['message'].\ - split('\n')[0] - else: - item_title = u'%s' % revision_dict['id'] - item_link = h.url_for(controller='revision', action='read', - id=revision_dict['id']) - item_description = _('Log message: ') - item_description += '%s' % (revision_dict['message'] or '') - item_author_name = revision_dict['author'] - item_pubdate = revision_date - feed.add_item( - title=item_title, - link=item_link, - description=item_description, - author_name=item_author_name, - pubdate=item_pubdate, - ) - response.headers['Content-Type'] = 'application/atom+xml' - return feed.writeString('utf-8') - - package_type = c.pkg_dict['type'] or 'dataset' - - return render( - self._history_template(c.pkg_dict.get('type', package_type)), - extra_vars={'dataset_type': package_type}) - - def new(self, data=None, errors=None, error_summary=None): - if data and 'type' in data: - package_type = data['type'] - else: - package_type = self._guess_package_type(True) - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj, - 'save': 'save' in request.params} - - # Package needs to have a organization group in the call to - # check_access and also to save it - try: - check_access('package_create', context) - except NotAuthorized: - abort(403, _('Unauthorized to create a package')) - - if context['save'] and not data and request.method == 'POST': - return self._save_new(context, package_type=package_type) - - data = data or clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( - request.params, ignore_keys=CACHE_PARAMETERS)))) - c.resources_json = h.json.dumps(data.get('resources', [])) - # convert tags if not supplied in data - if data and not data.get('tag_string'): - data['tag_string'] = ', '.join( - h.dict_list_reduce(data.get('tags', {}), 'name')) - - errors = errors or {} - error_summary = error_summary or {} - # in the phased add dataset we need to know that - # we have already completed stage 1 - stage = ['active'] - if data.get('state', '').startswith('draft'): - stage = ['active', 'complete'] - - # if we are creating from a group then this allows the group to be - # set automatically - data['group_id'] = request.params.get('group') or \ - request.params.get('groups__0__id') - - form_snippet = self._package_form(package_type=package_type) - form_vars = {'data': data, 'errors': errors, - 'error_summary': error_summary, - 'action': 'new', 'stage': stage, - 'dataset_type': package_type, - } - c.errors_json = h.json.dumps(errors) - - self._setup_template_variables(context, {}, - package_type=package_type) - - new_template = self._new_template(package_type) - return render(new_template, - extra_vars={'form_vars': form_vars, - 'form_snippet': form_snippet, - 'dataset_type': package_type}) - - def resource_edit(self, id, resource_id, data=None, errors=None, - error_summary=None): - - context = {'model': model, 'session': model.Session, - 'api_version': 3, 'for_edit': True, - 'user': c.user, 'auth_user_obj': c.userobj} - data_dict = {'id': id} - - try: - check_access('package_update', context, data_dict) - except NotAuthorized: - abort(403, _('User %r not authorized to edit %s') % (c.user, id)) - - if request.method == 'POST' and not data: - data = data or \ - clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( - request.POST)))) - # we don't want to include save as it is part of the form - del data['save'] - - data['package_id'] = id - try: - if resource_id: - data['id'] = resource_id - get_action('resource_update')(context, data) - else: - get_action('resource_create')(context, data) - except ValidationError as e: - errors = e.error_dict - error_summary = e.error_summary - return self.resource_edit(id, resource_id, data, - errors, error_summary) - except NotAuthorized: - abort(403, _('Unauthorized to edit this resource')) - h.redirect_to(controller='package', action='resource_read', id=id, - resource_id=resource_id) - - pkg_dict = get_action('package_show')(context, {'id': id}) - if pkg_dict['state'].startswith('draft'): - # dataset has not yet been fully created - resource_dict = get_action('resource_show')(context, - {'id': resource_id}) - return self.new_resource(id, data=resource_dict) - # resource is fully created - try: - resource_dict = get_action('resource_show')(context, - {'id': resource_id}) - except NotFound: - abort(404, _('Resource not found')) - c.pkg_dict = pkg_dict - c.resource = resource_dict - # set the form action - c.form_action = h.url_for(controller='package', - action='resource_edit', - resource_id=resource_id, - id=id) - if not data: - data = resource_dict - - package_type = pkg_dict['type'] or 'dataset' - - errors = errors or {} - error_summary = error_summary or {} - vars = {'data': data, 'errors': errors, - 'error_summary': error_summary, 'action': 'edit', - 'resource_form_snippet': self._resource_form(package_type), - 'dataset_type': package_type} - return render('package/resource_edit.html', extra_vars=vars) - - def new_resource(self, id, data=None, errors=None, error_summary=None): - ''' FIXME: This is a temporary action to allow styling of the - forms. ''' - if request.method == 'POST' and not data: - save_action = request.params.get('save') - data = data or \ - clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( - request.POST)))) - # we don't want to include save as it is part of the form - del data['save'] - resource_id = data['id'] - del data['id'] - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - - # see if we have any data that we are trying to save - data_provided = False - for key, value in data.iteritems(): - if ((value or isinstance(value, cgi.FieldStorage)) - and key != 'resource_type'): - data_provided = True - break - - if not data_provided and save_action != "go-dataset-complete": - if save_action == 'go-dataset': - # go to final stage of adddataset - h.redirect_to(controller='package', action='edit', id=id) - # see if we have added any resources - try: - data_dict = get_action('package_show')(context, {'id': id}) - except NotAuthorized: - abort(403, _('Unauthorized to update dataset')) - except NotFound: - abort(404, _('The dataset {id} could not be found.' - ).format(id=id)) - if not len(data_dict['resources']): - # no data so keep on page - msg = _('You must add at least one data resource') - # On new templates do not use flash message - - if asbool(config.get('ckan.legacy_templates')): - h.flash_error(msg) - h.redirect_to(controller='package', - action='new_resource', id=id) - else: - errors = {} - error_summary = {_('Error'): msg} - return self.new_resource(id, data, errors, - error_summary) - # XXX race condition if another user edits/deletes - data_dict = get_action('package_show')(context, {'id': id}) - get_action('package_update')( - dict(context, allow_state_change=True), - dict(data_dict, state='active')) - h.redirect_to(controller='package', action='read', id=id) - - data['package_id'] = id - try: - if resource_id: - data['id'] = resource_id - get_action('resource_update')(context, data) - else: - get_action('resource_create')(context, data) - except ValidationError as e: - errors = e.error_dict - error_summary = e.error_summary - return self.new_resource(id, data, errors, error_summary) - except NotAuthorized: - abort(403, _('Unauthorized to create a resource')) - except NotFound: - abort(404, _('The dataset {id} could not be found.' - ).format(id=id)) - if save_action == 'go-metadata': - # XXX race condition if another user edits/deletes - data_dict = get_action('package_show')(context, {'id': id}) - get_action('package_update')( - dict(context, allow_state_change=True), - dict(data_dict, state='active')) - h.redirect_to(controller='package', action='read', id=id) - elif save_action == 'go-dataset': - # go to first stage of add dataset - h.redirect_to(controller='package', action='edit', id=id) - elif save_action == 'go-dataset-complete': - # go to first stage of add dataset - h.redirect_to(controller='package', action='read', id=id) - else: - # add more resources - h.redirect_to(controller='package', action='new_resource', - id=id) - - # get resources for sidebar - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - try: - pkg_dict = get_action('package_show')(context, {'id': id}) - except NotFound: - abort(404, _('The dataset {id} could not be found.').format(id=id)) - try: - check_access( - 'resource_create', context, {"package_id": pkg_dict["id"]}) - except NotAuthorized: - abort(403, _('Unauthorized to create a resource for this package')) - - package_type = pkg_dict['type'] or 'dataset' - - errors = errors or {} - error_summary = error_summary or {} - vars = {'data': data, 'errors': errors, - 'error_summary': error_summary, 'action': 'new', - 'resource_form_snippet': self._resource_form(package_type), - 'dataset_type': package_type} - vars['pkg_name'] = id - # required for nav menu - vars['pkg_dict'] = pkg_dict - template = 'package/new_resource_not_draft.html' - if pkg_dict['state'].startswith('draft'): - vars['stage'] = ['complete', 'active'] - template = 'package/new_resource.html' - return render(template, extra_vars=vars) - - def edit(self, id, data=None, errors=None, error_summary=None): - package_type = self._get_package_type(id) - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj, - 'save': 'save' in request.params} - - if context['save'] and not data and request.method == 'POST': - return self._save_edit(id, context, package_type=package_type) - try: - c.pkg_dict = get_action('package_show')(dict(context, - for_view=True), - {'id': id}) - context['for_edit'] = True - old_data = get_action('package_show')(context, {'id': id}) - # old data is from the database and data is passed from the - # user if there is a validation error. Use users data if there. - if data: - old_data.update(data) - data = old_data - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - # are we doing a multiphase add? - if data.get('state', '').startswith('draft'): - c.form_action = h.url_for(controller='package', action='new') - c.form_style = 'new' - return self.new(data=data, errors=errors, - error_summary=error_summary) - - c.pkg = context.get("package") - c.resources_json = h.json.dumps(data.get('resources', [])) - - try: - check_access('package_update', context) - except NotAuthorized: - abort(403, _('User %r not authorized to edit %s') % (c.user, id)) - # convert tags if not supplied in data - if data and not data.get('tag_string'): - data['tag_string'] = ', '.join(h.dict_list_reduce( - c.pkg_dict.get('tags', {}), 'name')) - errors = errors or {} - form_snippet = self._package_form(package_type=package_type) - form_vars = {'data': data, 'errors': errors, - 'error_summary': error_summary, 'action': 'edit', - 'dataset_type': package_type, - } - c.errors_json = h.json.dumps(errors) - - self._setup_template_variables(context, {'id': id}, - package_type=package_type) - - # we have already completed stage 1 - form_vars['stage'] = ['active'] - if data.get('state', '').startswith('draft'): - form_vars['stage'] = ['active', 'complete'] - - edit_template = self._edit_template(package_type) - return render(edit_template, - extra_vars={'form_vars': form_vars, - 'form_snippet': form_snippet, - 'dataset_type': package_type}) - - def read_ajax(self, id, revision=None): - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj, - 'revision_id': revision} - try: - data = get_action('package_show')(context, {'id': id}) - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - - data.pop('tags') - data = flatten_to_string_key(data) - response.headers['Content-Type'] = 'application/json;charset=utf-8' - return h.json.dumps(data) - - def history_ajax(self, id): - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - data_dict = {'id': id} - try: - pkg_revisions = get_action('package_revision_list')( - context, data_dict) - except NotAuthorized: - abort(403, _('Unauthorized to read package %s') % '') - except NotFound: - abort(404, _('Dataset not found')) - - data = [] - approved = False - for num, revision in enumerate(pkg_revisions): - if not approved and revision['approved_timestamp']: - current_approved, approved = True, True - else: - current_approved = False - - data.append({'revision_id': revision['id'], - 'message': revision['message'], - 'timestamp': revision['timestamp'], - 'author': revision['author'], - 'approved': bool(revision['approved_timestamp']), - 'current_approved': current_approved}) - - response.headers['Content-Type'] = 'application/json;charset=utf-8' - return h.json.dumps(data) - - def _get_package_type(self, id): - """ - Given the id of a package this method will return the type of the - package, or 'dataset' if no type is currently set - """ - pkg = model.Package.get(id) - if pkg: - return pkg.type or 'dataset' - return None - - def _tag_string_to_list(self, tag_string): - ''' This is used to change tags from a sting to a list of dicts ''' - out = [] - for tag in tag_string.split(','): - tag = tag.strip() - if tag: - out.append({'name': tag, - 'state': 'active'}) - return out - - def _save_new(self, context, package_type=None): - # The staged add dataset used the new functionality when the dataset is - # partially created so we need to know if we actually are updating or - # this is a real new. - is_an_update = False - ckan_phase = request.params.get('_ckan_phase') - from ckan.lib.search import SearchIndexError - try: - data_dict = clean_dict(dict_fns.unflatten( - tuplize_dict(parse_params(request.POST)))) - if ckan_phase: - # prevent clearing of groups etc - context['allow_partial_update'] = True - # sort the tags - if 'tag_string' in data_dict: - data_dict['tags'] = self._tag_string_to_list( - data_dict['tag_string']) - if data_dict.get('pkg_name'): - is_an_update = True - # This is actually an update not a save - data_dict['id'] = data_dict['pkg_name'] - del data_dict['pkg_name'] - # don't change the dataset state - data_dict['state'] = 'draft' - # this is actually an edit not a save - pkg_dict = get_action('package_update')(context, data_dict) - - if request.params['save'] == 'go-metadata': - # redirect to add metadata - url = h.url_for(controller='package', - action='new_metadata', - id=pkg_dict['name']) - else: - # redirect to add dataset resources - url = h.url_for(controller='package', - action='new_resource', - id=pkg_dict['name']) - h.redirect_to(url) - # Make sure we don't index this dataset - if request.params['save'] not in ['go-resource', - 'go-metadata']: - data_dict['state'] = 'draft' - # allow the state to be changed - context['allow_state_change'] = True - - data_dict['type'] = package_type - context['message'] = data_dict.get('log_message', '') - pkg_dict = get_action('package_create')(context, data_dict) - - if ckan_phase: - # redirect to add dataset resources - url = h.url_for(controller='package', - action='new_resource', - id=pkg_dict['name']) - h.redirect_to(url) - - self._form_save_redirect(pkg_dict['name'], 'new', - package_type=package_type) - except NotAuthorized: - abort(403, _('Unauthorized to read package %s') % '') - except NotFound as e: - abort(404, _('Dataset not found')) - except dict_fns.DataError: - abort(400, _(u'Integrity Error')) - except SearchIndexError as e: - try: - exc_str = text_type(repr(e.args)) - except Exception: # We don't like bare excepts - exc_str = text_type(str(e)) - abort(500, _(u'Unable to add package to search index.') + exc_str) - except ValidationError as e: - errors = e.error_dict - error_summary = e.error_summary - if is_an_update: - # we need to get the state of the dataset to show the stage we - # are on. - pkg_dict = get_action('package_show')(context, data_dict) - data_dict['state'] = pkg_dict['state'] - return self.edit(data_dict['id'], data_dict, - errors, error_summary) - data_dict['state'] = 'none' - return self.new(data_dict, errors, error_summary) - - def _save_edit(self, name_or_id, context, package_type=None): - from ckan.lib.search import SearchIndexError - log.debug('Package save request name: %s POST: %r', - name_or_id, request.POST) - try: - data_dict = clean_dict(dict_fns.unflatten( - tuplize_dict(parse_params(request.POST)))) - if '_ckan_phase' in data_dict: - # we allow partial updates to not destroy existing resources - context['allow_partial_update'] = True - if 'tag_string' in data_dict: - data_dict['tags'] = self._tag_string_to_list( - data_dict['tag_string']) - del data_dict['_ckan_phase'] - del data_dict['save'] - context['message'] = data_dict.get('log_message', '') - data_dict['id'] = name_or_id - pkg = get_action('package_update')(context, data_dict) - c.pkg = context['package'] - c.pkg_dict = pkg - - self._form_save_redirect(pkg['name'], 'edit', - package_type=package_type) - except NotAuthorized: - abort(403, _('Unauthorized to read package %s') % id) - except NotFound as e: - abort(404, _('Dataset not found')) - except dict_fns.DataError: - abort(400, _(u'Integrity Error')) - except SearchIndexError as e: - try: - exc_str = text_type(repr(e.args)) - except Exception: # We don't like bare excepts - exc_str = text_type(str(e)) - abort(500, _(u'Unable to update search index.') + exc_str) - except ValidationError as e: - errors = e.error_dict - error_summary = e.error_summary - return self.edit(name_or_id, data_dict, errors, error_summary) - - def _form_save_redirect(self, pkgname, action, package_type=None): - '''This redirects the user to the CKAN package/read page, - unless there is request parameter giving an alternate location, - perhaps an external website. - @param pkgname - Name of the package just edited - @param action - What the action of the edit was - ''' - assert action in ('new', 'edit') - url = request.params.get('return_to') or \ - config.get('package_%s_return_url' % action) - if url: - url = url.replace('', pkgname) - else: - if package_type is None or package_type == 'dataset': - url = h.url_for(controller='package', action='read', - id=pkgname) - else: - url = h.url_for('{0}_read'.format(package_type), id=pkgname) - h.redirect_to(url) - - def delete(self, id): - - if 'cancel' in request.params: - h.redirect_to(controller='package', action='edit', id=id) - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - - try: - if request.method == 'POST': - get_action('package_delete')(context, {'id': id}) - h.flash_notice(_('Dataset has been deleted.')) - h.redirect_to(controller='package', action='search') - c.pkg_dict = get_action('package_show')(context, {'id': id}) - dataset_type = c.pkg_dict['type'] or 'dataset' - except NotAuthorized: - abort(403, _('Unauthorized to delete package %s') % '') - except NotFound: - abort(404, _('Dataset not found')) - return render('package/confirm_delete.html', - extra_vars={'dataset_type': dataset_type}) - - def resource_delete(self, id, resource_id): - - if 'cancel' in request.params: - h.redirect_to(controller='package', action='resource_edit', - resource_id=resource_id, id=id) - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - - try: - check_access('package_delete', context, {'id': id}) - except NotAuthorized: - abort(403, _('Unauthorized to delete package %s') % '') - - try: - if request.method == 'POST': - get_action('resource_delete')(context, {'id': resource_id}) - h.flash_notice(_('Resource has been deleted.')) - pkg_dict = get_action('package_show')(None, {'id': id}) - if pkg_dict['state'].startswith('draft'): - h.redirect_to(controller='package', action='new_resource', - id=id) - else: - h.redirect_to(controller='package', action='read', id=id) - c.resource_dict = get_action('resource_show')( - context, {'id': resource_id}) - c.pkg_id = id - except NotAuthorized: - abort(403, _('Unauthorized to delete resource %s') % '') - except NotFound: - abort(404, _('Resource not found')) - return render('package/confirm_delete_resource.html', - {'dataset_type': self._get_package_type(id)}) - - def resource_read(self, id, resource_id): - context = {'model': model, 'session': model.Session, - 'user': c.user, - 'auth_user_obj': c.userobj, - 'for_view': True} - - try: - c.package = get_action('package_show')(context, {'id': id}) - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - - for resource in c.package.get('resources', []): - if resource['id'] == resource_id: - c.resource = resource - break - if not c.resource: - abort(404, _('Resource not found')) - - # required for nav menu - c.pkg = context['package'] - c.pkg_dict = c.package - dataset_type = c.pkg.type or 'dataset' - - # get package license info - license_id = c.package.get('license_id') - try: - c.package['isopen'] = model.Package.\ - get_license_register()[license_id].isopen() - except KeyError: - c.package['isopen'] = False - - # Deprecated: c.datastore_api - use h.action_url instead - c.datastore_api = '%s/api/action' % \ - config.get('ckan.site_url', '').rstrip('/') - - c.resource['can_be_previewed'] = self._resource_preview( - {'resource': c.resource, 'package': c.package}) - - resource_views = get_action('resource_view_list')( - context, {'id': resource_id}) - c.resource['has_views'] = len(resource_views) > 0 - - current_resource_view = None - view_id = request.GET.get('view_id') - if c.resource['can_be_previewed'] and not view_id: - current_resource_view = None - elif c.resource['has_views']: - if view_id: - current_resource_view = [rv for rv in resource_views - if rv['id'] == view_id] - if len(current_resource_view) == 1: - current_resource_view = current_resource_view[0] - else: - abort(404, _('Resource view not found')) - else: - current_resource_view = resource_views[0] - - vars = {'resource_views': resource_views, - 'current_resource_view': current_resource_view, - 'dataset_type': dataset_type} - - template = self._resource_template(dataset_type) - return render(template, extra_vars=vars) - - @maintain.deprecated('Resource preview is deprecated. Please use the new ' - 'resource views') - def _resource_preview(self, data_dict): - '''Deprecated in 2.3''' - return bool(datapreview.get_preview_plugin(data_dict, - return_first=True)) - - def resource_download(self, id, resource_id, filename=None): - """ - Provides a direct download by either redirecting the user to the url - stored or downloading an uploaded file directly. - """ - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - - try: - rsc = get_action('resource_show')(context, {'id': resource_id}) - get_action('package_show')(context, {'id': id}) - except (NotFound, NotAuthorized): - abort(404, _('Resource not found')) - - if rsc.get('url_type') == 'upload': - upload = uploader.get_resource_uploader(rsc) - filepath = upload.get_path(rsc['id']) - fileapp = paste.fileapp.FileApp(filepath) - try: - status, headers, app_iter = request.call_application(fileapp) - except OSError: - abort(404, _('Resource data not found')) - response.headers.update(dict(headers)) - content_type, content_enc = mimetypes.guess_type( - rsc.get('url', '')) - if content_type: - response.headers['Content-Type'] = content_type - response.status = status - return app_iter - elif 'url' not in rsc: - abort(404, _('No download is available')) - h.redirect_to(rsc['url']) - - def follow(self, id): - '''Start following this dataset.''' - context = {'model': model, - 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - data_dict = {'id': id} - try: - get_action('follow_dataset')(context, data_dict) - package_dict = get_action('package_show')(context, data_dict) - h.flash_success(_("You are now following {0}").format( - package_dict['title'])) - except ValidationError as e: - error_message = (e.message or e.error_summary - or e.error_dict) - h.flash_error(error_message) - except NotAuthorized as e: - h.flash_error(e.message) - h.redirect_to(controller='package', action='read', id=id) - - def unfollow(self, id): - '''Stop following this dataset.''' - context = {'model': model, - 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - data_dict = {'id': id} - try: - get_action('unfollow_dataset')(context, data_dict) - package_dict = get_action('package_show')(context, data_dict) - h.flash_success(_("You are no longer following {0}").format( - package_dict['title'])) - except ValidationError as e: - error_message = (e.message or e.error_summary - or e.error_dict) - h.flash_error(error_message) - except (NotFound, NotAuthorized) as e: - error_message = e.message - h.flash_error(error_message) - h.redirect_to(controller='package', action='read', id=id) - - def followers(self, id=None): - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - - data_dict = {'id': id} - try: - c.pkg_dict = get_action('package_show')(context, data_dict) - c.pkg = context['package'] - c.followers = get_action('dataset_follower_list')( - context, {'id': c.pkg_dict['id']}) - - dataset_type = c.pkg.type or 'dataset' - except NotFound: - abort(404, _('Dataset not found')) - except NotAuthorized: - abort(403, _('Unauthorized to read package %s') % id) - - return render('package/followers.html', - {'dataset_type': dataset_type}) - - def groups(self, id): - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj, 'use_cache': False} - data_dict = {'id': id} - try: - c.pkg_dict = get_action('package_show')(context, data_dict) - dataset_type = c.pkg_dict['type'] or 'dataset' - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - - if request.method == 'POST': - new_group = request.POST.get('group_added') - if new_group: - data_dict = {"id": new_group, - "object": id, - "object_type": 'package', - "capacity": 'public'} - try: - get_action('member_create')(context, data_dict) - except NotFound: - abort(404, _('Group not found')) - - removed_group = None - for param in request.POST: - if param.startswith('group_remove'): - removed_group = param.split('.')[-1] - break - if removed_group: - data_dict = {"id": removed_group, - "object": id, - "object_type": 'package'} - - try: - get_action('member_delete')(context, data_dict) - except NotFound: - abort(404, _('Group not found')) - h.redirect_to(controller='package', action='groups', id=id) - - context['is_member'] = True - users_groups = get_action('group_list_authz')(context, data_dict) - - pkg_group_ids = set(group['id'] for group - in c.pkg_dict.get('groups', [])) - user_group_ids = set(group['id'] for group - in users_groups) - - c.group_dropdown = [[group['id'], group['display_name']] - for group in users_groups if - group['id'] not in pkg_group_ids] - - for group in c.pkg_dict.get('groups', []): - group['user_member'] = (group['id'] in user_group_ids) - - return render('package/group_list.html', - {'dataset_type': dataset_type}) - - def activity(self, id): - '''Render this package's public activity stream page.''' - - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - data_dict = {'id': id} - try: - c.pkg_dict = get_action('package_show')(context, data_dict) - c.pkg = context['package'] - c.package_activity_stream = get_action( - 'package_activity_list_html')( - context, {'id': c.pkg_dict['id']}) - dataset_type = c.pkg_dict['type'] or 'dataset' - except NotFound: - abort(404, _('Dataset not found')) - except NotAuthorized: - abort(403, _('Unauthorized to read dataset %s') % id) - - return render('package/activity.html', - {'dataset_type': dataset_type}) - - def resource_embedded_dataviewer(self, id, resource_id, - width=500, height=500): - """ - Embedded page for a read-only resource dataview. Allows - for width and height to be specified as part of the - querystring (as well as accepting them via routes). - """ - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - - try: - c.resource = get_action('resource_show')(context, - {'id': resource_id}) - c.package = get_action('package_show')(context, {'id': id}) - c.resource_json = h.json.dumps(c.resource) - - # double check that the resource belongs to the specified package - if not c.resource['id'] in [r['id'] - for r in c.package['resources']]: - raise NotFound - dataset_type = c.package['type'] or 'dataset' - - except (NotFound, NotAuthorized): - abort(404, _('Resource not found')) - - # Construct the recline state - state_version = int(request.params.get('state_version', '1')) - recline_state = self._parse_recline_state(request.params) - if recline_state is None: - abort(400, ('"state" parameter must be a valid recline ' - 'state (version %d)' % state_version)) - - c.recline_state = h.json.dumps(recline_state) - - c.width = max(int(request.params.get('width', width)), 100) - c.height = max(int(request.params.get('height', height)), 100) - c.embedded = True - - return render('package/resource_embedded_dataviewer.html', - extra_vars={'dataset_type': dataset_type}) - - def _parse_recline_state(self, params): - state_version = int(request.params.get('state_version', '1')) - if state_version != 1: - return None - - recline_state = {} - for k, v in request.params.items(): - try: - v = h.json.loads(v) - except ValueError: - pass - recline_state[k] = v - - recline_state.pop('width', None) - recline_state.pop('height', None) - recline_state['readOnly'] = True - - # previous versions of recline setup used elasticsearch_url attribute - # for data api url - see http://trac.ckan.org/ticket/2639 - # fix by relocating this to url attribute which is the default location - if 'dataset' in recline_state and \ - 'elasticsearch_url' in recline_state['dataset']: - recline_state['dataset']['url'] = \ - recline_state['dataset']['elasticsearch_url'] - - # Ensure only the currentView is available - # default to grid view if none specified - if not recline_state.get('currentView', None): - recline_state['currentView'] = 'grid' - for k in recline_state.keys(): - if k.startswith('view-') and \ - not k.endswith(recline_state['currentView']): - recline_state.pop(k) - return recline_state - - def resource_views(self, id, resource_id): - package_type = self._get_package_type(id.split('@')[0]) - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - data_dict = {'id': id} - - try: - check_access('package_update', context, data_dict) - except NotAuthorized: - abort(403, _('User %r not authorized to edit %s') % (c.user, id)) - # check if package exists - try: - c.pkg_dict = get_action('package_show')(context, data_dict) - c.pkg = context['package'] - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - - try: - c.resource = get_action('resource_show')(context, - {'id': resource_id}) - c.views = get_action('resource_view_list')(context, - {'id': resource_id}) - - except NotFound: - abort(404, _('Resource not found')) - except NotAuthorized: - abort(403, _('Unauthorized to read resource %s') % id) - - self._setup_template_variables(context, {'id': id}, - package_type=package_type) - - return render('package/resource_views.html') - - def edit_view(self, id, resource_id, view_id=None): - package_type = self._get_package_type(id.split('@')[0]) - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - - # update resource should tell us early if the user has privilages. - try: - check_access('resource_update', context, {'id': resource_id}) - except NotAuthorized as e: - abort(403, _('User %r not authorized to edit %s') % (c.user, id)) - - # get resource and package data - try: - c.pkg_dict = get_action('package_show')(context, {'id': id}) - c.pkg = context['package'] - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - try: - c.resource = get_action('resource_show')(context, - {'id': resource_id}) - except (NotFound, NotAuthorized): - abort(404, _('Resource not found')) - - data = {} - errors = {} - error_summary = {} - view_type = None - to_preview = False - - if request.method == 'POST': - request.POST.pop('save', None) - to_preview = request.POST.pop('preview', False) - if to_preview: - context['preview'] = True - to_delete = request.POST.pop('delete', None) - data = clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( - request.params, ignore_keys=CACHE_PARAMETERS)))) - data['resource_id'] = resource_id - - try: - if to_delete: - data['id'] = view_id - get_action('resource_view_delete')(context, data) - elif view_id: - data['id'] = view_id - data = get_action('resource_view_update')(context, data) - else: - data = get_action('resource_view_create')(context, data) - except ValidationError as e: - # Could break preview if validation error - to_preview = False - errors = e.error_dict - error_summary = e.error_summary - except NotAuthorized: - # This should never happen unless the user maliciously changed - # the resource_id in the url. - abort(403, _('Unauthorized to edit resource')) - else: - if not to_preview: - h.redirect_to(controller='package', - action='resource_views', - id=id, resource_id=resource_id) - - # view_id exists only when updating - if view_id: - try: - old_data = get_action('resource_view_show')(context, - {'id': view_id}) - data = data or old_data - view_type = old_data.get('view_type') - # might as well preview when loading good existing view - if not errors: - to_preview = True - except (NotFound, NotAuthorized): - abort(404, _('View not found')) - - view_type = view_type or request.GET.get('view_type') - data['view_type'] = view_type - view_plugin = datapreview.get_view_plugin(view_type) - if not view_plugin: - abort(404, _('View Type Not found')) - - self._setup_template_variables(context, {'id': id}, - package_type=package_type) - - data_dict = {'package': c.pkg_dict, 'resource': c.resource, - 'resource_view': data} - - view_template = view_plugin.view_template(context, data_dict) - form_template = view_plugin.form_template(context, data_dict) - - vars = {'form_template': form_template, - 'view_template': view_template, - 'data': data, - 'errors': errors, - 'error_summary': error_summary, - 'to_preview': to_preview, - 'datastore_available': p.plugin_loaded('datastore')} - vars.update( - view_plugin.setup_template_variables(context, data_dict) or {}) - vars.update(data_dict) - - if view_id: - return render('package/edit_view.html', extra_vars=vars) - - return render('package/new_view.html', extra_vars=vars) - - def resource_view(self, id, resource_id, view_id=None): - ''' - Embedded page for a resource view. - - Depending on the type, different views are loaded. This could be an - img tag where the image is loaded directly or an iframe that embeds a - webpage or a recline preview. - ''' - context = {'model': model, - 'session': model.Session, - 'user': c.user, - 'auth_user_obj': c.userobj} - - try: - package = get_action('package_show')(context, {'id': id}) - except (NotFound, NotAuthorized): - abort(404, _('Dataset not found')) - - try: - resource = get_action('resource_show')( - context, {'id': resource_id}) - except (NotFound, NotAuthorized): - abort(404, _('Resource not found')) - - view = None - if request.params.get('resource_view', ''): - try: - view = json.loads(request.params.get('resource_view', '')) - except ValueError: - abort(409, _('Bad resource view data')) - elif view_id: - try: - view = get_action('resource_view_show')( - context, {'id': view_id}) - except (NotFound, NotAuthorized): - abort(404, _('Resource view not found')) - - if not view or not isinstance(view, dict): - abort(404, _('Resource view not supplied')) - - return h.rendered_resource_view(view, resource, package, embed=True) - - def resource_datapreview(self, id, resource_id): - ''' - Embedded page for a resource data-preview. - - Depending on the type, different previews are loaded. This could be an - img tag where the image is loaded directly or an iframe that embeds a - webpage, or a recline preview. - ''' - context = { - 'model': model, - 'session': model.Session, - 'user': c.user, - 'auth_user_obj': c.userobj - } - - try: - c.resource = get_action('resource_show')(context, - {'id': resource_id}) - c.package = get_action('package_show')(context, {'id': id}) - - data_dict = {'resource': c.resource, 'package': c.package} - - preview_plugin = datapreview.get_preview_plugin(data_dict) - - if preview_plugin is None: - abort(409, _('No preview has been defined.')) - - preview_plugin.setup_template_variables(context, data_dict) - c.resource_json = json.dumps(c.resource) - dataset_type = c.package['type'] or 'dataset' - except (NotFound, NotAuthorized): - abort(404, _('Resource not found')) - else: - return render(preview_plugin.preview_template(context, data_dict), - extra_vars={'dataset_type': dataset_type}) diff --git a/ckanext/justicehub_theme/fanstatic/justicehub_theme.js b/ckanext/justicehub_theme/fanstatic/justicehub_theme.js new file mode 100644 index 0000000..d572298 --- /dev/null +++ b/ckanext/justicehub_theme/fanstatic/justicehub_theme.js @@ -0,0 +1,135 @@ +// Read More effect. +// https://www.viralpatel.net/dynamically-shortened-text-show-more-link-jquery/ +$(document).ready(function() { + var showChar = 215; + var ellipsestext = "..."; + var moretext = "more"; + var lesstext = "less"; + $('.more').each(function() { + var content = $(this).html(); + + if(content.length > showChar) { + + var c = content.substr(0, showChar); + var h = content.substr(showChar-1, content.length - showChar); + + var html = c + '' + ellipsestext+ ' ' + h + '  ' + moretext + ''; + + $(this).html(html); + } + }); + + $(".morelink").click(function(){ + if($(this).hasClass("less")) { + $(this).removeClass("less"); + $(this).html(moretext); + } else { + $(this).addClass("less"); + $(this).html(lesstext); + } + $(this).parent().prev().toggle(); + $(this).prev().toggle(); + return false; + }); +}); + + +// tooltip enabled. +$('[data-toggle="tooltip"]').tooltip(); + +// counting animation for stats. +// https://stackoverflow.com/a/23006629/3860168 +$('.stats-big').each(function () { + var $this = $(this); + $({ value: 0 }).animate({ Counter: $this.text() }, { + duration: 500, /* NOTE: this duration should later be increased if the count goes up. */ + easing: 'swing', + step: function () { + $this.text(Math.ceil(this.Counter)); + } + }); +}); + +// bootstrap nav-tabs. +ckan.module('justicehub_theme_tabs', function ($) { + return { + initialize: function () { + + // change the page location to a tab on first load if the url has a hash. + this.onHashChange(); + + // on every url hash change, load the relevant tab. + window.onhashchange = this.onHashChange; + + // on clicking any tab, change the url hash. + $('.nav-tabs li a').on('click', this.onTabClick); + }, + + onHashChange: function () { + // https://stackoverflow.com/a/9393768/3860168 + hash = window.location.hash; + $('.nav-tabs li a[href="'+hash+'"]').tab('show'); + }, + + onTabClick: function () { + // https://stackoverflow.com/a/9393768/3860168 + window.location.hash = this.hash; + } + }; +}); + +// subscribe to mailing. +ckan.module('justicehub_theme_subscribe', function ($) { + return { + initialize: function () { + $('[data-toggle="popover"]').popover(); + + function subscribeSubmit(event) { + $('[data-toggle="popover"]').popover('hide'); + + console.log(event); + var url = event.target.action; + var request = new XMLHttpRequest(); + + request.open('POST', url, true); + + request.onload = function() { + // success + response = JSON.parse(request.response) + + if (response.errors) { + msg = response.errors[0].message; + msg = " " + msg; + + } + else if (response.status == 400 && response.errors==undefined) { + msg = response.detail.split('. ')[0]; + msg = " " + msg; + } + else if (response.status == 'subscribed') { + msg = "You've been successfully Subscribed!"; + msg = " " + msg; + } + + console.log(msg); + if ($(window).width() < 768) { + $('[data-toggle="popover"][class*="visible"]').data('bs.popover').options.content=msg; + $('[data-toggle="popover"][class*="visible"]').popover('show'); + } else { + $('[data-toggle="popover"]').data('bs.popover').options.content=msg; + $('[data-toggle="popover"]').popover('show'); + } + }; + + request.onerror = function() { + // request failed + }; + + request.send(new FormData(event.target)); + event.preventDefault(); + } + console.log("initialized"); + document.getElementById("subscribe-form").addEventListener("submit", subscribeSubmit); + } + }; +}); diff --git a/ckanext/justicehub_theme/fanstatic/subscribe.js b/ckanext/justicehub_theme/fanstatic/subscribe.js deleted file mode 100644 index fade516..0000000 --- a/ckanext/justicehub_theme/fanstatic/subscribe.js +++ /dev/null @@ -1,26 +0,0 @@ -ckan.module('justicehub_theme_subscribe', function ($) { - return { - initialize: function () { - function subscribeSubmit(event) { - console.log(event); - var url = event.target.action; - var request = new XMLHttpRequest(); - request.open('POST', url, true); - request.onload = function() { - // success - console.log(request.responseText); - }; - - request.onerror = function() { - // request failed - }; - - request.send(new FormData(event.target)); - event.preventDefault(); - } - console.log("initialized"); - document.getElementById("subscribe-form").addEventListener("submit", subscribeSubmit); - } - }; - }); - \ No newline at end of file diff --git a/ckanext/justicehub_theme/controllers/stats.py b/ckanext/justicehub_theme/lib/__init__.py similarity index 100% rename from ckanext/justicehub_theme/controllers/stats.py rename to ckanext/justicehub_theme/lib/__init__.py diff --git a/ckanext/justicehub_theme/lib/helpers.py b/ckanext/justicehub_theme/lib/helpers.py new file mode 100644 index 0000000..4e28eec --- /dev/null +++ b/ckanext/justicehub_theme/lib/helpers.py @@ -0,0 +1,110 @@ +import json +from datetime import datetime + +from ckanext.issues.controller.controller import issues_for_dataset +from dateutil.parser import parse as parse_dates +from sqlalchemy import MetaData +from sqlalchemy.sql import select + +import ckan.logic as logic +import ckan.model as model +from ckan.common import c + +cached_tables = {} + +def is_org_admin(username, org): + for user in org['users']: + if user['name'] == username and user['capacity'] == 'admin': + return True + +def issues_vars(dataset_id, request): + extra_vars = issues_for_dataset(dataset_id, request.GET) + return extra_vars + +def get_assignee_user(user_id): + ''' + return user object for a user id. + ''' + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + user = model.Session.query(model.User)\ + .filter(model.User.id == user_id)\ + .distinct()\ + .first() + return user + + +def parse_json(string): + return json.loads(string) + + +def get_package_avg_downloads(pkg): + ''' + Returns the average of total downloads of all the resources of a package. + ''' + total_downloads = 0 + avg_downloads = 0 + + if not pkg['resources']: + return avg_downloads + + for resource in pkg['resources']: + if isinstance(resource['downloads'], unicode): + resource['downloads'] = json.loads(resource['downloads'].replace("'", '"')) + + total_downloads += resource['downloads']['total'] + + avg_downloads = total_downloads / float(len(pkg['resources'])) + return int(avg_downloads) + + +def package_activity_stream(id): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + package_activity_stream = logic.get_action('package_activity_list_html')(context, {'id': id}) + return package_activity_stream + + +def get_table(name): + if name not in cached_tables: + meta = MetaData() + meta.reflect(bind=model.meta.engine) + table = meta.tables[name] + cached_tables[name] = table + return cached_tables[name] + + +def get_resource_downloads(resource): + connection = model.Session.connection() + resource_stats = get_table('resource_stats') + s = select([resource_stats.c.resource_id, + resource_stats.c.visits_recently, + resource_stats.c.visits_ever] + ).where(resource_stats.c.resource_id == resource['id']) + res = connection.execute(s).fetchone() + if res: + return {'total' : res.visits_ever, + 'recent': res.visits_recently} + + return {'total': 0, 'recent': 0} + + +def get_package_visits(pkg): + ''' + Returns the number of times the package is accessed + ''' + connection = model.Session.connection() + total = 0 + recent = 0 + tracking_summary = get_table('tracking_summary') + s = select([tracking_summary.c.package_id, + tracking_summary.c.recent_views, + tracking_summary.c.running_total] + ).where(tracking_summary.c.package_id == pkg['id']) + res = connection.execute(s).fetchone() + if res: + total = res.running_total + recent = res.recent_views + return {'total' : total, 'recent': recent} diff --git a/ckanext/justicehub_theme/logic/action/__init__.py b/ckanext/justicehub_theme/logic/action/__init__.py index 8ad7b82..249778e 100644 --- a/ckanext/justicehub_theme/logic/action/__init__.py +++ b/ckanext/justicehub_theme/logic/action/__init__.py @@ -1,4 +1,6 @@ from action import ( partner_users_autocomplete, - get_package_owner_details + get_package_owner_details, + metadata_autocomplete, + resource_metadata_autocomplete ) diff --git a/ckanext/justicehub_theme/logic/action/action.py b/ckanext/justicehub_theme/logic/action/action.py index 58d7778..84adbdd 100644 --- a/ckanext/justicehub_theme/logic/action/action.py +++ b/ckanext/justicehub_theme/logic/action/action.py @@ -1,17 +1,11 @@ from ckanext.issues.logic import schema +from six import string_types import ckan.lib.dictization.model_dictize as model_dictize -import ckan.lib.plugins as lib_plugins -import ckan.logic as logic import ckan.model as model import ckan.plugins as p from ckan.logic import validate -try: - import ckan.authz as authz -except ImportError: - import ckan.new_authz as authz - @p.toolkit.side_effect_free @validate(schema.organization_users_autocomplete_schema) @@ -48,9 +42,68 @@ def get_package_owner_details(context, data_dict): include_extras=True ) - group_plugin = lib_plugins.lookup_group_plugin(partner_dict['type']) - schema = logic.schema.default_show_group_schema() - partner, errors = lib_plugins.plugin_validate( - group_plugin, context, partner_dict, schema, 'organization_show' - ) - return partner + # FIXME: commented becaues this validation causes solr to fail reindexing + # group_plugin = lib_plugins.lookup_group_plugin(partner_dict['type']) + # schema = logic.schema.default_show_group_schema() + # partner, errors = lib_plugins.plugin_validate( + # group_plugin, context, partner_dict, schema, 'organization_show' + # ) + for item in partner_dict['extras']: + if item['state'] == 'active': + partner_dict[item['key']] = item['value'] + + partner_dict.pop('extras') + return partner_dict + + +@p.toolkit.side_effect_free +def metadata_autocomplete(context, data_dict): + session = context['session'] + field = data_dict['field'] + is_list = data_dict.get('islist', False) + term = data_dict['incomplete'] + limit = data_dict.get('limit', 10) + matching = [] + + if term and isinstance(term, string_types) and is_list: + results = session.execute( + """ + select term + from ( + select unnest(string_to_array(value, ',')) as term + from package_extra where key='{field}' + ) terms + where term ilike '%{term}%' + limit {limit}; + """.format(field=field, term=term, limit=limit) + ).fetchall() + if results: + matching = [result for result in results[0]] + + elif term and isinstance(term, string_types): + q = model.Session.query(model.PackageExtra) + results = q.filter(model.PackageExtra.key == field)\ + .filter(model.PackageExtra.value.ilike('{0}%'.format(term)))\ + .distinct()\ + .limit(limit) + + matching = [result.value for result in results] + return matching + +@p.toolkit.side_effect_free +def resource_metadata_autocomplete(context, data_dict): + session = context['session'] + field = data_dict['field'] + term = data_dict['incomplete'] + limit = data_dict.get('limit', 10) + matching = [] + + if term and isinstance(term, string_types): + results = session.execute("""select extras::json->>'{field}' + from resource + where extras::json->>'{field}' ilike '{term}%' + limit {limit};""".format(field=field, term=term, limit=limit) + ).fetchall() + matching = [result[0] for result in results] + + return matching diff --git a/ckanext/justicehub_theme/plugin.py b/ckanext/justicehub_theme/plugin.py index 0173654..e8d55cd 100644 --- a/ckanext/justicehub_theme/plugin.py +++ b/ckanext/justicehub_theme/plugin.py @@ -1,94 +1,11 @@ -import json - -import requests - import ckanext.justicehub_theme.logic.action as jh_action -from sqlalchemy import MetaData -from sqlalchemy.sql import select +from ckanext.justicehub_theme.lib import helpers -import ckan.logic as logic -import ckan.lib.base as base import ckan.model as model import ckan.plugins as plugins import ckan.plugins.toolkit as toolkit -from ckan.common import c,request -import ckan.lib.navl.dictization_functions as dict_fns - -from pylons import config - -import ckan.controllers.organization as org - -cached_tables = {} -clean_dict = logic.clean_dict -parse_params = logic.parse_params -tuplize_dict = logic.tuplize_dict - -def package_activity_stream(id): - context = {'model': model, 'session': model.Session, - 'user': c.user, 'for_view': True, - 'auth_user_obj': c.userobj} - package_activity_stream = logic.get_action('package_activity_list_html')(context, {'id': id}) - return package_activity_stream - -def get_table(name): - if name not in cached_tables: - meta = MetaData() - meta.reflect(bind=model.meta.engine) - table = meta.tables[name] - cached_tables[name] = table - return cached_tables[name] - -def get_resource_downloads(resource): - connection = model.Session.connection() - resource_stats = get_table('resource_stats') - s = select([resource_stats.c.resource_id, - resource_stats.c.visits_recently, - resource_stats.c.visits_ever] - ).where(resource_stats.c.resource_id == resource['id']) - res = connection.execute(s).fetchone() - if res: - return {'total' : res.visits_ever, - 'recent': res.visits_recently} - - return {'total': 0, 'recent': 0} - -def get_package_avg_downloads(pkg): - ''' - Returns the average of total downloads of all the resources of a package. - ''' - total_downloads = 0 - avg_downloads = 0 - - if not pkg['resources']: - return avg_downloads +from ckan.common import c - for resource in pkg['resources']: - if isinstance(resource['downloads'], unicode): - resource['downloads'] = json.loads(resource['downloads'].replace("'", '"')) - - total_downloads += resource['downloads']['total'] - - avg_downloads = total_downloads / float(len(pkg['resources'])) - return int(avg_downloads) - -def get_package_visits(pkg): - ''' - Returns the number of times the package is accessed - ''' - connection = model.Session.connection() - total = 0 - recent = 0 - tracking_summary = get_table('tracking_summary') - s = select([tracking_summary.c.package_id, - tracking_summary.c.recent_views, - tracking_summary.c.running_total] - ).where(tracking_summary.c.package_id == pkg['id']) - res = connection.execute(s).fetchone() - if res: - total = res.running_total - recent = res.recent_views - return {'total' : total, - 'recent': recent} class Justicehub_ThemePlugin(plugins.SingletonPlugin): plugins.implements(plugins.IConfigurer) @@ -114,9 +31,13 @@ def get_helpers(self): # extension they belong to, to avoid clashing with functions from # other extensions. return { - 'justicehub_theme_get_package_avg_downloads': get_package_avg_downloads, - 'justicehub_theme_package_activity_stream': package_activity_stream - } + 'justicehub_theme_get_package_avg_downloads': helpers.get_package_avg_downloads, + 'justicehub_theme_package_activity_stream': helpers.package_activity_stream, + 'justicehub_theme_parse_json': helpers.parse_json, + 'justicehub_theme_get_assignee_user': helpers.get_assignee_user, + 'justicehub_theme_issue_vars': helpers.issues_vars, + 'justicehub_theme_is_org_admin': helpers.is_org_admin + } #IActions def get_actions(self): @@ -138,7 +59,7 @@ def before_view(self, package_dict): and should return a modified (or not) version of it. ''' for resource_dict in package_dict['resources']: - resource_dict['downloads'] = get_resource_downloads(resource_dict) + resource_dict['downloads'] = helpers.get_resource_downloads(resource_dict) package_dict['tracking_summary'] = ( model.TrackingSummary.get_for_package(package_dict['id'])) @@ -178,7 +99,11 @@ def after_delete(self, context, pkg_dict): pass def after_show(self, context, pkg_dict): - pass + partner = toolkit.get_action('get_package_owner_details')( + context, + data_dict={'org_id': pkg_dict['owner_org']} + ) + pkg_dict['partner'] = partner def before_index(self, pkg_dict): return pkg_dict @@ -186,113 +111,25 @@ def before_index(self, pkg_dict): # IRoute def after_map(self, map): map.connect('jhorg_members', '/jhorg/members/{id}', - controller='ckanext.justicehub_theme.plugin:JHOrgController', + controller='ckanext.justicehub_theme.controllers:JHOrgController', action='members') map.connect('jhorg_stats', '/jhorg/stats/{id}', - controller='ckanext.justicehub_theme.plugin:JHOrgController', + controller='ckanext.justicehub_theme.controllers:JHOrgController', action='org_stats') map.connect('jhsubscribe', '/subscribe', - controller='ckanext.justicehub_theme.plugin:SubscribeController', + controller='ckanext.justicehub_theme.controllers:SubscribeController', action='subscribe') + return map def before_map(self, map): map.connect('jhorg_members', '/organization/members/{id}', - controller='ckanext.justicehub_theme.plugin:JHOrgController', + controller='ckanext.justicehub_theme.controllers:JHOrgController', action='members') map.connect('jhorg_stats', '/jhorg/stats/{id}', - controller='ckanext.justicehub_theme.plugin:JHOrgController', + controller='ckanext.justicehub_theme.controllers:JHOrgController', action='org_stats') map.connect('jhsubscribe', '/subscribe', - controller='ckanext.justicehub_theme.plugin:SubscribeController', + controller='ckanext.justicehub_theme.controllers:SubscribeController', action='subscribe') return map - - -class JHOrgController(org.OrganizationController): - def members(self, id): - ''' - Modified core method from 'group' controller. - Enable view for non signed in user. - - :param id: id of the organization for which the member list is requested - :type id: string - :return: the rendered template - :rtype: unicode - ''' - group_type = self._ensure_controller_matches_group_type(id) - - context = {'model': model, 'session': model.Session, - 'user': c.user} - - data_dict = {'id': id} - try: - c.members = self._action('member_list')( - context, {'id': id, 'object_type': 'user'} - ) - data_dict['include_datasets'] = False - c.group_dict = self._action('group_show')(context, data_dict) - except NotFound: - abort(404, _('Oraganization not found')) - - return self._render_template('organization/jh_members.html', group_type) - - def org_stats(self, id): - group_type = self._ensure_controller_matches_group_type(id) - - context = {'model': model, 'session': model.Session, - 'user': c.user, - 'schema': self._db_to_form_schema(group_type=group_type), - 'for_view': True} - data_dict = {'id': id, 'type': group_type} - # unicode format (decoded from utf8) - c.q = '' - - try: - data_dict['include_datasets'] = True - c.group_dict = self._action('group_show')(context, data_dict) - c.group = context['group'] - except NotFound: - abort(404, _('Organization not found')) - self._read(id, 999, group_type) - downloads = 0 - visits = 0 - for package in c.page.items: - downloads += get_package_avg_downloads(package) - visits += get_package_visits(package)['total'] - c.group_dict.update({'downloads':downloads, 'visits': visits}) - return plugins.toolkit.render('organization/stats.html', extra_vars={'group_type': group_type, 'downloads': downloads}) - -class SubscribeController(base.BaseController): - def subscribe(self): - basic_auth_key = config.get("mailchimp_api_key", "") - subscriber_list_id = config.get("mailchimp_list_id", "") - subscriber_tag_id = config.get("mailchimp_tag_id", "") - mailchimp_base_url = config.get("mailchimp_base_url", "") - url = mailchimp_base_url + subscriber_list_id + "/members" - - request_body = clean_dict(dict_fns.unflatten( - tuplize_dict(parse_params(request.params)))) - - payload = { - "email_address": str(request_body.get("email", "")), - "status": "subscribed" - } - headers = { - 'Content-Type': 'application/json', - 'Authorization': 'Basic '+ basic_auth_key - } - - response = requests.request("POST", url, headers=headers, data = json.dumps(payload)) - - print(response.text.encode('utf8')) - - tag_url = mailchimp_base_url + subscriber_list_id + "/segments/" + subscriber_tag_id + "/members" - - payload = { - "email_address": str(request_body.get("email", "")) - } - - response = requests.request("POST", tag_url, headers=headers, data = json.dumps(payload)) - - print response.text.encode('utf8') \ No newline at end of file diff --git a/ckanext/justicehub_theme/public/common.css b/ckanext/justicehub_theme/public/common.css new file mode 100644 index 0000000..45b765e --- /dev/null +++ b/ckanext/justicehub_theme/public/common.css @@ -0,0 +1,514 @@ +/* ---------------------- Site Fonts ------------------- */ +.radios label, +.popover .popover-content, +.footer-links .input-group input, +body { + font-family: 'Montserrat', sans-serif; + font-weight: 400; /* Regular */ +} +.res-meta, +.tooltip, +.pkg_tags, +.popover .popover-content, +.footer-links .nav-item a { + font-size: 14px; +} +.badge, +.count, +.sort-box, +.dataset-meta, +.control-label, +.input-group-addon, +.package-item-row, +.dataset-desc, +.rsrc-list-side > li a, +.dropdown-menu > li > a, +.context-info .info, +.footer-links .input-group-btn .btn { + font-size: 16px; +} +.dl-horizontal, +.resource-item .heading, +.resource-item .description, +.nav-simple .nav-item a, +.nav-facet .nav-item a, +.filters .module-content.empty, +.downloads, +.nav-pills li, +.partner-links.social-links .fa.fa-envelope { + font-size: 18px; +} +body, +.page-heading, +.dataset-title, +.dataset-org, +.action a, +.res-actions a, +.issues-actions a, +.res-actions a, +.content_action a, +.btn, +.login-form .checkbox, +#field-giant-search, +.partner-links.social-links .fa, +.search-form .search-input.search-giant input { + font-size: 20px; +} +h1, +.col2 p, +.stages li, +.nav-tabs > li > a, +.module-heading, +.metadata-heading, +.login-sidebar-heading, +.footer-intro { + font-size: 25px; +} +.col3 h2 { + font-size: 30px; +} +.landing-desc { + font-size: 40px; +} +.intro-box span { + font-size: 50px; +} +.stats-big { + font-size: 60px; +} +.fa.fa-circle { + font-size: 90px; +} +.footer-links .nav-item a, +#field-giant-search, +.footer-links .input-group input::placeholder, +.search-form .search-input.search-giant input { + font-weight: 300; /* Light */ +} +.badge, +.intro-box, +.landing-desc, +.resource-item .description, +.dataset-org, +.res-meta, +.dataset-meta, +.nav-pills li, +.footer-intro-invert, +.footer-disclaimer { + font-weight: 500; /* Medium */ +} +.count, +.activity .item a, +.rsrc-list-side > li a, +.media-item .media-heading, +.footer-disclaimer div:last-child { + font-weight: 600; /* Semi Bold */ +} +.stats-box, +.pkg_tags a, +.sort-box, +.dataset-count-box, +.input-group-addon, +.col3 h2, +.stages li, +.nav-tabs > li > a, +[class^="btn btn-"], +.action a, +.res-actions a, +.content_action a, +.dataset-title, +.module-heading, +.metadata-heading, +.popover .popover-content, +.intro-box span:nth-child(2), +.footer-links .input-group-btn .btn { + font-weight: 700; /* Bold */ +} +.stats-big, +.footer-intro { + font-weight: 800; /* Extra Bold */ +} +.col2 p, +.downloads, +.activity .item, +.intro-box span:nth-child(2), +.footer-disclaimer div:last-child, +.filters .module-content.empty, +#field-giant-search, +.footer-links .input-group input, +.search-form .search-input.search-giant input { + font-style: italic; +} + +/* ----------------------- colors ------------------------- */ +a, +code, +.count, +.media-item, +.res-title, +.fa-check, +.btn-xs, +.primary-color, +.btn-xs:active:hover, +.bg-clear.btn, +.bg-clear.btn:hover, +.bg-clear.btn:active:focus, +.fa-certificate, +.dataset-org > a, +.activity .item .date, +.activity .item .actor a, +.links-list a, +.subscribe-error, +.login-form .checkbox, +.footer-links .nav-item > a:hover, +.footer-disclaimer div:last-child { + color: #f65940; +} +.homepage .col2, +.metadata-list dt, +.activity .item a { + color: #bcb4bf; +} +.res-meta, +.downloads, +.dataset-meta { + color: #707070; +} +.subscribe-success { + color: #10c6a0; +} +body, +.stats-box, +.res-title a, +.fa.fa-lock, +.fa.fa-trash, +.secondary-color, +.fa.fa-unlock-alt, +.dataset-title a, +.resource-item .heading, +.page-header .nav-tabs li a, +.footer-intro-invert, +.stages li .highlight, +.metadata-list dd, +.issue-list-group li a, +.stages li.complete .highlight { + color: #403644; +} +.popover .popover-content, +.context-info .nums dl, +.homepage .col3, +.secondary, +.landing-desc, +.site-footer, +.pkg_tags a, +.nav-facet, +.nav-simple .nav-item a, +.module-heading, +.metadata-heading, +.activity-item .activity .item, +.filters .module-heading, +.filters .module-content.empty, +.nav-facet .nav-item > a, +.member_action .delete, +.footer-links .nav-item > a, +.btn-register:hover, +[class^="btn btn-"].disabled:hover, +.context-info .follow_button .btn:hover, +.dropdown-menu > li > a, +.dropdown-menu > li > a:hover, +.nav-tabs-plain li.active a:hover, +.page-header .nav-tabs li a:hover, +.nav-tabs-plain li.active a, +.page-header .nav-tabs li.active a, +.select2-container-multi .select2-choices > li.select2-search-choice { + color: #ffffff; +} +.media-grid, +.members-table thead th, +.stages li .highlight, +.stages li.complete .highlight, +.subscribe-box .btn.btn-default:hover, +[role="main"], body { + background: none; + background-color: #ffffff; +} +.secondary, +.masthead, +.landing-desc, +.site-footer, +.account-masthead, +.nav-facet, +.sidebar, +.site-footer .popover, +.dropdown-menu, +[class^="btn btn-"], +[class^="btn btn-"].disabled:hover, +.nav-tabs .content_action a, +.stages li.active .highlight, +.metadata-heading, +.nav-tabs-plain li.active a, +.page-header .nav-tabs li.active a, +.filters .module-heading, +.filters .module-content.empty, +.footer-links .input-group-btn .btn, +.btn-register:hover, +.context-info .follow_button .btn:hover, +.dropdown-menu > li a, +.masthead .navigation .nav-pills > li > a:hover, +.masthead .navigation .nav-pills > li > a:active, +.masthead .navigation .nav-pills > li > a:focus, +.masthead .navigation .nav-pills > li.active > a, +.navigation .open a.dropdown-toggle, +.landing-desc .social-links .fa:hover, +.footer-links .social-links .fa:hover, +.composite-control-repeating .btn-success, +[role="main"] .container .row .col3 { + background: none; + background-color: #403644; + border-color: #403644; +} +.context-info .follow_button .btn, +.subscribe-box, +.action a, +.content_action a, +.content_action .btn.btn-danger, +.res-actions a, +.login-sidebar-heading, +h2.module-heading, +.member_action .delete, +.nav-facet .nav-item.active, +.nav-facet .nav-item.active a, +.composite-control-repeating .btn-danger, +.masthead .navigation .nav-pills .dropdown-menu > li a:hover, +.nav-pills li.active a, +.nav-pills li.active a:hover, +.nav-pills li a:hover, +.dropdown-menu > li a:hover, +.nav-tabs-plain li.active a:hover, +.page-header .nav-tabs li a:hover, +.select2-results .select2-highlighted { + background: none; + background-color: #f65940; + border-color: #f65940; +} +.activity .item .fa[class*="fa"] { + background-color: #00abb7; +} +.pkg_tags { + background-color: #bcb4bf; +} +.table-striped tbody tr:nth-child(2n+1) td, +.table-striped tbody tr:nth-child(2n+1) th { + background-color: #c1bbc3; +} +.label[data-format="doc"], +.label[data-format*="doc"] { + background-color: #115abe; +} +.label[data-format="tsv"], +.label[data-format*="tsv"] { + background-color: #7ca34c; +} +.label[data-format="zip"], +.label[data-format*="zip"] { + background-color: #7ca34c; +} +.label[data-format="ods"], +.label[data-format*="ods"] { + background-color: #2db55d; +} +.label[data-format="odb"], +.label[data-format*="odb"] { + background-color: #85c12a; +} +.label[data-format="txt"], +.label[data-format*="txt"] { + background-color: #55a4b5; +} +.label[data-format="odt"], +.label[data-format*="odt"] { + background-color: #862112; +} +.module-heading { + border-bottom-color: #f65940; +} +.subscribe-box .input-group .form-control, +.btn-register:hover, +.context-info .follow_button .btn:hover { + border-color: #ffffff; +} +.subscribe-box .btn.btn-default:hover { + border-color: #f65940; +} +.nav-tabs-plain, +.page-header .nav-tabs { + border-bottom: 2px solid #f65940; +} + +/* ---------------------- social links ------------------ */ +/* https://www.w3schools.com/howto/howto_css_social_media_buttons.asp */ +.social-links a { + text-decoration: none; +} +.social-links .fa { + padding: 12px 5px; + font-size: 35px; + width: 50px; + height: 50px; + text-decoration: none; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; +} +.fa.fa-envelope { + font-size: 30px; +} +.social-links li { + margin-right: 12px; +} +.social-links li:first-of-type { + padding-left: 0; +} +.social-links .fa { + background-color: #403644; + color: #ffffff; +} +.social-links .fa:hover { + background-color: #ffffff; + border: 1px solid #403644; + color: #403644; +} + +/* --------------------- icons ------------------------- */ +img.fa.fa-check { + width: 20px; + height: 20px; +} +img.fa-check.fa-lg { + width: 28px; + height: 28px; +} +img.fa.fa-certificate { + width: 21px; + height: 27px; +} +img.fa-certificate.fa-lg { + width: 22px; + height: 28px; +} +.dropdown-toggle .caret { + width: 10px; +} +.subscribe-error, +.subscribe-success { + font-size: 26px; + width: 35px; + height: 35px; + border-radius: 50%; + text-align: center; + background-color: #ffffff; + padding: 3px; + margin-right: 10px; +} + +/* --------------------- stats ------------------------- */ +.stats-box { + margin-top: 50px; + margin-bottom: 20px; +} +.stats-big { + display: block; + line-height: 1; +} + +/* ------------------- labels ------------------------- */ +.label { + display: inline-block; + padding: 5px 15px; + border-radius: 25px; +} + +/* -------------------------------------- Mobile adjustments ----------------------------- */ +@media (min-width:320px) and (orientation: portrait) { + .count, + .pkg_tags, + .sort-box, + .res-meta, + .downloads, + .dataset-meta, + .input-group-addon, + .package-item-row, + .dataset-count-box, + .resource-item .description, + .footer-links .nav-item a { + font-size: 11px; + } + .dataset-desc { + font-size: 12px; + } + h3, + .btn, + .col2 p, + .stats-box, + .content_action a, + .dataset-title, + .dataset-org, + .media-heading, + .nav-tabs li a, + .nav-pills li, + .dl-horizontal, + .dropdown-menu > li > a, + .toolbar .breadcrumb, + .resource-item .heading, + .table thead tr th.table-actions .btn, + body { + font-size: 14px; + } + h1, h2, + .landing-desc, + .page-heading, + .control-label, + #field-giant-search, + .search-form .search-input.search-giant input, + .login-form .checkbox, + .footer-intro, + .btn.bg-clear { + font-size: 14px; + } + .intro-box span, + .stages .highlight, + .metadata-heading { + font-size: 18px; + } + .stats-big { + font-size: 30px; + } + .fa.fa-circle { + font-size: 60px; + } + .nav-pills li.dropdown a.dropdown-toggle:active, + .nav-pills li.dropdown a.dropdown-toggle:focus { + background-color: #f65940; + } + .footer-intro-invert { + color: #ffffff; + } + .subscribe-box { + background-color: #403644; + } + .page-header .nav-tabs { + border-bottom: none; + } + img.fa.fa-certificate { + width: 12px; + height: 16px; + } + img.fa.fa-check { + width: 13px; + height: 14px; + } + .stats-big { + text-align: center; + } +} diff --git a/ckanext/justicehub_theme/public/dataset.css b/ckanext/justicehub_theme/public/dataset.css new file mode 100644 index 0000000..ae8e289 --- /dev/null +++ b/ckanext/justicehub_theme/public/dataset.css @@ -0,0 +1,215 @@ +/* ------------- package-item --------------- */ +.dataset-title { + margin-top: 0; + margin-bottom: 0; +} +.dataset-org { + margin-bottom: 20px; +} +.dataset-resources { + margin-top: 0; +} + +/* ------------- package-detail ------------ */ +h1+.dataset-meta { + margin-bottom: 30px; +} +.dataset-meta { + margin-bottom: 10px; +} +.resource-list { + margin-top: 20px; +} +.pkg_tags { + border-radius:30px; + padding: 5px 20px; + display:inline-flex; +} +.dataset-desc { + width:80%; + margin-bottom:20px; + text-align: justify; +} +.row.dataset-meta { + padding: 10px 0; +} +.org-box { + width: 350px; + height: 350px; + margin-top: -30px; +} +.activity { + background:none +} +.activity .item .date { + display:block +} +.actor .gravatar { + display:none +} +.activity .item a { + font-style: normal; +} +.activity .item .icon { + height: 45px; + width: 45px; + display: flex; + justify-content: center; + align-items: center; + font-size: 25px; +} +.activity .item { + margin: 25px; +} +.activity .item .fa { + margin-top: 10px; +} +.activity .item::after { + margin-bottom: 30px; +} +.page-section { + border-top: 2px solid #f65940; +} +.additional-info .table { + width: 65%; +} +.additional-info thead tr th, +.additional-info tbody tr td { + padding: 10px; + padding-left: 5%; +} + +/* -------------------------- files ----------------------- */ +.resource-item .heading:hover { + text-decoration: none; +} +/* -------------------------- links ----------------------- */ +.links-list.dl-horizontal dt { + width: 30%; + text-align: left; +} +.links-list.dl-horizontal dd { + width: 70%; + margin-left: 35%; +} + +/* -------------------------- metadata ----------------------- */ +.metadata-list { + margin: 30px 15px; +} +.metadata-list.dl-horizontal dt { + display: inline; +} +.metadata-list.dl-horizontal dd { + display: inline-block; + margin-left: 0; + width: 70%; +} +.metadata-list.dl-horizontal dt { + width: 30%; + margin-left: -1%; + text-align: left; +} +.metadata-list.dl-horizontal dt strong { + float: right; +} +/* -------------------------- dataset-edit ----------------------- */ +.btn-default .fa-bars+span { + display: none; +} +.btn-default .fa-bars::before { + display: none; +} +.btn-default .fa-bars:after { + content: ' REORDER'; + font-family: 'Montserrat', sans-serif; + font-weight: 700; + font-size: 20px; +} +.stages { + border-bottom: 2px solid #f65940; + margin-left: -30px; + margin-top: -30px; +} +.stages li { + padding: 0; +} +.stages li .highlight { + position: static; + text-align: center; + padding: 20px 0; + text-indent: 5mm; /* important for responsive mode */ +} +.stages li:before { + content: none; +} +.stages li:after { + top: 0; + border:none; + margin: 0; +} +.metadata-heading { + padding: 15px 0; + width: 50%; + display: inline-block; + text-align: center; +} +.metadata-small-heading { + padding: 10px 0; + width: 270px; +} +.metadata-small-heading:hover { + background-color: #f65940; +} + +/* --------------------- responsive ------------------------- */ +@media (min-width: 320px) and (orientation: portrait) { + .page-section { + padding-right: 0; + } + .metadata-heading { + padding: 20px 50px; + margin-left: 0px; + width: 100%; + text-align: left; + } + .metadata-small-heading { + padding: 10px 50px; + } + .additional-info .table { + width: 100%; + margin-left: 0; + } + .partner-links.social-links { + margin-top: 5%; + float: right; + } + .links-list.dl-horizontal dt, + .links-list.dl-horizontal dd { + width: 100%; + } + .links-list.dl-horizontal dd { + margin-left: 0; + } + + /* -------------------------- metadata ----------------------- */ + .metadata-list { + margin-left: 0; + } + .metadata-list.dl-horizontal dt strong { + float: none; + } + .metadata-list.dl-horizontal dt { + margin-left: 0; + } + .metadata-list.dl-horizontal dd { + margin-left: 5%; + } + .dataset-desc { + width:100%; + } + .org-box { + width: 300px; + height: 200px; + } +} diff --git a/ckanext/justicehub_theme/public/dataset_info.css b/ckanext/justicehub_theme/public/dataset_info.css deleted file mode 100644 index 188ae80..0000000 --- a/ckanext/justicehub_theme/public/dataset_info.css +++ /dev/null @@ -1,274 +0,0 @@ -.activity .item::after{ - margin-bottom:40px; -} - - -.activity .item { - font-weight: normal; - font-style: italic; -} -.activity .item .actor a { - color: #f65940; -} -.activity .item a { - font-weight: bold; - font-style: normal; -} -.activity .item .icon { - background-color: #00abb7 !important; - height: 45px; - width: 45px; - display: flex; - justify-content: center; - align-items: center; - font-size: 25px; -} -.activity .item p { - margin-left:50px; - font-size: 16px; -} - -.activity .item .fa{ - margin-top:10px; -} - -.activity .item { - margin-left:15px; -} - -.activity .item a{ - color:white; -} - -.activity { - background:none -} - - -.activity .item .date{ - color:#f65940; - display:block -} - -.actor .gravatar { - display:none -} - -.stats-box { - margin-top: 50px; - margin-bottom: 20px; -} -.stats { - color:#f65940; - font-size:50px; - font-weight:bolder; - display: block; -} -.stats-text { - color:#403644; - font-size:20px; - font-weight:bold; - display: block; -} -.pckg_rsrc_actvty { - background:#403644; -} -.additional-info h3 { - font-size: 25px; -} -.pckg_rsrc_actvty, -.page-section { - font-weight: bolder; - border-top: 2px solid #f65940; - padding-top: 20px; -} -.page-section { - padding-bottom: 20px; - margin-left: 0; -} -.page-section > span:first-child { - font-size: 25px; - display: inline-block; - margin-bottom: 30px; - padding-left: 15px; -} -.page-section .links-list { - padding-left: 15px; - font-size: 20px; - font-weight: normal; - height: 150px; -} -.page-section .links-list a { - color: #403644; -} -.resources { - padding: 10px 35px; -} -.top-section { - margin: 0 20px; - margin-bottom: 100px; -} -.package-desc { - width:75%; - margin-bottom:50px; - font-weight:bold; -} -/* Ref: https://stackoverflow.com/a/36311846/3860168 */ -.image-box { - display:flex; - justify-content:center; - align-items: center; -} -.org-box { - width: 350px; - height: 350px; - margin-top: -30px; -} -.image-box img.media-image { - margin:auto; - width: 100%; - height: 100%; -} -.pkg_tags { - border-radius:30px; - padding: 5px 20px; - display:inline-flex; -} -.pkg_tags a { - color:white; - font-weight:bold; -} - -.pckg_rsrc_actvty { - border-top: 2px solid #f65940; - margin-left: 0; -} -.pckg_rsrc_actvty > .activity-item { - color: #ffffff; - margin-left: 10px; - margin-right: 20px; -} -.pckg_rsrc_actvty h2 { - font-weight:bolder; - font-size: 24px; - line-height: 1.2; - color:white; - text-align:center; - border-bottom: 2px solid #f65940; - padding-top:5px; - padding-bottom:21px; -} -.content_action a { - background-color: #f65940; - border-radius: 30px; - padding: 5px 50px; - color: #ffffff; - font-size: 24px; - margin-top: 10px; -} -.content_action a:hover { - color: #f65940; - background-color: inherit; - border: 1px solid #f65940; -} -.package-tabs { - margin-left: 0; -} -.package-tabs .nav-tabs { - width: 100%; - margin-top: -60px; - border-bottom: 2px solid #f65940; -} -.package-tabs .nav-tabs li a { - border-radius: 0; - font-size: 30px; -} -.pkg_tags:nth-child(even) {background-color:#00abb7;} -.pkg_tags:nth-child(odd) {background-color:#f65940;} - -.additional-info .table { - width: 50%; - margin-left: 25%; - font-size: 16px; -} -.table-striped thead tr th, -.table-striped thead tr td { - padding-top: 10px; - padding-bottom: 10px; - padding-left: 10%; - font-size: 20px; -} -.table-striped tbody tr th, -.table-striped tbody tr td { - padding-top: 12px; - padding-bottom: 12px; -} -.table-striped tbody tr:nth-child(2n) td, -.table-striped tbody tr:nth-child(2n) th { - background-color: #ffffff; -} -.table-striped tbody tr:nth-child(2n+1) td, -.table-striped tbody tr:nth-child(2n+1) th { - background-color: #c1bbc3; -} -.resource-item { - margin-right: 20px; -} -.dropdown-toggle { - border-radius: 30px; -} -.partner-links .social-links .fa:hover { - background-color: #ffffff; - color: #f65940; - border: 1px solid #f65940; -} - - -/*----------------- data set editing page css ----------------------*/ -.edit-pkg-header{ - border-bottom:1px solid #403644; -} - -.nav-tabs > li > a { - color: #403644; - border: None; - font-size: 20px; - font-weight: bolder; -} - -.nav-tabs > li > a > i { - display:None; -} - -.nav-tabs > li.active > a { - background: #403644; - color:#ffffff; - border: None; -} - -.page_primary_action .btn-flat { - border-radius:0px; - margin-right :40px; -} - -/* --------------------- responsive ------------------------- */ -@media only screen and (max-width: 768px){ - .nav-tabs > li { - margin-right:0px; - } - .nav-tabs > li > a { - font-size: 15px; - } - .page_primary_action .btn-flat { - border-radius:0px; - margin-right :0px; - } - .pckg_rsrc_actvty{ - display:block; - } - .additional-info .table { - width: 100%; - margin-left: 0; - font-size: 16px; - } -} diff --git a/ckanext/justicehub_theme/public/form.css b/ckanext/justicehub_theme/public/form.css index 7fb0122..41eed45 100644 --- a/ckanext/justicehub_theme/public/form.css +++ b/ckanext/justicehub_theme/public/form.css @@ -1,162 +1,87 @@ /*----------------- form style ----------------------*/ .search-form, .dataset-form { border-bottom: none; - margin-bottom: 50px; -} -.dataset-form { - padding-right: 50px; + margin-bottom: 20px; } +/* --------------- tag remove icon ----------------- */ .select2-container-multi .select2-search-choice-close { left: auto; margin: 5px 10px 5px 5px; - /* background: url('https://image.flaticon.com/icons/svg/2919/2919559.svg') right top no-repeat;*/ background: url('https://image.flaticon.com/icons/svg/458/458595.svg'); } - -/*------------------ form control borders, backgrounds fonts colors ----------------*/ -.dataset-form .form-actions .btn-primary, -.dataset-form .form-actions .btn-primary:active, -.dataset-form .form-actions .btn-primary:hover, -.dataset-form .form-actions .btn-primary:focus { - color: #f65940; -} -.page_primary_action .btn.btn-default, -.controls > a.btn, -.js .image-upload .btn.hover, -.controls .btn, -.dataset-form .form-actions .btn-finish, -.select2-container-multi .select2-choices > li.select2-search-choice { - color: #ffffff; -} -.dataset-form .form-actions .btn-finish:active, -.dataset-form .form-actions .btn-finish:hover, -.dataset-form .form-actions .btn-finish:focus, -.control-label { - color: #403644; -} -.select2-results .select2-highlighted, -.control-label, -.dataset-form .form-actions .btn-primary, -.dataset-form .form-actions .btn-primary:focus, -.select2-container-multi .select2-choices > li.select2-search-choice:nth-child(2n), -.select2-container-multi .select2-choices > li.select2-search-choice:nth-child(2n+1) { - font-weight: bold; +/* --------------- arrow icon in select dropdown --- */ +.select2-container .select2-choice .select2-arrow { + margin-top: 7px; + margin-right: 10px; + background: transparent; + box-shadow: none; + border-left: none; } -.page_primary_action .btn.btn-default, -.controls > input:first-child, -.input-group > *:first-child, -.editor .editor-info-block, -[class^="input-group"] > *:first-child, -[class^="form-control"], -[class^="form-control"]:focus, -fieldset.checkboxes > label > input, -.select2-drop, -.select2-container .select2-choice, -.select2-container-multi .select2-choices { - border-radius: 0px; +.select2-container .select2-choice .select2-arrow b { + background-size: cover; } -.input-group > *:first-child, + +/*------------------ form control borders, backgrounds ----------------*/ +.square-border, +.form-control, +.form-control:focus, +.controls input, +.controls .form-control, +.input-group-addon, +.editor textarea, .editor .editor-info-block, -[class^="input-group"] > *:first-child, -[class^="form-control"], -[class^="form-control"]:focus, -.dataset-form .form-actions .btn-finish:hover, -.dataset-form .form-actions .btn-finish:focus, -.dataset-form .form-actions .btn-finish:active, .select2-drop, .select2-container .select2-choice, -.select2-container-multi.form-control, -.select2-container-multi .select2-choices { +.select2-container .select2-choices { + border-radius: 0px; border: 1px solid #f65940; } +.btn-xs, +.btn-xs:hover, +.bg-clear.btn, +.bg-clear.btn:active:hover, +.controls .select2-container, +.checkbox-other.select2-container { + border: none; +} +.btn-xs, +.btn-xs:active:hover, +.btn-xs:focus, +.btn-xs:focus, .bg-clear, -.dataset-form .form-actions .btn-primary, -.dataset-form .form-actions .btn-primary:focus, -.dataset-form .form-actions .btn-finish:hover, -.dataset-form .form-actions .btn-finish:active, -.checkbox-other > *:first-child, -fieldset.checkboxes > label > input, -.controls .select2-container .select2-choice .select2-arrow, -.select2-dropdown-open.select2-container > .select2-choices, -.controls .select2-container > a.select2-choice { +.bg-clear.active, +.bg-clear.active:hover, +.bg-clear:active, +.bg-clear:focus, +.bg-clear:hover, +.bg-clear:active:hover, +.bg-clear:active:focus, +.bg-clear[disabled]:hover, +.controls .select2-container > a.select2-choice, +.checkbox-other.select2-container > .select2-choices { background: transparent; box-shadow: none; } -.page_primary_action .btn.btn-default, -.controls > *:first-child, -.controls > a.btn, -.dataset-form .form-actions .btn-danger, -.dataset-form .form-actions .btn-finish, -.dataset-form .form-actions .btn-primary, -.input-group > *:first-child, -.editor .editor-info-block, -fieldset.radios label, -fieldset.checkboxes label, -fieldset.checkboxes + label, -.select2-container-multi .select2-choices > li.select2-search-choice { - font-size: 18px; -} -fieldset.radios label, -fieldset.checkboxes label, -fieldset.checkboxes + label { - font-weight: normal; -} -.page_primary_action .btn.btn-default, -.controls > a.btn, -.controls .btn, -.dataset-form .form-actions .btn-finish, -.select2-container-multi .select2-choices > li.select2-search-choice { - background-color: #403644; -} -.sort-box { - font-size: 25px; -} -.sort-box, -.sort-box:focus, -.search-form .btn, -.controls .select2-container .select2-choice .select2-arrow { - border-left: none; -} .select2-container-active .select2-choices { border-color: #f65940 !important; /* important because it gets overridden by dynamic class change */ } -.controls > a.btn, -.controls .btn, -.dataset-form .form-actions .btn-primary, -.dataset-form .form-actions .btn-danger, -.dataset-form .form-actions .btn-finish, +.checkbox-other.select2-container .select2-choices { + border: none; + border-bottom: 1px solid #f65940; +} + +/* -------------------- tags ------------------- */ .select2-container-multi .select2-choices > li.select2-search-choice { padding: 7px 25px; border-radius: 30px; } -.select2-results .select2-highlighted, .select2-container-multi .select2-choices > li.select2-search-choice:nth-child(2n) { background-color: #f65940 !important; background: none; } .select2-container-multi .select2-choices > li.select2-search-choice:nth-child(2n+1) { background: none; - background-color: #403644 !important; -} -.control-label { - font-size: 21px; -} -#field-giant-search:focus, -.input-group .input-group-addon { - border-right: none; -} -.select2-dropdown-open.select2-container > .select2-choices, -.checkbox-other > *:first-child { - border-top: none; - border-left: none; - border-right: none; -} -.select2-container .select2-choice .select2-arrow { - margin-top: 7px; - margin-right: 10px; -} -.select2-container .select2-choice .select2-arrow b { - background-size: cover; + background-color: #403644; } /*------------------ form control layout ----------------*/ @@ -170,20 +95,32 @@ fieldset.checkboxes + label { .controls .select2-container { height: 50px; } -.search-form .input-group > *, -.search-form .input-group > select { - height: 75px; -} fieldset.checkboxes label { display: inline-block !important; } +.select2-container { + padding: 0; +} .checkbox-other { - width: 200px !important; + width: 250px !important; +} +/* HACK: the logo remove button was floating right */ +.js .image-upload .btn-remove-url { + right: 35px; +} +/* HACK: since we're not updating the composite template */ +.composite-header { + padding-top: 15px; + padding-left: 15px; } /* ----------------- form control spacing ------------- */ -.dataset-form .form-actions { - margin-top: 50px; +.form-actions { + margin-top: 20px; +} +.controls, +.form-actions { + padding-right: 30px; } .controls .select2-container.select2-container-multi .select2-choices { padding-top: 10px; @@ -199,10 +136,6 @@ fieldset.checkboxes label { padding-top: 3px; padding-bottom: 3px; } -.dataset-form .form-actions .btn-primary, -.controls > .select2-container .select2-choices { - border: none; -} .controls .select2-container .select2-choices { padding-left: 0; } @@ -213,35 +146,19 @@ fieldset.checkboxes label { padding-left: 30px; margin-bottom: 0; } -.search-form .input-group .btn, -.dataset-form .input-group .btn { - padding-top: 26px; - padding-bottom: 23px; -} -.select2-container { - padding: 0; -} .select2-container .select2-choice, .select2-container-multi .select2-choices { - padding: 0 0 0 10px; + padding: 0 0 0 12px; } fieldset.radios label > input { margin-right: 5px; - background: white; } fieldset.radios label, fieldset.checkboxes label { padding-right: 40px; } -.checkbox-other > *:first-child { - margin-left: 10px; -} .select2-search-choice > div { margin-right: 15px; - padding-left: 5px; -} -.controls .checkbox-other .select2-choices { - padding-top :0; } input[type="date"].form-control, input[type="month"].form-control { @@ -249,45 +166,18 @@ input[type="month"].form-control { } /* ----------------------------- login form ------------------------------ */ -.btn-register:focus, -.btn-register:active, -.btn-register:hover, -.btn-register { - font-size: 30px; - font-weight: bold; - color: white !important; - border: none; - background-color: #f65940 !important; - box-shadow: none; - width: 100%; -} #user-register-form, .login-form { padding-left: 20px; padding-right: 50px; padding-bottom: 20px; } -.btn-forgot, -.btn-forgot:hover { - background-color: #ffffff; - border: none; - color: #f65940; - font-size: 18px; - padding-right: 0; -} -.login-form .form-actions .btn-primary { - background-color: #403644; - border-radius: 30px; - color: #ffffff; - font-size: 23px; - padding: 6px 40px; - float: right; -} -.checkbox-inline { - padding: 10px 30px; -} -@media (max-device-width: 600px) and (orientation: portrait) { +/* ----------------------------- responsive ---------------------------- */ +@media (min-width: 320px) and (orientation: portrait) { + .search-form { + margin-bottom: 0; + } .form-group { padding-left: 10px; } diff --git a/ckanext/justicehub_theme/public/images/JH Logomark-favicon.png b/ckanext/justicehub_theme/public/images/JH Logomark-favicon.png new file mode 100644 index 0000000..d1cb05c Binary files /dev/null and b/ckanext/justicehub_theme/public/images/JH Logomark-favicon.png differ diff --git a/ckanext/justicehub_theme/public/images/agami_logo.png b/ckanext/justicehub_theme/public/images/agami_logo.png new file mode 100644 index 0000000..0c979a8 Binary files /dev/null and b/ckanext/justicehub_theme/public/images/agami_logo.png differ diff --git a/ckanext/justicehub_theme/public/images/cdl_logo.png b/ckanext/justicehub_theme/public/images/cdl_logo.png new file mode 100644 index 0000000..f30cb93 Binary files /dev/null and b/ckanext/justicehub_theme/public/images/cdl_logo.png differ diff --git a/ckanext/justicehub_theme/public/images/signed_pledge_icon.png b/ckanext/justicehub_theme/public/images/signed_pledge_icon.png new file mode 100644 index 0000000..2da36a2 Binary files /dev/null and b/ckanext/justicehub_theme/public/images/signed_pledge_icon.png differ diff --git a/ckanext/justicehub_theme/public/images/verified_icon.png b/ckanext/justicehub_theme/public/images/verified_icon.png new file mode 100644 index 0000000..ab6c81a Binary files /dev/null and b/ckanext/justicehub_theme/public/images/verified_icon.png differ diff --git a/ckanext/justicehub_theme/public/issues.css b/ckanext/justicehub_theme/public/issues.css index 1393d90..831901d 100644 --- a/ckanext/justicehub_theme/public/issues.css +++ b/ckanext/justicehub_theme/public/issues.css @@ -1,12 +1,15 @@ -.issues-home .search-form { - margin-right: -15px; -} #issues-found, .issue-list-group { - padding-left: 30px; + padding-left: 10px; margin-left: 0; margin-top: 0; } +.issues-home .gravatar { + border-radius: 50%; +} +.issues-home .input-group { + border-bottom: 1px solid #f65940; +} .issues-home .input-group .btn { border-left: none; } @@ -26,7 +29,6 @@ .issue-comment-header { background-color: #f65940 !important; color: #ffffff !important; - font-size: 18px; } .issue-comment-action [class^="subtle-btn"] { border: none; @@ -34,7 +36,6 @@ background: transparent; } .issue-comment-action [class^="subtle-btn"] i { - font-size: 24px; color: #ffffff; } .issues-sidebar-heading { @@ -58,7 +59,6 @@ font-weight: bold; } .issue-new .form-actions .btn { - font-size: 20px; color: #f65940; } .issue-new .dataset-form { @@ -67,10 +67,6 @@ .issue-list-group .list-group-item { box-shadow: 0 0 10px 1px #bcb4bf; } -.issue-list-group .list-group-item-name, -.issue-list-group .list-group-item-number { - font-size: 18px; -} .open.icon { padding-top: 5px; } diff --git a/ckanext/justicehub_theme/public/justicehub_theme.css b/ckanext/justicehub_theme/public/justicehub_theme.css index b0fd091..3623334 100644 --- a/ckanext/justicehub_theme/public/justicehub_theme.css +++ b/ckanext/justicehub_theme/public/justicehub_theme.css @@ -1,28 +1,30 @@ -.account-masthead { - background-color: rgb(40, 40, 40); - display: none; -} -.account-masthead .account .notifications a span { - background-color: black; +/* ----------------------- images --------------------------- */ +/* Ref: https://stackoverflow.com/a/36311846/3860168 */ +.image-box { + display:flex; + justify-content:center; + align-items: center; +} +.image-box img.media-image { + margin:auto; + max-width: 100%; + max-height: 100%; } -.account-masthead .account ul li a { - color: rgba(255, 255, 255, 0.6); +.context-info .image a { + width: 150px; + height: 150px; + margin-left: -50px; } -.account-masthead .account ul li a:hover { - color: rgba(255, 255, 255, 0.7); - background-color: black; +.context-info .image img { + border-radius: 50%; } - -.container { - width: 92%; -} -[role="main"] .container { - width: 100%; +/* ------------------------ header ------------------------ */ +/* Hide top account bar */ +.account-masthead { + display: none; } .masthead { - background: none; - background-color: #403644; padding-top: 0; padding-bottom: 0; } @@ -30,69 +32,74 @@ padding-top: 0; padding-bottom: 0; } -.masthead .navigation .nav-pills li { - font-size: 25px; - font-weight: bold; - padding-top: 40px; - height: 140px; +.masthead .navigation .nav-pills > li a { + padding: 10px 15px; } -.masthead .navigation .nav-pills li.active { - border-bottom: 15px solid #f65940; +/* .masthead .navigation .nav-pills > li a { + line-height: 1; } -.masthead .navigation .nav-pills li:hover { - border-bottom: 15px solid #f65940; +.masthead .navigation .nav-pills > li { + padding-top: 5%; + min-height: 100px; +}*/ +.masthead .navigation .nav-pills > li { + min-height: 100px; + display: flex; + align-items: center; } -.masthead .navigation .nav-pills li a:hover { - background-color: #403644; +.masthead .navigation .nav-pills > li:last-child { + min-width: 120px; } -.masthead .navigation .nav-pills li.active a { - background-color: #403644; +.masthead .navigation .nav-pills > li.active { + border-bottom: 1.5vh solid #f65940; + padding-top: 14px; } -.masthead .navigation .nav-pills li:last-child { - height: 140px; - background-color: #f65940; - color: #ffffff; - font-size: 25px; - padding-left: 5px; - padding-right: 10px; +.navigation .open, +.masthead .navigation .nav-pills > li:hover { + border-bottom: 1.5vh solid #f65940; + padding-top: 14px; +} +.navigation .dropdown .dropdown-menu { + padding: 0; + width: 100%; + min-width: 50px; + border-radius: 0; + border: none; } -.masthead .navigation .nav-pills li:last-child a:hover { - color: #ffffff; +.header-image { + width: 28%; } - .masthead .logo { - margin-top: 10px; - margin-bottom: 10px; - margin-left: -45px; - width: 490px; - height: 120px; + min-height: 100px; + display: flex; + align-items: center; } -.logo img { - min-width: 100%; - max-width: 100%; - min-height: 100%; +.navbar-toggle { + font-size: 8vw; + margin: 0; +} +.masthead .logo img { object-fit: cover; + max-height: 40px; } -.toolbar { - padding: 20px 20px; +/* -------------------- body layout ----------------------- */ +.container { + width: 95%; +} +[role="main"] .container { + width: 100%; } .box { box-shadow: none; border: none; } -.module > .module-content:nth-child(2), -.module-content { +.module-content.page-header { padding: 0; } -.module-content:last-child { - padding-bottom: 0px; -} -.homepage [role="main"] { - background: none; - background-color: #ffffff; +.module-content { + padding-right: 5px; } - .wrapper { box-shadow: none; min-height: 600px; @@ -103,406 +110,448 @@ border-right: none; } -[role="main"], body { - font-family: 'Montserrat'; - background: none; - background-color: #ffffff; +.toolbar { + padding: 20px 20px; } -.site-footer { - background: none; - background-color: #403644; + +/* ----------------------- dashboard ----------------- */ +.popover-followee { + border-radius: 0; } -/* The text in the footer. */ -.site-footer, -.site-footer label, -.site-footer small { - color: rgba(255, 255, 255, 0.6); +/* ----------------------- pre-primary-blocks ----------------- */ +.morecontent span { + display: none; } -/* The link texts in the footer. */ -.site-footer a { - color: rgba(255, 255, 255, 0.6); +a.morelink { + text-decoration:none; + outline: none; } - -.site-footer { - color: #ffffff; +a.morelink:visited { + color: #f65940; } - -.footer-links { - padding: 20px 10px; +.top-section { + margin-left: 40px; + margin-bottom: 200px; } -.footer-links .col-md-1 { - padding: 20px 0; +.top-section > .row:first-child { + margin-bottom: 3%; } - - -/* https://www.w3schools.com/howto/howto_css_social_media_buttons.asp */ -.social-links a { - text-decoration: none; -} -.social-links .fa { - padding: 12px 5px; - font-size: 45px; - width: 75px; - height: 75px; - text-decoration: none; - border-radius: 50%; - background-color: #f65940; - color: #ffffff; - display: flex; - justify-content: center; - align-items: center; +.top-section > .row:nth-child(2) { + margin-bottom: 1%; } - -.social-links li { - margin-right: 18px; +.context .content_action, +.nav-tabs .content_action { + margin-right: 30px; } -.social-links li:first-of-type { - padding-left: 0; +[class^="btn btn-"], +.nav-tabs .content_action a { + border-color: #403644; } -.social-links .fa:hover { - background-color: #403644; - border: 1px solid #ffffff; - color: #ffffff; +.res-actions a, +.content_action a { + border-color: #f65940; } -.footer-links .nav-item > a:hover, -.footer-links .nav-item > a { - margin: 0; - padding: 0; +[class^="btn btn-"], +.res-actions a, +.content_action a { + border-radius: 30px; + padding: 5px 25px; color: #ffffff; - font-size: 16px; } - -.footer-disclaimer { - font-size: 25px; +.content_action a { + margin-top: 10px; } -.footer-disclaimer div:last-child { +.js .image-upload .btn.hover, +[class^="btn btn-"]:hover, +[class^="btn btn-"]:active, +[class^="btn btn-"]:focus, +.open > .dropdown-toggle.btn, +.bg-clear:active:hover, +.res-actions a:hover, +.content_action a:hover { color: #f65940; - font-size: 30px; - font-style: italic; + border: 1px solid #f65940; + background-color: #ffffff; + text-decoration:none; } -.footer-links i.fa-circle.fa-sm { - font-size: 9px; - color: #f65940; - padding: 10px; + +/* ---------------------- primary blocks ---------------------- */ +.page-heading { + border-bottom: 2px solid #f65940; + padding-top: 6px; + padding-left: 30px; + padding-bottom: 20px; + margin-bottom: 10px !important; + margin-left: -30px; } -.footer-links .input-group-btn .btn, -.footer-links .input-group input { - border-radius: 30px; +h2.page-heading { + padding-bottom: 50px; + padding-left: 20px; } -.footer-links .input-group .form-control:focus { - border-color: #f65940; - z-index: 0; +.module .page-header { + margin: 0; + padding-bottom: 0; + background: transparent; + border: none; } -.footer-links input { - font-size: 20px !important; - font-style: italic; - color: #b9b4b4; - padding: 23px 15px; +.page-header .nav-tabs { + width: 100%; + margin-top: -55px; } -.footer-links .input-group-btn .btn { - margin-left: -20px; - background-color: #f65940; - font-size: 20px; - border-color: #f65940; - text-align: center; +.page-header .nav-tabs li a { + border-radius: 0; + padding-left: 30px; +} +.page-header .nav-tabs li a:hover { + border-bottom: 1.5px solid #f65940; +} +.page-header .nav-tabs::before, +.page-header .nav-tabs::after { + border: none; +} +.page-header .nav-tabs > li { + margin-bottom: -2px; +} +.nav-tabs > li > a > i { + display: None; +} +.js .tab-content { + display: block; } -/* ------------- sidebar --------- */ +/* ------------------------- sidebar ------------------------ */ .context-info .module-content { padding: 0; } -.module-narrow h1.heading, -.module-narrow h2.module-heading { - font-size: 22px; - background-color: #f65940; - height: 100px; - padding-left: 25px; - padding-top: 30px; - border-bottom: none; -} .context-info .nums { border: none; } .module-footer { border: none; - background-color: #403644; - color: #ffffff; } -.module-heading { - color: #ffffff; - font-weight: bold; - font-size: 1.5em; - background-color: #403644; - border-color: #f87c69; - margin-bottom: 0 !important; - border-top: none; +.sidebar { + border-top: 2px solid #f65940; } - -.primary { +.module-shallow .module-content p { + padding: 20px 20px; padding-left: 0; } - -h1 { - font-size: 30px; +.module-shallow .module-content:first-child, +.module-shallow .module-content:last-child { + padding-top: 0; + padding-bottom: 0; } -hr { - margin-top: 20px; - border-top: 1.5px solid #403644; +.sidebar h2 { + line-height: 1.2; + text-align:center; + border-bottom: 2px solid #f65940; + padding-top:5px; + padding-bottom:21px; + margin-bottom: 0; +} + +.primary { + padding-left: 0; + padding-right: 0; } .page_primary_action { padding-right: 40px; + margin-bottom: 10px; height: 40px; + margin-top: 10px; } .page_primary_action .btn-primary { - background-color: #403644; - color: #ffffff; border-radius: 30px; padding: 5px 25px; - font-size: 20px; } [role="main"] .secondary { padding-right: 0; - /* min-height: 700px; */ - background-color: #403644; } aside .module-content { - color: #ffffff; - background-color: #403644; + padding-left: 0; } aside .module [class^=module] { - padding: 30px; - padding-bottom: 10px; + padding-left: 40px; + padding-top: 20px; + padding-bottom: 20px; +} +.social-links.col-sm-7 { + width: 55%; +} + + +/* ------------------------ footer ------------------------ */ +.footer-links .image-box { + margin-top: -20px; + height: 70px; + width: 30%; + display: inline-block; +} +.footer-links .agami-logo { + width: 162px; + height: 44px; +} +.footer-links .cdl-logo { + width: 204.07px; + height: 70px; +} +.site-footer { + padding: 0 30px; +} +.footer-links { + padding: 50px 0; +} +.footer-links > .row { + margin-top: 15px; +} +.footer-links .nav-item { + display: inline; +} +.footer-links .nav-item a { + text-decoration: none; +} +.footer-links .list-inline > li { + padding-left: 0.1em; + padding-right: 0.2em; +} +.footer-disclaimer { + margin-bottom: 10px; +} +.footer-links i.fa-circle.fa-sm { + font-size: 9px; + color: #f65940; + padding: 0; +} +.subscribe-box { + padding-right: 60px; + padding-left: 50px; +} +.subscribe-box .btn.btn-default { + padding: 10px 25px; +} +.site-footer .popover { + border-radius: 0; + box-shadow: 5px 5px 1px #403644; + max-width: 400px; + width: 350px; +} +.site-footer .popover .popover-content { + display: flex; + align-items: center; +} +.popover > .arrow { + display: none; } /* ------------------------------- Login ------------------------- */ .login-sidebar { - background-color: #403644; margin-top: -20px; height: 700px; } .login-sidebar-heading { - color: #ffffff; - font-size: 28px; - padding: 50px 60px; - padding-bottom: 10px; + padding-top: 20px; + padding-bottom: 20px; + text-align: center; } .login-sidebar p { - padding: 15px 5px; - font-size: 16px; -} -.login-sidebar p.action { - background-color: #f65940; + display: flex; + justify-content: center; + align-items: center; } .login-heading { border-bottom: 1px solid #f65940; - padding: 0 50px; + padding: 0 80px; margin-bottom: -1.5px; } - -/* ----------------------------- homepage --------------------------- */ -[role="main"] .container .row .col1 { - padding: 10% 4%; -} -[role="main"] .container .row .col2 { - padding: 13% 10%; - font-size: 25px; - font-style: italic; - color: #707070; -} -[role="main"] .container .row .col3 { - background-color: #403644; - color: #ffffff; - padding: 40px 60px; +.btn-forgot { + margin-top: 5px; } -[role="main"] .container .row .col3 h2{ - font-size: 40px; - border-bottom: 2px solid #f65940; - padding-bottom: 10px; - margin-bottom: 30px; +.alert-danger a { + font-weight: 600; + color: #f65940; + text-decoration: none; } -[role="main"] .container .row .col3 p { - padding: 10px 0; - font-size: 25px; + +/* ----------------------------- homepage --------------------------- */ +.intro-box span:first-child { + display: inline-block; + width: 400px; } -.col-circle { - position: relative; +.intro-box span:nth-child(2) { + display: inline-block; + width: 500px; } -.col-circle i.fa { - font-size: 350px; - color: #f65940; +.search-meta .page_primary_action { + margin-bottom: 5%; + padding-left: 20px; } -.homepage .module-search { - min-height: 860px; - background: url('/uploads/admin/JH Logomark.png') no-repeat; - background-clip: border-box; - background-position: -390px -320px; - background-size: 75%; - padding-top: 320px; - padding-left: 200px; -} -.search-form .search-input.search-giant .form-control { - border-radius: 60px; - line-height: 2; - font-size: 32px; - font-style: italic; - border: 5px solid #f65940; - padding-left: 30px; - height: 100px; +.landing-desc { + padding: 40px 40px; } -.homepage .module-search .form-group { - padding: 10px 0; +.landing-desc .row:first-child { + margin-bottom: 40px; } -.homepage .module-search .module-content, -.homepage .search-form .search-giant, -.homepage .module-search .stats-box { - background: transparent; - color: #403644; +.landing-desc .row:nth-child(2) { + margin-bottom: 3%; } -.search-form .search-input.search-giant .fa { - color: #403644; - margin-right: 20px; +.landing-desc .nav-item > a { + margin: 0; + padding: 0; } -.homepage .module-search .stats-box .tag { +.landing-desc .social-links .fa { background-color: #f65940; color: #ffffff; - border: 1px solid #f65940; - box-shadow: none; } -.homepage .module-search .stats-box .tag:hover { - background-color: inherit; - color: #f65940; +.landing-desc .social-links .fa:hover { + border-color: #ffffff; + color: #ffffff; } -.search-form .search-input.search-giant { - width: 88%; +.landing-quotes-top { + position: relative; + top: 35px; + left: 5%; + z-index: 1; } -.module-search .search-form { - padding: 0; +.landing-quotes-bottom { + position: relative; + top: -40px; + right: -60%; +} +.homepage .module-search { + min-height: 30vh; + margin-top: 5%; + background: transparent; +} +.homepage .module-search .module-content { + background: transparent; } .search-meta { - width: 95%; + width: 80%; + margin: auto; } -.search-meta .stats { - color:#f65940; - font-size: 80px; - font-weight:bolder; - display: block; +.search-meta .stats-box { + margin-top: 3%; } -.stats-text { - margin-top: -20px; - color:#403644; - font-size:20px; - font-weight:bold; - display: block; +.search-meta .stats-box a:hover { + text-decoration: none; } /* -------------------------------------- Mobile adjustments ----------------------------- */ -@media (max-device-width: 600px) and (orientation: portrait) { - .toolbar .breadcrumb, - .toolbar .breadcrumb a { - color: #403644; +@media (min-width:320px) and (orientation: portrait) { + .module-content { + padding: 10px; } - .footer-links:last-of-type div:last-of-type ul { - float: left !important; + .top-section { + margin: 0 20px; + padding: 5px; } - .masthead { - margin-bottom: initial; - padding: 10px 0; + .page_primary_action { + padding-right: 30px; } - .masthead .logo { + .context .content_action { + margin-right: 20px; + } + .nav-tabs .content_action { + margin-right: 10px; + } + .hug.page-header .nav-tabs { margin-top: 0; - margin-bottom: 0; - height: 80px; - width: 390px; } - .navbar-toggle { - font-size: 40px; - margin: 0; + .page-header .nav-tabs { + margin-top: 50px; } - .masthead .navbar-collapse { - padding: 10px 0; + .page-heading { + padding-left: 45px; } - .masthead .navigation .nav-pills li { - height: auto; - font-size: 18px; - font-weight: bold; - padding-top: 5px; - padding-bottom: 5px; + h2.page-heading { + padding-left: 35px; + } + .toolbar .breadcrumb, + .toolbar .breadcrumb a { + color: #403644; } - .masthead .navigation .nav-pills li.active { - border-bottom: 5px solid #f65940; + .header-image { + width: 50%; } - .masthead .navigation .nav-pills li:hover { - border-bottom: 5px solid #f65940; + .masthead .logo { + min-height: 60px; } - .masthead .navigation .nav-pills li:last-child { + .masthead .navigation .nav-pills > li { height: auto; - background-color: #f65940; - color: #ffffff; - padding-top: 5px; - font-size: 18px; - border-bottom: none; + min-height: 40px; + display: block; } - .masthead .navigation .nav-pills li:last-child a:hover { - color: #ffffff; + .masthead .navigation .nav-pills > li.active { + border-bottom: 3px solid #f65940; + padding-top: 0; } - .footer-links { - padding: 5px 10px; + .navigation .open { + border-bottom: 3px solid #f65940; + padding-top: 0; } - .footer-links { - padding-bottom: 20px; + .navigation .dropdown:last-child .dropdown-menu { + position: static; } .login-heading { - padding-left: 20px; + padding-left: 65px; margin-bottom: 20px; margin-left: -10px; } .login-sidebar { height: 100%; } - .search-form .search-input.search-giant .form-control { - line-height: 1; - height: 65px; - border: 3px solid #f65940; - } - .homepage .module-search { - background-clip: border-box; - background-position: -155px -95px; - background-size: 120%; - padding-top: 160px; - padding-left: 70px; - min-height: 500px; - border-bottom: 2px solid #f65940; - margin-bottom: 50px; - } - .search-form .search-input.search-giant { + .btn-forgot { + margin-top: 15px; + } + .wrapper { + min-height: 400px; + } + .container { width: 100%; - margin-bottom: 80px; } - .search-meta { - width: 120%; - margin-left: -80px; + .intro-box span:first-child { + width: 100%; } - .search-meta .stats { - font-size:50px; + .search-meta { + width: 90%; } .search-meta .stats-text { margin-top: 0; - font-size: 16px; - } - .col-circle i.fa { - font-size: 100px; - color: #f65940; - text-align: center; } - [role="main"] .container .row .col2 { - padding: 2% 0; - margin-bottom: 30px; + .landing-quotes-bottom { + position: relative; + top: -35px; + right: -80%; } [role="main"] .secondary { display: none; } - .module .module-content { + .footer-links .col { padding: 0; } + .footer-links { + padding: 5px 5px; + } + .subscribe-box { + background-color: #403644; + } + .footer-links i.fa-circle.fa-sm { + padding: 5px; + } + .footer-links > .row { + margin-bottom: 10px; + } + .landing-desc .social-links .fa, + .footer-links .social-links .fa { + width: 7.5vw; + height: 28px; + font-size: 5vw; + } + .footer-links .social-links li { + margin-right: 0; + } + .social-links.col-xs-12 { + width: 100%; + } } diff --git a/ckanext/justicehub_theme/public/organization.css b/ckanext/justicehub_theme/public/organization.css deleted file mode 100644 index 6c2937e..0000000 --- a/ckanext/justicehub_theme/public/organization.css +++ /dev/null @@ -1,215 +0,0 @@ -/* ---------- package-item ------- */ - -.label { - padding: 6px 21px; - border-radius: 15px; - font-weight: bold; -} -.dataset-heading { - margin-bottom: 0; -} -.dataset-item { - border-bottom: none; -} -.dataset-org, .downloads, .dataset-heading, .dataset-meta { - font-size: 18px; - font-weight: bold; -} -.dataset-org > a{ - color: #f65940; -} -.downloads, .dataset-meta { - color: #bcb4bf; -} -.heading { - padding-bottom: 20px; -} -.org-row { - padding-left:50px; -} -.org-row .private-icon -{ - padding: 0; - background: none; -} -.org-row .private-icon i { - color: #403644; -} -.partner-logo a { - height: 200px; -} -.follow_button a { - background-color: #f65940; - border-radius: 30px; - padding: 5px 50px; - color: #ffffff; - font-size: 24px; - margin-top: 10px; - border: 0; -} -.follow_button a:hover { - color: #f65940; - background-color: inherit; - border: 1px solid #f65940; -} - -.header-tabs { - margin-left: 0; -} -.header-tabs .nav-tabs { - width: 100%; - margin-top: -60px; - border-bottom: 2px solid #f65940; -} -.header-tabs .nav-tabs li a { - border-radius: 0; - font-size: 30px; -} - -/* ----------- stages ----------- */ - -/* ------------ background borders fonts ------ */ -.page-header .content_action { - color: #403644; -} -.page-header, -.page-header .nav { - background: transparent; - border: none; -} -.page-header .nav-tabs li.active a, -.page-header .nav-tabs li.active a:hover, -.stages li, -.stages li.active .highlight, -.metadata-heading, -aside .module-content { - background-color: #403644; -} -.page-header .nav-tabs li.active a:hover, -.stages, -.stages li.active .highlight, -.module-heading, -.metadata-heading, -aside .module-content { - color: #ffffff; -} - -.module-heading, -.metadata-heading, -.stages li .highlight { - font-weight: bold; -} -.metadata-heading, -.stages li .highlight { - font-size: 19px; -} -.page-header .nav, -.page-header .nav li a:hover, -.stages { - border: none; - border-bottom: 1px solid #403644; -} -.nav-tabs, -.stages { - margin: 50px -65px 0 0; -} -.stages li { - padding: 0; -} -.page-header .nav-tabs li a, -.stages li .highlight { - position: static; - text-align: center; -} -.stages li.complete .highlight, -.stages li.uncomplete .highlight { - background-color: #ffffff; - color: #403644; -} -.stages li:before { - content: none; -} -.stages li:after { - top: 0; - border:none; - margin: 0; -} - -/* --------------- layout sizing ------------- */ -.metadata-heading { - padding: 10px 0; - margin-left: -30px; - width: 55%; - display: inline-block; - text-align: center; -} - -@media (max-device-width: 600px) and (orientation: portrait) { - .metadata-heading { - margin-left: 0; - } -} - -.wrapper::before { - top: auto; -} - - -/* members table */ - -.members-table { - margin-top: 30px; -} - -.members-table > thead > tr > th{ - text-align: left; - background-color: white; - border: 0; - border-bottom: 2px solid #f65940; - padding: 25px; - font-size: 25px; - font-weight: bold; -} - -.members-table > tbody > tr > td{ - padding: 35px; - font-size: 20px; - color: black; - font-weight: bold; -} - -.member_media > img { - width: 50px; - height: 50px; -} - -.member_media > a { - color: black; - padding-left: 10px; - -} - -.member_action .btn-primary { - background-color: #403644; - color: #ffffff; - border-radius: 30px; - padding: 5px 25px; - font-size: 20px; -} - -.member_action .delete { - background-color: red; - color: #ffffff; - border-radius: 25px; - padding: 5px 10px; - font-size: 20px; - margin-left: 5px; -} - -.add_member { - background-color: #403644; - color: #ffffff; - border-radius: 30px; - padding: 5px 25px; - font-size: 20px; -} \ No newline at end of file diff --git a/ckanext/justicehub_theme/public/package.css b/ckanext/justicehub_theme/public/package.css deleted file mode 100644 index 5ff1241..0000000 --- a/ckanext/justicehub_theme/public/package.css +++ /dev/null @@ -1,132 +0,0 @@ -/* ---------- package-item ------- */ - -.dataset-list { - padding-top: 20px; -} -.label { - padding: 5px 20px; - border-radius: 25px; - font-weight: bold; - font-size: 18px; -} -.dataset-heading { - margin-bottom: 0; - font-weight: bold; -} -.dataset-item { - border-bottom: none; -} -.dataset-desc { - font-size: 20px; -} -.downloads, .dataset-meta { - font-style: italic; -} -.dataset-org, .downloads, .dataset-heading, .dataset-meta { - font-size: 25px; -} -.dataset-org > a{ - color: #f65940; -} -.downloads, .dataset-meta { - color: #bcb4bf; -} -.heading { - padding-bottom: 20px; -} -.package-item-row { - padding-left:50px; -} -.package-item-row .private-icon -{ - padding-left: 5px; - background: none; - font-size: 15px; -} -.package-item-row .private-icon i { - color: #403644; -} - -/* ----------- stages ----------- */ - -/* ------------ background borders fonts ------ */ -.page-header .content_action { - color: #403644; -} -.page-header, -.page-header .nav { - background: transparent; - border: none; -} -.page-header .nav-tabs li.active a, -.page-header .nav-tabs li.active a:hover, -.stages li, -.stages li.active .highlight, -.metadata-heading { - background-color: #403644; -} -.page-header .nav-tabs li.active a:hover, -.stages, -.stages li.active .highlight, -.module-heading, -.metadata-heading { - color: #ffffff; -} - -.module-heading, -.metadata-heading, -.stages li .highlight { - font-weight: bold; -} -.metadata-heading, -.stages li .highlight { - font-size: 19px; -} -.page-header .nav, -.page-header .nav li a:hover, -.stages { - border: none; - border-bottom: 1px solid #403644; -} -.page-header .nav-tabs, -.stages { - margin: 50px -65px 0 0; -} -.stages li { - padding: 0; -} -.page-header .nav-tabs li a, -.stages li .highlight { - position: static; - text-align: center; - text-indent: 0; - padding: 10px 10px; -} -.stages li.complete .highlight, -.stages li.uncomplete .highlight { - background-color: #ffffff; - color: #403644; -} -.stages li:before { - content: none; -} -.stages li:after { - top: 0; - border:none; - margin: 0; -} - -/* --------------- layout sizing ------------- */ -.metadata-heading { - padding: 10px 0; - margin-left: -30px; - width: 55%; - display: inline-block; - text-align: center; -} - -@media (max-device-width: 600px) and (orientation: portrait) { - .metadata-heading { - margin-left: 0; - } -} diff --git a/ckanext/justicehub_theme/public/partner.css b/ckanext/justicehub_theme/public/partner.css index a7e9adf..d8f0e77 100644 --- a/ckanext/justicehub_theme/public/partner.css +++ b/ckanext/justicehub_theme/public/partner.css @@ -1,76 +1,96 @@ -.media-item { - color:#f65940; - margin:20px; - height:200px; -} -.count { - color:#f65940; -} -.media-item .media-image { - height: 130px; - width: 150px; +/* -------------------------- org-item ----------------------- */ +.media-grid { + margin-top: 10px; + border: none; } .media-item { - height: 210px; + width:210px; + margin-left: 24px; } - - -/*----------------- partner editing data set page css ----------------------*/ -.partner-datset-content{ - margin-left:15px; +.count, +.media-item .media-heading { + line-height: 1; + margin-bottom: 0; } - -.partner-dataset-table > tbody > tr > td{ - border-top:None; - height:150px; +.media-logo { + height: 150px; } -.partner-dataset-table > tbody > tr > td a{ - font-size:25px; +/* ------------------------- org-detail ---------------------- */ +.partner-links li { + margin-right: 0; } - -.partner-dataset-table > thead > tr > th{ - border-bottom:None; - height:150px; - vertical-align:middle; +.partner-links.social-links .fa { + width: 31px; + height: 31px; } - -.dataset-notes{ - font-size:18px; +.partner-links.social-links .fa-twitter { + padding-left: 6px; } - -.context .content_action a { - padding-right:25px; - padding-left:25px; - font-size:16px; - font-weight:bolder; +.partner-links.social-links .fa-facebook { + padding-top: 13px; + padding-left: 4px; } - -.btn-icon{ - background: white; - border:None; - font-size:15px; +.partner-logo { + height: 200px; } - -.action-fa{ - font-size:40px; +.follow_button a { + margin-top: 10px; } -.btn-icon:hover{ - background: white; - border:None; +/* ----------------------- members --------------------------- */ +.members-table > tbody > tr > td { + border-top: None; + padding-top: 35px; + vertical-align: middle; +} +.member_media > img { + width: 60px; + height: 60px; + border-radius: 50%; +} +.member_media > a { + padding-left: 10px; + +} +.member_action .delete { + border-radius: 25px; + padding: 3px 6px; + margin-left: 5px; + font-size: 16px; } -.page_primary_action { - padding-right:0px; - margin-bottom: 0; +/*----------------- org-edit dataset ----------------------*/ +.btn-icon { + border:None; + padding: 0; } -.page_primary_action .search-form { - margin-bottom: 10px; +.table thead tr th.table-actions { + padding-left: 15px; } - -.private-icon{ - font-size:25px; - margin-left:10px; +.btn-icon:hover{ + background: #ffffff; +} +.partner-dataset-table td.context { + padding-left: 20px; +} +/* -------------------------------------- Mobile adjustments ----------------------------- */ +@media (min-width:320px) and (orientation: portrait) { + .media-item { + width: 40%; + } + .media-logo { + height: 80px; + } + .img-responsive { + max-width: 75%; + } + .member_media > img { + width: auto; + height: auto; + } + .members-table > thead > tr > th, + .members-table > tbody > tr > td { + padding: 10px; + } } - diff --git a/ckanext/justicehub_theme/public/resource.css b/ckanext/justicehub_theme/public/resource.css index c2ccc7c..4da7f13 100644 --- a/ckanext/justicehub_theme/public/resource.css +++ b/ckanext/justicehub_theme/public/resource.css @@ -1,175 +1,38 @@ -.btn-action{ - background-color: #f65940; - border-radius: 30px; - padding: 5px 30px; - color: #ffffff; - font-size: 15px; - margin-top: 10px; - border: none; -} - -.btn-action:hover { - background-color: #ffffff; - border: 1px solid #f65940; - color: #f65940; -} - -.resource-desc{ - font-size:20px; -} - -.res-line-margin-bottom{ - margin-bottom:20px; -} - -.res-title{ - font-size:20px; - color:#f65940; - font-weight:bolder; -} - -.res-url-src{ - font-size:17px; - color : #403644; -} - -.module-content .nav-tabs { - margin: 0px; - margin-top: 50px; - border-top: 2px solid #f65940; - border-bottom: 2px solid #f65940; - margin-left:-35px; -} - -.res-top-content{ - padding-left:35px; -} - -.module-content .nav-tabs-plain { - padding: 0px; -} - -.module-content .nav { - background-color: #ffffff; - color: #403644; -} - -.module-content .nav-tabs > li.active > a { - background: white; - color: #403644; - border-bottom:0.1px solid #f65940; -} - -.rsrc-list-side > li { - padding-left:15px; - border: none; +.module-resource h1 { + margin-bottom: 0; } - - -.rsrc-list-side .rsrc-item-side a{ - color:#ffffff; -} - -.rsrc-list-side .rsrc-item-side:hover { - background:#eeeeee; - color:#403644; -} - -.rsrc-list-side .rsrc-item-side a:hover{ - color:#403644; -} - -.rsrc-list-side{ - height:100vh; -} - -.context-info h1.rsrc-info-heading{ - background:#f65940; - padding-top:40px; - padding-bottom:40px; - padding-left:15px; -} - -.module .rsrc-list-side .rsrc-item-side a{ - margin-top:5px; - margin-left:0px; - font-size:18px; - font-weight:bolder; - padding-left:0px; -} - - -.rsrc-item-desc-side{ - color: #f65940; - font-size:15px; - border-bottom: 1px dotted #dddddd; - padding-left:15px; -} - -.res-social{ - position:absolute; - bottom:17px; - right:0; -} - -@media only screen and (max-width: 768px){ - .res-social{ - position:unset; - } - -} - - -.content_action .rsrc-edit-actn-btn{ - font-size:15px; - padding: 5px 20px; -} - - -.module-narrow h2.social-heading { - font-size: 20px; - background-color: #ffffff; +.social-heading { + background-color: #ffffff !important; color: #f65940; height: 70px; - padding-top: 20px; - padding-left:0px; + padding-left: 0 !important; border-bottom: none; } - - -.social .social-links .fa:hover { - background-color: #ffffff; - border: 1px solid #403644; - color: #403644; +.res-social, +.res-actions { + margin-top: 20px; + margin-bottom: 50px; } - - -.social .social-links .fa { - padding: 12px 5px; - font-size: 30px; - width: 50px; - height: 50px; - text-align: center; - text-decoration: none; - border-radius: 50%; - background-color: #403644; - color: #ffffff; +.res-actions .dropdown-menu { + padding: 0; + width: 100%; + min-width: 50px; + border: none; + margin: 0; } - - -.module-content .res-actions{ - position: None; - float: None; - top: None; - right: 0px; - margin-top:30px; - margin-bottom:30px; +.res-actions .dropdown-menu a { + border-radius: 0; + margin-top: 0; +} +/* FIXME: hiding for Alpha */ +.res-actions ul > li a[data-module=api-info] { + display: none; +} +.res-actions .btn:active, +.open > .dropdown-toggle.btn, +.open > .dropdown-toggle.btn:hover, +.res-actions .btn:hover { + background-color: #f65940; + border-color: #f65940; + color: #fff; } - - - - - - - - diff --git a/ckanext/justicehub_theme/public/search.css b/ckanext/justicehub_theme/public/search.css index 8d953cc..1c63f33 100644 --- a/ckanext/justicehub_theme/public/search.css +++ b/ckanext/justicehub_theme/public/search.css @@ -1,22 +1,89 @@ -.field-giant-search { +.sort-box, +.sort-box:focus, +.search-form .btn { + border-left: none; } -.dataset-count-box { - color: #202020; - padding: 20px 0 !important; +#field-giant-search:focus { + border-right: none; +} + +.search-form .input-group-btn .btn, +.search-form .input-group >:first-child, +.search-form .input-group > select { + height: 75px; +} +.module-content .search-form { + margin-left: -30px; + margin-top: -15px; + margin-right: -5px; +} +#field-giant-search+.input-group-btn .fa { + padding: 0 5px; +} +#field-giant-search+.input-group-btn .btn { + background-color: #f65940; + color: #ffffff; +} +.search-form .search-input.search-giant .form-control { + border-radius: 60px; + line-height: 2; + border: 4px solid #f65940; + padding-left: 3%; + height: 50%; +} +.search-form .search-input.search-giant .fa { + color: #ffffff; + margin: auto; +} +.search-form .search-input.search-giant button { + background-color: #f65940; + border-radius: 50px; + display: flex; + justify-content: center; + align-items: center; + width: 60px; + height: 60px; + margin-top: -30px; +} +.homepage .module-search .form-group { + padding: 0 0; +} +.search-form .search-input.search-giant { + width: 80%; + margin: auto; +} +.footer-links .input-group-btn .btn, +.footer-links .input-group input { + border-radius: 30px; +} +.footer-links .input-group-btn:last-child > .btn { + border-bottom-right-radius: 30px !important; + border-top-right-radius: 30px !important; +} +.footer-links .input-group .form-control:focus { + border-color: #f65940; + z-index: 0; +} +.footer-links input { + padding: 20px 15px; +} +.footer-links .input-group-btn .btn { + margin-left: -20px; + border-color: #403644; text-align: center; } -.dataset-count-box span { - font-size: 25px; - font-weight: bold; +.dataset-count-box { + border-left: none !important; + padding: 0; + justify-content: center !important; + align-items: center !important; + display: flex !important; + flex-direction: column; } -.dataset-count { - color: #f65940; +.empty, .extra { + padding-left: 20px; } .filters .module-heading { - color: #ffffff; - font-weight: bold; - font-size: 30px; - background-color: #403644; border-color: #f65940; margin-bottom: 0 !important; border-top: none; @@ -24,29 +91,85 @@ .filters .module-content { height: 100%; } -.nav-facet .nav-item.active > a{ +.nav-facet .nav-item.active, +.nav-facet .nav-item.active a { background-color: #f65940; color: #ffffff; } -.nav-item.active > a:hover { - color: #403644; -} -.nav-facet .nav-item > a { - color: #ffffff; - font-size: 20px; -} +.nav-facet .nav-item:hover, .nav-facet .nav-item > a:hover { color: #403644; background-color: #ffffff; } -.nav-facet .nav-item > a:hover::after { +.nav-facet .nav-item.active > a::before { + border-left: none; +} +.nav-facet .nav-item:not(.active) > a:hover::after { background: none; } +.filters .module-content.empty, .filters .nav-facet .nav-item { - padding-left: 40px; + padding-left: 50px; border-bottom: none; } -.nav-facet, .filters .module-content.empty { - background-color: #403644; - color: #ffffff; +.filters .module-footer { + display: none; +} +.popover-followee .popover-header .input-group-addon { + border-radius: 0px; + border: 1px solid #f65940; + background-color: #f65940; +} + +/* -------------------------------------- Mobile adjustments ----------------------------- */ +@media (min-width: 320px) and (orientation: portrait) { + #field-giant-search { + padding-left: 35px; + } + .search-form .input-group-btn .btn, + .search-form .input-group >:first-child, + .search-form .input-group > select { + height: 50px; + } + .search-form .search-input.search-giant .form-control { + line-height: 1; + border: 3px solid #f65940; + } + .search-form .search-input.search-giant { + width: 90%; + } + .search-form .search-input.search-giant .fa { + color: #403644; + } + .search-form .search-input.search-giant button { + background: transparent; + margin-right: -5%; + } + .footer-links input { + padding: 0 10px; + line-height: 1; + font-size: 12px; + } + .footer-links .input-group { + display: block; + } + .footer-links .input-group .form-control { + border-top-right-radius: 30px; + border-bottom-right-radius: 30px; + } + .footer-links .input-group-btn .btn { + position: absolute; + top: 5px; + left: -10px; + } + .footer-links .input-group-btn .btn { + background-color: #f65940; + border-color: #f65940; + padding: 0 5px; + } + .footer-links .input-group-btn button > .fa { + text-align: center; + font-size: 14px; + width: 10px; + } } diff --git a/ckanext/justicehub_theme/templates/base.html b/ckanext/justicehub_theme/templates/base.html index 8f37c05..b111432 100644 --- a/ckanext/justicehub_theme/templates/base.html +++ b/ckanext/justicehub_theme/templates/base.html @@ -1,16 +1,24 @@ {% ckan_extends %} +{% block meta_viewport %} + +{% endblock %} + {% block styles %} {{ super() }} + - - - + - + {% endblock %} +{%- block scripts %} + {{ super() }} + {% resource 'ckanext_issues/autocomplete-action-api.js' %} + {% resource 'justicehub_theme/justicehub_theme.js' %} +{% endblock -%} diff --git a/ckanext/justicehub_theme/templates/footer.html b/ckanext/justicehub_theme/templates/footer.html index b112ea7..88a6c1e 100644 --- a/ckanext/justicehub_theme/templates/footer.html +++ b/ckanext/justicehub_theme/templates/footer.html @@ -1,100 +1,141 @@
- {% resource "justicehub_theme/subscribe.js"%}
- - {% block footer_content %} - + {% block footer_content %} +
-
\ No newline at end of file + diff --git a/ckanext/justicehub_theme/templates/group/index.html b/ckanext/justicehub_theme/templates/group/index.html new file mode 100644 index 0000000..a54d9db --- /dev/null +++ b/ckanext/justicehub_theme/templates/group/index.html @@ -0,0 +1,37 @@ +{% ckan_extends %} + +{% block subtitle %}{{ _('Sectors') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Sectors'), controller='group', action='index', named_route=group_type + '_index' %}
  • +{% endblock %} + +{% block pre_primary %} + {% block page_primary_action %} +
    + {% if h.check_access('group_create') %} + {% link_for _('ADD SECTOR'), controller='group', action='new', class_='btn btn-primary pull-right', named_route=group_type + '_new' %} + {% endif %} +
    + {% endblock %} + + {% block groups_search_form %} + {% snippet 'snippets/search_form.html', form_id='group-search-form', type='group', query=c.q, sorting_selected=c.sort_by_selected, count=c.page.item_count, placeholder=_('Search groups...'), show_empty=request.params, no_bottom_border=true if c.page.items, sorting = [(_('Name Ascending'), 'title asc'), (_('Name Descending'), 'title desc')] %} + {% endblock %} +{% endblock %} + +{% block primary_content %} + {% block primary_content_inner %} + +

    {{ _('Groups') }}

    + + {% block groups_list %} + {{ super() }} + {% endblock %} + + {% block page_pagination %} + {{ c.page.pager(q=c.q or '', sort=c.sort_by_selected or '') }} + {% endblock %} + + {% endblock %} +{% endblock %} diff --git a/ckanext/justicehub_theme/templates/header.html b/ckanext/justicehub_theme/templates/header.html index 1137a5e..1a06836 100644 --- a/ckanext/justicehub_theme/templates/header.html +++ b/ckanext/justicehub_theme/templates/header.html @@ -3,8 +3,39 @@ {% block header_site_navigation %} {% endblock %} diff --git a/ckanext/justicehub_theme/templates/home/about.html b/ckanext/justicehub_theme/templates/home/about.html new file mode 100644 index 0000000..316d2cb --- /dev/null +++ b/ckanext/justicehub_theme/templates/home/about.html @@ -0,0 +1,15 @@ +{% ckan_extends %} + +{% block about %} +
    +
    + {% if g.site_about %} + {{ h.render_markdown(g.site_about) }} + {% else %} +

    {{ _('About') }}

    + {% snippet 'home/snippets/about_text.html' %} + {% endif %} + {% endblock %} +
    +
    + diff --git a/ckanext/justicehub_theme/templates/home/layout4.html b/ckanext/justicehub_theme/templates/home/layout4.html index 8229d60..ae2c49c 100644 --- a/ckanext/justicehub_theme/templates/home/layout4.html +++ b/ckanext/justicehub_theme/templates/home/layout4.html @@ -2,33 +2,53 @@
    {% block search %} - {% snippet 'home/snippets/search.html' %} + {% snippet 'home/snippets/search.html' %} {% endblock %}
    -
    -
    - -
    -
    -

    - {{ _('An Open community to exchange justice related data, knowledge and other resources online and offline.') }} -

    -
    -
    -

    {{ _('JUSTICE HUB') }}

    -

    - {{ _('This is an intiative from CivicDataLab in collaboration with Agami.\ - The hub initiated as part of the Agami Data for Justice Challenge 2019.') }} -

    -

    - {{ _('Justice Hub is a collobarative data platform that focuses on legal and justice related datasets and other resources.') }} -

    -

    - {{ _('Our objective is to build a common platform for members of the legal data and tech community\ - (Civil Society Organizations, researchers, journalists, law firms etc.) to share, build and collaborate on the common.') }} -

    + +
    +
    +
    +
    + {{ _('We enable organizations + and individuals to create, share, + manage and harness datasets for justice.') }} +
    +
    +
    +
    + +
    +
    +
    +
    diff --git a/ckanext/justicehub_theme/templates/home/snippets/search.html b/ckanext/justicehub_theme/templates/home/snippets/search.html index 29b7d83..19711d6 100644 --- a/ckanext/justicehub_theme/templates/home/snippets/search.html +++ b/ckanext/justicehub_theme/templates/home/snippets/search.html @@ -3,6 +3,23 @@ {% set stats=h.get_site_statistics() %}