Skip to content

Commit

Permalink
[#1251] add view forms
Browse files Browse the repository at this point in the history
  • Loading branch information
kindly committed Oct 30, 2013
1 parent c36e377 commit 2a184a2
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 40 deletions.
7 changes: 7 additions & 0 deletions ckan/config/routing.py
Expand Up @@ -260,6 +260,13 @@ def make_map():
height="800")
m.connect('/dataset/{id}/resource/{resource_id}/preview',
action='resource_datapreview')
m.connect('views', '/dataset/{id}/resource/{resource_id}/views',
action='resource_views', ckan_icon='reorder')
m.connect('new_view', '/dataset/{id}/resource/{resource_id}/new_view',
action='edit_view', ckan_icon='edit')
m.connect('edit_view',
'/dataset/{id}/resource/{resource_id}/edit_view/{view_id}',
action='edit_view', ckan_icon='edit')

# group
map.redirect('/groups', '/group')
Expand Down
126 changes: 125 additions & 1 deletion ckan/controllers/package.py
Expand Up @@ -310,7 +310,7 @@ def resources(self, id):
data_dict = {'id': id}

try:
check_access('package_update', context)
check_access('package_update', context, data_dict)
except NotAuthorized, e:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
# check if package exists
Expand Down Expand Up @@ -1378,6 +1378,130 @@ def _parse_recline_state(self, params):
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 or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
data_dict = {'id': id}

