Skip to content

Commit

Permalink
Merge pull request #17 from abgov/Clone
Browse files Browse the repository at this point in the history
Clone functionality
  • Loading branch information
coreyerickson authored Oct 4, 2016
2 parents 09a3e2f + 2c3253c commit c198117
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 32 deletions.
103 changes: 102 additions & 1 deletion ckanext/open_alberta/controller.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from logging import getLogger
import logging
import re
from datetime import datetime
import urlparse
import requests
import ckan.logic as logic
import ckan.lib.base as base
import ckan.plugins.toolkit as toolkit

from ckan.common import _, request, c
import ckan.lib.helpers as h
Expand All @@ -11,12 +14,17 @@
import ckan.lib.navl.dictization_functions as dictization_functions
import ckan.lib.mailer as mailer
from ckan.controllers.user import UserController
from ckan.lib.base import BaseController

from pylons import config
from pylons.decorators import jsonify
import ckan.lib.captcha as captcha

unflatten = dictization_functions.unflatten

_NOT_AUTHORIZED = _('Not authorized to see this page')
_UNEXPECTED_ERROR = _('Server error. Please contact technical support.')


class SuggestController(base.BaseController):

Expand Down Expand Up @@ -172,3 +180,96 @@ def __before__(self, action, **env):
UserController.__before__(self, action, **env)
c.display_private_only = True


class PackageCloneController(BaseController):
""" Controller to faciliatate cloning a package to ease up creating new
data sets.
"""

def __before__(self, action, **env):
""" Checks if the invoking user has permissions to create data sets.
If the permission check fails, HTTP 401 error is raised.
"""
base.BaseController.__before__(self, action, **env)
self._context = dict(model=base.model,
user=base.c.user,
auth_user_obj=base.c.userobj)
try:
logic.check_access('package_create', self._context)
except logic.NotAuthorized:
base.abort(401, _NOT_AUTHORIZED)


@jsonify
def index(self, id):
""" Clone the specified data set record.
Arguments:
id (string): URL/slug of the data set.
Returns:
string: JSON response.
Successful clone return value:
{'status': 'success',
'redirect_url': <URL of data set edit page>
}
Data validation error return value:
{'status': 'error',
'errors': {<field1>: [<validation error message>],
<field2>: [<validation error message>]}
}
Any other (unexpected) error:
{'status': 'error',
'errorMessage': <message>
}
"""
logger = logging.getLogger(__name__)

if toolkit.request.method == 'POST':
try:
# TODO: handle publication
pkg = toolkit.get_action('package_show')(None, dict(id=id))

cfg_adst = config.get('ckanext.openalberta.clonable_ds_types', 'opendata,publication')
allowed_types = set(re.split('\s*,\s*', cfg_adst))
if pkg['type'] not in allowed_types:
logger.warn('Requested cloning of unsupported package type (%s). Supported types: %s.',
pkg['type'], cfg_adt)
return {
'status': 'error',
'errorMessage': _('This package type is not allowed to be cloned.')
}

pkg['title'] = toolkit.request.params.getone('title')
pkg['name'] = toolkit.request.params.getone('name')
pkg['date_created'] = pkg['date_modified'] = datetime.now()
pkg['state'] = 'draft'
del pkg['id']

action = toolkit.get_action('package_create')
newpkg = action(self._context, pkg)
return {
'status': 'success',
'redirect_url': h.url_for(controller='package', action='edit', id=newpkg['name'])
}
except toolkit.ValidationError as ve:
errflds = set(ve.error_dict.keys()) - {'title', 'name'}
if errflds:
# There are validation errors other than title and name (slug).
# If this happens, it means something is wrong with the package
return {
'status': 'error',
'errorMessage': _('The data set is in an invalid state. Please correct it before trying to clone.')
}
return {
'status': 'error',
'errors': ve.error_dict
}
except:
logger.exception('Error in PackageCloneController:index')
return {
'status': 'error',
'errorMessage': _UNEXPECTED_ERROR
}

else:
toolkit.abort(403, _NOT_AUTHORIZED)

