Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/approve reject #5

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions ckanext/ed/actions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from ckan.plugins import toolkit
from logging import getLogger
import os
import requests
import uuid
from ckanext.ed import helpers
import zipfile
import os

from ckan.controllers.admin import get_sysadmins
import requests
from logging import getLogger
from ckan.lib.mailer import MailerException
from ckan.logic.action.create import package_create as core_package_create
from ckan.logic.action.get import package_show as core_package_show
from ckan.plugins import toolkit

from ckanext.ed import helpers
from ckanext.ed.mailer import mail_package_publish_request_to_admins


SUPPORTED_RESOURCE_MIMETYPES = [
Expand Down Expand Up @@ -136,3 +142,30 @@ def prepare_zip_resources(context, data_dict):
os.remove(file_path)

return {'zip_id': None}


@toolkit.side_effect_free
def package_show(context, data_dict):
package = core_package_show(context, data_dict)
# User with less perms then creator should not be able to access pending dataset
approval_pending = package.get('approval_state') == 'approval_pending'
try:
toolkit.check_access('package_update', context, data_dict)
can_edit = True
except toolkit.NotAuthorized:
can_edit = False
if not can_edit and approval_pending:
raise toolkit.ObjectNotFound
return package


@toolkit.side_effect_free
def package_create(context, data_dict):
dataset_dict = core_package_create(context, data_dict)
if dataset_dict.get('approval_state') == 'approval_pending':
try:
mail_package_publish_request_to_admins(context, dataset_dict)
except MailerException:
message = '[email] Package Publishing request is not sent: {0}'
log.critical(message.format(data_dict.get('title')))
return dataset_dict
46 changes: 44 additions & 2 deletions ckanext/ed/controller.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import os
from ckan.plugins import toolkit
from ckanext.ed.helpers import get_storage_path_for
import logging

from ckan import model
from ckan.common import response
from ckan.lib import base
from ckan.plugins import toolkit

from ckanext.ed.helpers import get_storage_path_for

log = logging.getLogger()


class DownloadController(base.BaseController):
Expand All @@ -25,3 +31,39 @@ def download_zip(self, zip_id):
response.headers['Content-Type'] = 'application/octet-stream'
response.content_disposition = 'attachment; filename=' + package_name
os.remove(file_path)


class ApproveRejectControler(base.BaseController):
def approve(self, id):
_make_action(id, 'approve')

def reject(self, id):
_make_action(id, 'reject')


def _raise_not_authz_or_not_pending(id):
toolkit.check_access(
'package_delete', {'model': model, 'user': toolkit.c.user}, {'id': id})
# check approval_state is pending
data_dict = toolkit.get_action('package_show')({}, {'id': id})
if data_dict.get('approval_state') != 'approval_pending':
raise toolkit.ObjectNotFound('Dataset "{}" not found'.format(id))


def _make_action(package_id, action='reject'):
states = {
'reject': 'rejected',
'approve': 'approved'
}
# check access and state
_raise_not_authz_or_not_pending(package_id)
data_dict = toolkit.get_action('package_patch')(
{'model': model, 'user': toolkit.c.user},
{'id': package_id, 'approval_state': states[action]}
)
msg = 'Dataset "{0}" {1}'.format(data_dict['title'], states[action])
if action == 'approve':
toolkit.h.flash_success(msg)
else:
toolkit.h.flash_error(msg)
toolkit.redirect_to(controller='package', action='read', id=data_dict['name'])
52 changes: 37 additions & 15 deletions ckanext/ed/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,38 +29,39 @@ def get_recently_updated_datasets(limit=5):
Returns recent created or updated datasets.
:param limit: Limit of the datasets to be returned. Default is 5.
:type limit: integer
:param user: user name
:type user: string

:returns: a list of recently created or updated datasets
:rtype: list
'''
try:
pkg_search_results = toolkit.get_action('package_search')(data_dict={
'sort': 'metadata_modified desc',
'rows': limit,
})['results']
pkg_search_results = toolkit.get_action('package_search')(
data_dict={
'sort': 'metadata_modified desc',
'rows': limit
})['results']
return pkg_search_results

except toolkit.ValidationError, search.SearchError:
return []
else:
pkgs = []
for pkg in pkg_search_results:
package = toolkit.get_action('package_show')(
data_dict={'id': pkg['id']})
modified = datetime.strptime(
package['metadata_modified'].split('T')[0], '%Y-%m-%d')
package['days_ago_modified'] = ((datetime.now() - modified).days)
pkgs.append(package)
return pkgs

log.warning('Unexpected Error occured while searching')
return []

def get_most_popular_datasets(limit=5):
'''
Returns most popular datasets based on total views.
:param limit: Limit of the datasets to be returned. Default is 5.
:type limit: integer
:param user: user name
:type user: string

:returns: a list of most popular datasets
:rtype: list
'''
data = pkg_search_results = toolkit.get_action('package_search')(data_dict={
data = pkg_search_results = toolkit.get_action('package_search')(
data_dict={
'sort': 'views_total desc',
'rows': limit,
})['results']
Expand Down Expand Up @@ -105,3 +106,24 @@ def get_total_views_for_dataset(id):
return dataset.get('tracking_summary').get('total')
except Exception:
return 0


def is_admin(user, office=None):
"""
Returns True if user is admin of given organisation.
If office param is not provided checks if user is admin of any organisation

:param user: user name
:type user: string
:param office: office id
:type user: string

:returns: True/False
:rtype: boolean
"""
user_orgs = _get_action(
'organization_list_for_user', {'user': user}, {'user': user})
if office is not None:
return any([i.get('capacity') == 'admin' \
and i.get('id') == office for i in user_orgs])
return any([i.get('capacity') == 'admin' for i in user_orgs])
42 changes: 42 additions & 0 deletions ckanext/ed/mailer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import logging

from ckan import model
from ckan.common import config
from ckan.plugins import toolkit
from ckan.lib.mailer import mail_user
from ckan.lib.base import render_jinja2
from ckan.logic.action.get import member_list as core_member_list

log = logging.getLogger(__name__)


def mail_package_publish_request_to_admins(context, data_dict):
members = core_member_list(
context=context,
data_dict={'id': data_dict.get('owner_org')}
)
admin_ids = [i[0] for i in members if i[2] == 'Admin']
for admin_id in admin_ids:
user = model.User.get(admin_id)
if user.email:
subj = _compose_email_subj(data_dict, event='request')
body = _compose_email_body(data_dict, user, event='request')
mail_user(user, subj, body)
log.debug('[email] Pakcage publishing request email sent to {0}'.format(user.name))


def _compose_email_subj(data_dict, event='request'):
return '[US ED] Package Publishing {0}: {1}'.format(event.capitalize(), data_dict.get('title'))


def _compose_email_body(data_dict, user, event='request'):
pkg_link = toolkit.url_for('dataset_read', id=data_dict['name'], qualified=True)
return render_jinja2('emails/package_publish_{0}.txt'.format(event), {
'admin_name': user.fullname or user.name,
'site_title': config.get('ckan.site_title'),
'site_url': config.get('ckan.site_url'),
'package_title': data_dict.get('title'),
'package_description': data_dict.get('notes', ''),
'package_url': pkg_link,
'publisher_name': data_dict.get('contact_name')
})
35 changes: 28 additions & 7 deletions ckanext/ed/plugin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from ckan.lib.plugins import DefaultTranslation
import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit
from ckanext.ed import helpers
from ckanext.ed import actions
from ckan.lib.plugins import DefaultTranslation

from ckanext.ed import actions, helpers, validators


class EDPlugin(plugins.SingletonPlugin, DefaultTranslation):
Expand All @@ -11,34 +11,49 @@ class EDPlugin(plugins.SingletonPlugin, DefaultTranslation):
plugins.implements(plugins.ITranslation)
plugins.implements(plugins.IActions)
plugins.implements(plugins.IRoutes, inherit=True)
plugins.implements(plugins.IValidators)
plugins.implements(plugins.IPackageController, inherit=True)

# ITemplateHelpers

def get_helpers(self):
return {
'ed_get_groups': helpers.get_groups,
'ed_is_admin': helpers.is_admin,
'ed_get_recently_updated_datasets': helpers.get_recently_updated_datasets,
'ed_get_most_popular_datasets': helpers.get_most_popular_datasets,
'ed_get_total_views_for_dataset': helpers.get_total_views_for_dataset,
}

# IActions

def get_actions(self):
return {
'ed_prepare_zip_resources': actions.prepare_zip_resources,
'package_create': actions.package_create,
'package_show': actions.package_show
}

# IConfigurer
# IPackageController
def before_search(self, search_params):
search_params.update({
'fq': '!(approval_state:approval_pending) ' + search_params.get('fq', '')
})
return search_params

# IConfigurer
def update_config(self, config_):
toolkit.add_template_directory(config_, 'templates')
toolkit.add_public_directory(config_, 'public')
toolkit.add_resource('fanstatic', 'ed')

# IRoutes

def before_map(self, map):
publish_controller = 'ckanext.ed.controller:ApproveRejectControler'
map.connect('/dataset-publish/{id}/approve',
controller=publish_controller,
action='approve')
map.connect('/dataset-publish/{id}/reject',
controller=publish_controller,
action='reject')
map.connect(
'download_zip',
'/download/zip/{zip_id}',
Expand All @@ -47,3 +62,9 @@ def before_map(self, map):
)

return map

# IValidators
def get_validators(self):
return {
'state_validator': validators.state_validator
}
6 changes: 6 additions & 0 deletions ckanext/ed/schemas/dataset.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@
"label": "Temporal",
"form_placeholder": "eg. 2000-01-15T00:45:00Z/2010-01-15T00:06:00Z",
"help_text": "The range of temporal applicability of a dataset (i.e., a start and end date of applicability for the data)."
},
{
"field_name": "approval_state",
"form_snippet": null,
"validators": "state_validator",
"label": "Approved"
}
],
"resource_fields": [
Expand Down
18 changes: 18 additions & 0 deletions ckanext/ed/templates/emails/package_publish_request.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Dear {{ admin_name }},

A new package has been requested for publishing by {{ publisher_name }}:

{{ package_title }}

{{ package_description}}

To approve or reject the request, please visit the following page (logged in as a office administrator):

{{ package_url }}

Have a nice day.


--
Message sent by {{ site_title }} ({{ site_url }})
This is an automated message, please don't respond to this address.
34 changes: 34 additions & 0 deletions ckanext/ed/templates/package/new_package_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{% extends 'package/snippets/package_form.html' %}

{% block stages %}
{% if form_style != 'edit' %}
{% if not h.ed_is_admin(c.user) %}
<div class="alert alert-warning" role="alert">
<p>
{% trans %}<strong>Note:</strong> This dataset will be submited for an administrator approval!{% endtrans %}
</p>
</div>
{% endif %}
{{ super() }}
{% endif %}
{% endblock %}

{% block save_button_text %}
{% if form_style != 'edit' %}
{{ super() }}
{% else %}
{{ _('Update Dataset') }}
{% endif %}
{% endblock %}

{% block cancel_button %}
{% if form_style != 'edit' %}
{{ super() }}
{% endif %}
{% endblock %}

{% block delete_button %}
{% if form_style == 'edit' and h.check_access('package_delete', {'id': pkg_dict.id}) %}
{{ super() }}
{% endif %}
{% endblock %}
Loading