try:
check_access('package_update', context, data_dict)
except NotAuthorized, e:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
# check if package exists
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
c.pkg = context['package']
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % id)

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(401, _('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 or c.author, '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, e:
abort(401, _('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:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % id)
try:
c.resource = get_action('resource_show')(context,
{'id': resource_id})
except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % id)

data = {}
errors = {}
error_summary = {}
view_type = None

if request.method == 'POST':
request.POST.pop('save', None)
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
get_action('resource_view_update')(context, data)
else:
get_action('resource_view_create')(context, data)
except ValidationError, e:
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(401, _('Unauthorized to edit resource'))
else:
redirect(h.url_for(controller='package',
action='resource_views',
id=id, resource_id=resource_id))

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')
except NotFound:
abort(404, _('View not found'))
except NotAuthorized:
abort(401, _('Unauthorized to view View %s') % view_id)


view_type = view_type or request.GET.get('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)

vars = {'form_template': view_plugin.info().get('form_template'),
'data': data, 'errors': errors, 'error_summary': error_summary}

if view_id:
return render('package/edit_view.html', extra_vars = vars)
return render('package/new_view.html', extra_vars = vars)


def resource_datapreview(self, id, resource_id):
'''
Embeded page for a resource data-preview.
Expand Down
8 changes: 8 additions & 0 deletions ckan/lib/datapreview.py
Expand Up @@ -130,4 +130,12 @@ def get_view_plugin(view_type):
if name == view_type:
return plugin

def get_allowed_view_plugins(data_dict):

can_preview = []

for plugin in p.PluginImplementations(p.IResourceView):
if plugin.can_preview(data_dict):
can_preview.append(plugin)
return can_preview

12 changes: 12 additions & 0 deletions ckan/lib/helpers.py
Expand Up @@ -1613,6 +1613,17 @@ def resource_preview(resource, package):
resource_url=url,
raw_resource_url=resource.get('url'))

def get_allowed_view_types(resource, package):
data_dict = {'resource': resource, 'package': package}
plugins = datapreview.get_allowed_view_plugins(data_dict)

allowed_view_types = []
for plugin in plugins:
info = plugin.info()
allowed_view_types.append((info['name'],
info.get('title', info['name'])))
allowed_view_types.sort(key=lambda item: item[1])
return allowed_view_types

def list_dict_filter(list_, search_field, output_field, value):
''' Takes a list of dicts and returns the value of a given key if the
Expand Down Expand Up @@ -1838,4 +1849,5 @@ def get_site_statistics():
'get_featured_organizations',
'get_featured_groups',
'get_site_statistics',
'get_allowed_view_types',
]
4 changes: 2 additions & 2 deletions ckan/logic/action/create.py
Expand Up @@ -288,7 +288,7 @@ def resource_view_create(context, data_dict):
'''
model = context['model']
schema = (context.get('schema') or
ckan.logic.schema.default_resource_view_schema())
ckan.logic.schema.default_create_resource_view_schema())

view_type = data_dict.get('view_type', '')
view_plugin = datapreview.get_view_plugin(view_type)
Expand All @@ -314,7 +314,7 @@ def resource_view_create(context, data_dict):
).filter_by(resource_id=data['resource_id']).first()

order = 0
if max_order[0]:
if max_order[0] is not None:
order = max_order[0] + 1
data['order'] = order

Expand Down
8 changes: 4 additions & 4 deletions ckan/logic/schema.py
Expand Up @@ -576,19 +576,19 @@ def create_schema_for_required_keys(keys):
return schema


def default_resource_view_schema():
def default_create_resource_view_schema():
schema = {
'id': [ignore_missing, unicode],
'resource_id': [not_empty, resource_id_exists],
'title': [ignore_missing, unicode],
'title': [not_empty, unicode],
'description': [ignore_missing, unicode],
'view_type': [not_empty, unicode],
'__extras': [empty],
}
return schema


def default_update_resource_view_schema():
schema = default_resource_view_schema()
schema = default_create_resource_view_schema()
schema.update({'id': [not_missing, not_empty, unicode]})
schema.update({'resource_id': [ignore_missing, resource_id_exists ]})
schema.update({'view_type': [ignore]}) #can not change after create
Expand Down
45 changes: 15 additions & 30 deletions ckan/plugins/interfaces.py
Expand Up @@ -201,41 +201,26 @@ class IResourceView(Interface):
'''
def info(self):
'''Return configuration for the view.
'''Return configuration for the view. Info can return
:param name: name of fiew type
:param title: title of view type (Optional)
:param schema: schema to validate extra view config (Optional)
:param form_template: path to form template that will be
added to edit/create view form, in order to define
extra config. (Optional)
eg:
{'name': 'image',
'title': 'Image',
'schema': {'image_url': [ignore_empty, unicode]},
'form_template': 'image_form.html'}
'''
return {'name': self.__class__.__name__}

def can_preview(self, data_dict):
'''Return info on whether the plugin can preview the resource.
This can be done in two ways:
1. The old way is to just return ``True`` or ``False``.
2. The new way is to return a dict with three keys:
``'can_preview'`` (``boolean``)
``True`` if the extension can preview the resource.
``'fixable'`` (``string``)
A string explaining how preview for the resource could be enabled,
for example if the ``resource_proxy`` plugin was enabled.
``'quality'`` (``int``)
How good the preview is: ``1`` (poor), ``2`` (average) or
``3`` (good). When multiple preview extensions can preview the
same resource, this is used to determine which extension will
be used.
:param data_dict: the resource to be previewed and the dataset that it
belongs to.
:type data_dict: dictionary
Make sure to check the ``on_same_domain`` value of the resource or the
url if your preview requires the resource to be on the same domain
because of the same-origin policy. To find out how to preview
resources that are on a different domain, read :ref:`resource-proxy`.
return ``True`` or ``False``.
'''

def setup_template_variables(self, context, data_dict):
Expand Down
23 changes: 23 additions & 0 deletions ckan/templates/package/edit_view.html
@@ -0,0 +1,23 @@
{% extends "package/view_edit_base.html" %}

{% block subtitle %}{{ _('Add View') }} - {{ h.resource_display_name(c.resource) }}{% endblock %}
{% block form_title %}{{ _('Add view') }}{% endblock %}

{% block breadcrumb_content %}
<li class="active"><a href="#">{{ _('Edit View') }}</a></li>
{% endblock %}

{% block form %}
<form class="dataset-form dataset-resource-form form-horizontal" method="post" data-module="basic-form resource-form">
{% snippet 'package/snippets/view_form.html', data=data, errors=errors, error_summary=error_summary, form_template=form_template %}
<div class="form-actions">
<button class="btn btn-danger pull-left" name="delete" value="Delete"> {{ _('Delete') }} </button>
<button class="btn btn-primary" name="save" value="Save" type="submit">{{ _('Update') }}</button>
</div>
</form>
{% endblock %}

{% block content_primary_nav %}
<li class="active"><a href="#"><i class="icon-edit"></i> {{ _('Edit View') }}</a></li>
{% endblock %}

22 changes: 22 additions & 0 deletions ckan/templates/package/new_view.html
@@ -0,0 +1,22 @@
{% extends "package/view_edit_base.html" %}

{% block subtitle %}{{ _('Add View') }} - {{ h.resource_display_name(c.resource) }}{% endblock %}
{% block form_title %}{{ _('Add view') }}{% endblock %}

{% block breadcrumb_content %}
<li class="active"><a href="#">{{ _('Add New View') }}</a></li>
{% endblock %}

{% block form %}
<form class="dataset-form dataset-resource-form form-horizontal" method="post" data-module="basic-form resource-form">
{% snippet 'package/snippets/view_form.html', data=data, errors=errors, error_summary=error_summary, form_template=form_template %}
<div class="form-actions">
<button class="btn btn-primary" name="save" value="Save" type="submit">{% block save_button_text %}{{ _('Add') }}{% endblock %}</button>
</div>
</form>
{% endblock %}

{% block content_primary_nav %}
<li class="active"><a href="#"><i class="icon-edit"></i> {{ _('New View') }}</a></li>
{% endblock %}

1 change: 1 addition & 0 deletions ckan/templates/package/resource_edit_base.html
Expand Up @@ -23,6 +23,7 @@
{% if 'datapusher' in g.plugins %}
{{ h.build_nav_icon('resource_data', _('Resource Data'), id=pkg.name, resource_id=res.id) }}
{% endif %}
{{ h.build_nav_icon('views', _('Views'), id=pkg.name, resource_id=res.id) }}
{% endblock %}

{% block primary_content_inner %}
Expand Down
35 changes: 35 additions & 0 deletions ckan/templates/package/resource_views.html
@@ -0,0 +1,35 @@
{% import 'macros/form.html' as form %}
{% extends "package/resource_edit_base.html" %}

{% block subtitle %}{{ _('View') }} - {{ h.resource_display_name(res) }}{% endblock %}

{% block page_primary_action %}

<form action={{ h.url_for(controller='package', action='edit_view', id=c.pkg_dict.name, resource_id=c.resource.id) }}>
<select id="field-view_type" name="view_type" data-module="autocomplete">
{% for option in h.get_allowed_view_types(c.resource, c.pkg_dict) %}
<option value="{{ option[0] }}"> {{ option[1] }}</option>
{% endfor %}
</select>
<button class="btn btn-primary" type="submit"> {{ _('New View') }}</button>
</form>

{% endblock %}

{% block primary_content_inner %}

{% if c.views %}
<ul class="resource-list">
{% for view in c.views %}
<li>
<a href="{{h.url_for(controller='package', action='edit_view', id=pkg.name, resource_id=view.resource_id, view_id=view.id)}}">
{{ view.title }}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p class="empty">{{ _('This Resource has no Views') }}</p>
{% endif %}

{% endblock %}
16 changes: 16 additions & 0 deletions ckan/templates/package/snippets/view_form.html
@@ -0,0 +1,16 @@
{% import 'macros/form.html' as form %}

{% set data = data or {} %}
{% set errors = errors or {} %}

{{ form.errors(error_summary) }}

{{ form.input('title', id='field-title', label=_('Title'), placeholder=_('eg. My View'), value=data.title, error=errors.title, classes=['control-full', 'control-large'], is_required=true) }}
{{ form.markdown('description', id='field-description', label=_('Description'), placeholder=_('eg. Information about my view'), value=data.description, error=errors.description) }}

{# form template is defined in ResouceView extention point #}
{% if form_template %}
{% snippet form_template, data=data, errors=errors %}
{% endif %}


0 comments on commit 2a184a2

Please sign in to comment.