9 changes: 9 additions & 0 deletions ckanext/open_alberta/fanstatic/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2435,3 +2435,12 @@ select, select.form-control, input.form-control { -webkit-transition: color 500m
.alberta-theme table tr.odd { background: #ffffff; }
.alberta-theme table tr td { border-bottom: 0; border-right: 1px solid #dddddd; padding: 6px 12px; max-width: 200px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
.alberta-theme table tr td a { font-weight: 700; text-decoration: underline; color: #000; }

#id-clone-ds-form .top-error-msg {
border: solid red 1px;
background-color: #FFE0E0;
color: red;
padding: 0.5em;
font-weight: bold;
display: none;
}
44 changes: 44 additions & 0 deletions ckanext/open_alberta/fanstatic/js/clone-ds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
$(function() {
/* This submits dataset clone request via Ajax.
* It has to be done this way because the dataset view page has javascript tabs,
* unlike vanilla CKAN.
* */
$('#id-clone-ds-form button[type=submit]').click(function(e) {
console.log('clone clicked...');
var url = $('#id-clone-ds-form').attr('action');
var titleInp = $('#field-title');
var urlInp = $('#field-name');
$('#id-clone-ds-form .top-error-msg').text('').hide();
titleInp.parent().removeClass('error').find('span.error-block').remove();
urlInp.parent().removeClass('error').find('span.error-block').remove();
$.post(url,
{ title: $('#field-title').val(),
name: $('#field-name').val() },
function(data) {
if (data.status == 'success')
window.location.href = data.redirect_url;
else if (data.status == 'error') {
if (data.errorMessage) {
$('#id-clone-ds-form .top-error-msg').text(data.errorMessage).show();
}
else {
if (data.errors.title)
titleInp.after('<span class="error-block">'+data.errors.title[0]+'</span>')
.parent().addClass('error');
if (data.errors.name) {
// URL input is initially hidden. Need to show.
var editBtn = titleInp.parent().find('.btn-mini');
if (editBtn.length)
$(editBtn[0]).click();

urlInp.after('<span class="error-block">'+data.errors.name[0]+'</span>')
.parent().addClass('error');
}
}
}
else
; // TODO: Unexpected server response
});
e.preventDefault();
});
});
16 changes: 12 additions & 4 deletions ckanext/open_alberta/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ def before_map(self, m):
controller='ckanext.open_alberta.controller:PagesController',
action='licence')

m.connect('private-packages' ,'/dashboard/datasets/private',
controller='ckanext.open_alberta.controller:DashboardPackagesController',
action='dashboard_datasets')

# /content/government-alberta-open-information-and-open-data-policy > /policy
m.redirect('/content/government-alberta-open-information-and-open-data-policy',
'/policy',
Expand Down Expand Up @@ -87,10 +83,12 @@ def before_map(self, m):

return m


class Open_AlbertaPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IConfigurer)
plugins.implements(plugins.ITemplateHelpers)
plugins.implements(plugins.interfaces.IActions)
plugins.implements(plugins.IRoutes, inherit=True)

def update_config(self, config_):
toolkit.add_template_directory(config_, 'templates')
Expand All @@ -104,6 +102,16 @@ def get_actions(self):
# Registers the custom API method defined above
return {'counter_on': counter_on_off}

def before_map(self, m):
m.connect('private-packages' ,'/dashboard/datasets/private',
controller='ckanext.open_alberta.controller:DashboardPackagesController',
action='dashboard_datasets')
m.connect('clone', '/dataset/clone/{id}',
controller='ckanext.open_alberta.controller:PackageCloneController',
action='index')
return m


class DateSearchPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IConfigurer)
plugins.implements(plugins.IPackageController, inherit=True)
Expand Down
1 change: 1 addition & 0 deletions ckanext/open_alberta/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
{% resource 'open_alberta/js/addthis.js' %}
{% resource 'open_alberta/js/abca.js' %}
{% resource 'open_alberta/js/private-ds.js' %}
{% resource 'open_alberta/js/clone-ds.js' %}
{% endblock %}
{% block bodytag %}
class="alberta-theme" {{ super() }}
Expand Down
39 changes: 24 additions & 15 deletions ckanext/open_alberta/templates/package/read.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{% extends "package/read_base.html" %}
{% import 'macros/form.html' as form %}

{% set pkg = c.pkg_dict %}
{# Display notes on package/read.html#}
{% set notes = h.markdown_extract(pkg.notes,extract_length=5000) %}

{% block primary_content_inner %}
<div class="container nopadding">
<div class="row">
Expand All @@ -27,23 +30,24 @@ <h1>
</h1>
#}
</div>
{% if pkg.archive_date|length > 0 or pkg.archivedate|length > 0 %}
<div class="col-sm-12">
{% if pkg.archive_date|length > 0 or pkg.archivedate|length > 0 %}
<div class="col-sm-12">
<h3 class="archived-item"><i class="fa fa-archive"></i> Archived</h3>
<div class="archived-item-explanation">This item has been replaced by a more recent resource or the content may be otherwise out of date. It is provided for informational and research purposes.</div>
</div>
{% endif %}
{% if pkg.replacedby_title and pkg.replacedby_uri %}
<div class="col-sm-12">
<h4 class="replaced-by">Replaced by: <a href="{{pkg.replacedby_uri}}">{{pkg.replacedby_title}}</a></h4>
</div>
{% endif %}
<div class="col-sm-12">
<div class="archived-item-explanation">This item has been replaced by a more recent resource or the content may be otherwise out of date. It is provided for informational and research purposes.</div>
</div>
{% endif %}
{% if pkg.replacedby_title and pkg.replacedby_uri %}
<div class="col-sm-12">
<h4 class="replaced-by">Replaced by: <a href="{{pkg.replacedby_uri}}">{{pkg.replacedby_title}}</a></h4>
</div>
{% endif %}
<div class="col-sm-12">
<ul class="nav nav-tabs resource-tabs">
<li role="presentation" class="active" data-tab-group="data-results" data-tab-name="tab-summary"><a href="#summary">Summary</a></li>
<li role="presentation" data-tab-group="data-results" data-tab-name="tab-detailed"><a href="#detailed">Detailed Information</a></li>
{# related_item/list will be removed from CKAN #}
{# {{ h.build_nav_icon('related_list', _('Related'), id=pkg.name) }} #}
{% if c.userobj %}
<li role="presentation" data-tab-group="data-results" data-tab-name="tab-actions"><a href="#actions">Actions</a></li>
{% endif %}
</ul>
<div id="result-summary" class="active tab-box tab-summary box" data-tab-group="data-results">
<div class="summary-section">
Expand Down Expand Up @@ -74,10 +78,15 @@ <h5 class="sm margin-md-top">Tags</h5>
{% snippet "package/snippets/additional_info.html", pkg_dict=pkg %}
{% endblock %}
</div>
{% if c.userobj %}
<div id="actions" class="tab-box tab-actions box" data-tab-group="data-results">
{% snippet "package/snippets/clone_form.html", pkg=pkg %}
</div>
{% endif %}
</div>
{% block package_resources %}
{% block package_resources %}

{% endblock %}
{% endblock %}
</div>
</div>
{% endblock %}
25 changes: 25 additions & 0 deletions ckanext/open_alberta/templates/package/snippets/clone_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% import 'macros/form.html' as form %}

<h2>{% block page_heading %}{{ _('Clone') }} {{ h.dataset_display_name(pkg) }}{% endblock %}</h2>

<form id="id-clone-ds-form" class="dataset-form form-horizontal" method="post" action="{{ h.url_for('clone', id=pkg.name) }}">
<div class="top-error-msg">Unexpected error message goes here...</div>
{% block package_basic_fields_title %}
{{ form.input('title', id='field-title', label=_('Title'), placeholder=_('New Title'),
attrs={'data-module': 'slug-preview-target'}, is_required=True) }}
{% endblock %}

{% block package_basic_fields_url %}
{% set prefix = h.url_for(controller='package', action='read', id='') %}
{% set domain = h.url_for(controller='package', action='read', id='', qualified=true) %}
{% set domain = domain|replace("http://", "")|replace("https://", "") %}
{% set attrs = {'data-module': 'slug-preview-slug', 'data-module-prefix': domain, 'data-module-placeholder': '<dataset>'} %}

{{ form.prepend('name', id='field-name', label=_('URL'), prepend=prefix, placeholder=_('New URL'),
attrs=attrs, is_required=true) }}
{% endblock %}

<div class="form-actions">
<button class="btn btn-primary" type="submit" name="save">{{ _('Clone') }}</button>
</div>
</form>
5 changes: 3 additions & 2 deletions ckanext/open_alberta/templates/snippets/package_item.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
{% set title = package.title or package.name %}
{% set notes = h.markdown_extract(package.notes, extract_length=truncate) %}
{% set modified_date = h.render_datetime(package.date_modified) %}
{% set ministry = package.organization.title %}
{# set ministry = package.organization.title #}

{% block package_item %}
{% if c.display_private_only and not package.private %}
{% else %}
{# skip #}
{% elif package.type == 'opendata' %}
<div class="row {{ item_class or "dataset-item" }}">
<div class="col-sm-12 col-md-8 nopadding">
{% block content %}
Expand Down
Loading

0 comments on commit c198117

Please sign in to comment.