Skip to content

Commit

Permalink
Merge branch 'master' into 220-clean-user-controller-imports
Browse files Browse the repository at this point in the history
  • Loading branch information
tobes committed Dec 20, 2012
2 parents 650ff43 + 477593b commit fc42fba
Show file tree
Hide file tree
Showing 156 changed files with 1,723 additions and 490 deletions.
10 changes: 9 additions & 1 deletion ckan/config/deployment.ini_tmpl
Expand Up @@ -33,7 +33,9 @@ app_instance_uuid = ${app_instance_uuid}

# List the names of CKAN extensions to activate.
# Note: This line is required to be here for packaging, even if it is empty.
ckan.plugins = stats
# Note: Add ``pdf_preview`` to enable the resource preview for PDFs
# Add the resource_proxy plugin to enable resorce proxying and get around the same origin policy
ckan.plugins = stats json_preview recline_preview

# If you'd like to fine-tune the individual locations of the cache data dirs
# for the Cache data, or the Session saves, un-comment the desired settings
Expand Down Expand Up @@ -235,6 +237,12 @@ ckan.feeds.author_link =
#ofs.aws_access_key_id = ....
#ofs.aws_secret_access_key = ....

## Previews
#
# Set the file types that should be previewed inline (e.g. images) or directly in an iframe.

ckan.preview.direct = png jpg gif
ckan.preview.loadable = html htm rdf+xml owl+xml xml n3 n-triples turtle plain atom csv tsv rss txt json

# Activity Streams
#
Expand Down
2 changes: 1 addition & 1 deletion ckan/config/routing.py
Expand Up @@ -269,7 +269,7 @@ def make_map():
action='resource_embedded_dataviewer')
m.connect('/dataset/{id}/resource/{resource_id}/viewer',
action='resource_embedded_dataviewer', width="960", height="800")
m.connect('/dataset/{id}/resource/{resource_id}/preview/{preview_type}',
m.connect('/dataset/{id}/resource/{resource_id}/preview',
action='resource_datapreview')

# group
Expand Down
37 changes: 33 additions & 4 deletions ckan/controllers/package.py
Expand Up @@ -28,10 +28,13 @@
import ckan.rating
import ckan.misc
import ckan.lib.accept as accept
import ckan.lib.helpers as h
import ckan.lib.datapreview as datapreview
import ckan.plugins as plugins
from home import CACHE_PARAMETERS

from ckan.lib.plugins import lookup_package_plugin
import ckan.plugins as p

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -1331,20 +1334,46 @@ def _parse_recline_state(self, params):
recline_state.pop(k)
return recline_state

def resource_datapreview(self, id, resource_id, preview_type):
def resource_datapreview(self, id, resource_id):
'''
Embeded 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, recline or a pdf preview.
'''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
context = {
'model': model,
'session': model.Session,
'user': c.user or c.author
}

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}
on_same_domain = datapreview.resource_is_on_same_domain(data_dict)
data_dict['resource']['on_same_domain'] = on_same_domain

plugins = p.PluginImplementations(p.IResourcePreview)
plugins_that_can_preview = [plugin for plugin in plugins
if plugin.can_preview(data_dict)]
if len(plugins_that_can_preview) == 0:
abort(409, _('No preview has been defined.'))
if len(plugins_that_can_preview) > 1:
log.warn('Multiple previews are possible. {0}'.format(
plugins_that_can_preview))

plugin = plugins_that_can_preview[0]
plugin.setup_template_variables(context, data_dict)

c.resource_json = json.dumps(c.resource)

except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % id)
return render('dataviewer/{type}.html'.format(type=preview_type))
else:
return render(plugin.preview_template(context, data_dict))
56 changes: 56 additions & 0 deletions ckan/lib/datapreview.py
@@ -0,0 +1,56 @@
# coding=UTF-8

"""Data previewer functions
Functions and data structures that are needed for the ckan data preview.
"""

import urlparse

import pylons.config as config

import ckan.plugins as p

DEFAULT_DIRECT_EMBED = ['png', 'jpg', 'gif']
DEFAULT_LOADABLE_IFRAME = ['html', 'htm', 'rdf+xml', 'owl+xml', 'xml', 'n3', 'n-triples', 'turtle', 'plain', 'atom', 'rss', 'txt']


def compare_domains(urls):
''' Return True if the domains of the provided are the same.
'''
first_domain = None
for url in urls:
# all urls are interpreted as absolute urls,
# except for urls that start with a /
if not urlparse.urlparse(url).scheme and not url.startswith('/'):
url = '//' + url
parsed = urlparse.urlparse(url.lower(), 'http')
domain = (parsed.scheme, parsed.hostname, parsed.port)

if not first_domain:
first_domain = domain
continue
if first_domain != domain:
return False
return True


def resource_is_on_same_domain(data_dict):
# compare CKAN domain and resource URL
ckan_url = config.get('ckan.site_url', '//localhost:5000')
resource_url = data_dict['resource']['url']

return compare_domains([ckan_url, resource_url])


def can_be_previewed(data_dict):
'''
Determines whether there is an extension that can preview the resource.
:param data_dict: contains a resource and package dict.
The resource dict has to have a value for ``on_same_domain``
:type data_dict: dictionary
'''
data_dict['resource']['on_same_domain'] = resource_is_on_same_domain(data_dict)
plugins = p.PluginImplementations(p.IResourcePreview)
return any(plugin.can_preview(data_dict) for plugin in plugins)
49 changes: 27 additions & 22 deletions ckan/lib/helpers.py
Expand Up @@ -38,6 +38,7 @@
import ckan.lib.fanstatic_resources as fanstatic_resources
import ckan.model as model
import ckan.lib.formatters as formatters
import ckan.lib.datapreview as datapreview

get_available_locales = i18n.get_available_locales
get_locales_dict = i18n.get_locales_dict
Expand Down Expand Up @@ -1162,13 +1163,13 @@ def urls_for_resource(resource):
''' Returns a list of urls for the resource specified. If the resource
is a group or has dependencies then there can be multiple urls.
NOTE: This is for special situations only and is not the way to generaly
NOTE: This is for special situations only and is not the way to generally
include resources. It is advised not to use this function.'''
r = getattr(fanstatic_resources, resource)
resources = list(r.resources)
core = fanstatic_resources.fanstatic_extensions.core
f = core.get_needed()
lib = resources[0].library
lib = r.library
root_path = f.library_url(lib)

resources = core.sort_resources(resources)
Expand Down Expand Up @@ -1373,39 +1374,43 @@ def format_resource_items(items):

def resource_preview(resource, pkg_id):
'''
Returns a rendered snippet for a embeded resource preview.
Returns a rendered snippet for a embedded resource 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, recline or a pdf preview.
embeds a web page, recline or a pdf preview.
'''

DIRECT_EMBEDS = ['png', 'jpg', 'gif']
LOADABLE = ['html', 'htm', 'rdf+xml', 'owl+xml', 'xml', 'n3',
'n-triples', 'turtle', 'plain', 'atom', 'tsv', 'rss',
'txt', 'json']
PDF = ['pdf', 'x-pdf', 'acrobat', 'vnd.pdf']

format_lower = resource['format'].lower()
directly = False
url = ''

if resource.get('datastore_active') or format_lower in ['csv', 'xls', 'tsv']:
url = url_for(controller='package', action='resource_datapreview',
resource_id=resource['id'], preview_type='recline', id=pkg_id, qualified=True)
elif format_lower in PDF:
url = url_for(controller='package', action='resource_datapreview',
resource_id=resource['id'], preview_type='pdf', id=pkg_id, qualified=True)
elif format_lower == 'jsonp':
data_dict = {'resource': resource, 'package': c.package}

if not resource['url']:
log.info('No url for resource {0} defined.'.format(resource['id']))
return snippet(
"dataviewer/snippets/no_preview.html",
resource_type=format_lower,
reason='No valid resource url has been defined.'
)
direct_embed = config.get('ckan.preview.direct', '').split()
if not direct_embed:
direct_embed = datapreview.DEFAULT_DIRECT_EMBED
loadable_in_iframe = config.get('ckan.preview.loadable', '').split()
if not loadable_in_iframe:
loadable_in_iframe = datapreview.DEFAULT_LOADABLE_IFRAME

if datapreview.can_be_previewed(data_dict):
url = url_for(controller='package', action='resource_datapreview',
resource_id=resource['id'], preview_type='json', id=pkg_id, qualified=True)
elif format_lower in LOADABLE:
url = resource['url']
elif format_lower in DIRECT_EMBEDS:
resource_id=resource['id'], id=pkg_id, qualified=True)
elif format_lower in direct_embed:
directly = True
url = resource['url']
elif format_lower in loadable_in_iframe:
url = resource['url']
else:
log.info('No preview handler for resource type {0}'.format(resource['format']))
log.info('No preview handler for resource type {0}'.format(format_lower))
return snippet(
"dataviewer/snippets/no_preview.html",
resource_type=format_lower
Expand Down
33 changes: 33 additions & 0 deletions ckan/plugins/interfaces.py
Expand Up @@ -14,6 +14,7 @@
'IPackageController', 'IPluginObserver',
'IConfigurable', 'IConfigurer',
'IActions', 'IResourceUrlChange', 'IDatasetForm',
'IResourcePreview',
'IGroupForm',
'ITagController',
'ITemplateHelpers',
Expand Down Expand Up @@ -192,6 +193,38 @@ def notify(self, resource):
pass


class IResourcePreview(Interface):
"""
Hook into the resource previews in helpers.py. This lets you
create custom previews for example for xml files.
"""

def can_preview(self, data_dict):
'''
Return True if the extension can preview the resource. The ``data_dict``
contains the resource and the package.
Make sure you also make sure to ckeck 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.
'''

def setup_template_variables(self, context, data_dict):
'''
Add variables to c just prior to the template being rendered.
The ``data_dict`` contains the resource and the package.
Change the url to a proxied domain if necessary.
'''

def preview_template(self, context, data_dict):
'''
Returns a string representing the location of the template to be
rendered for the read page.
The ``data_dict`` contains the resource and the package.
'''


class ITagController(Interface):
'''
Hook into the Tag controller. These will usually be called just before
Expand Down
12 changes: 11 additions & 1 deletion ckan/public/base/css/main.css
Expand Up @@ -5014,6 +5014,13 @@ ol.media-grid:after {
label {
font-weight: bold;
}
.btn-rounded {
-webkit-border-radius: 100px;
-moz-border-radius: 100px;
border-radius: 100px;
padding-left: 15px;
padding-right: 15px;
}
label {
cursor: pointer;
font-size: 14px;
Expand Down Expand Up @@ -6840,7 +6847,7 @@ header.masthead .navigation ul.unstyled li a {
-moz-border-radius: 3px;
border-radius: 3px;
}
header.masthead .navigation ul.unstyled li a.active {
header.masthead .navigation ul.unstyled li.active a {
background-color: #0d6581;
box-shadow: 0 -1px 0 #084152, 0 1px 0 #26758e;
}
Expand Down Expand Up @@ -7297,6 +7304,9 @@ header.masthead .debug {
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.activity .load-less {
margin-bottom: 15px;
}
.popover .about {
margin-bottom: 10px;
}
Expand Down
3 changes: 0 additions & 3 deletions ckan/public/base/datapreview/preview_json.min.js

This file was deleted.

8 changes: 4 additions & 4 deletions ckan/public/base/less/masthead.less
Expand Up @@ -72,10 +72,10 @@ header.masthead {
font-weight: bold;
padding: 4px 10px;
.border-radius(3px);
&.active {
background-color: @menuActiveBg;
box-shadow: 0 -1px 0 @menuActiveLo, 0 1px 0 @menuActiveHi;
}
}
&.active a {
background-color: @menuActiveBg;
box-shadow: 0 -1px 0 @menuActiveLo, 0 1px 0 @menuActiveHi;
}
}
}
Expand Down
16 changes: 0 additions & 16 deletions ckan/templates/dataviewer/json.html

This file was deleted.

14 changes: 9 additions & 5 deletions ckan/templates/dataviewer/snippets/no_preview.html
@@ -1,12 +1,16 @@
<div class="module-content">
<div class="alert alert-block">
<h4 class="pull-left">{{ _('This resource cannot be previewed.') }} </h4>
<button class="btn btn-link btn-mini" data-toggle="collapse" data-target="#demo">
<i class="icon-info-sign"></i> More details...
<button class="btn btn-link btn-mini" data-toggle="collapse" data-target="#details">
<i class="icon-info-sign"></i> {{ _('More details...') }}
</button>
<div id="demo" class="collapse">
{% set t = '<code>'|safe + resource_type + '</code>'|safe %}
{{ gettext('No handler defined for data type: %(type)s.', type=t) }}
<div id="details" class="collapse">
{% if reason %}
{{_(reason)}}
{% else %}
{% set t = '<code>'|safe + resource_type + '</code>'|safe %}
{{ gettext('No handler defined for data type: %(type)s.', type=t) }}
{% endif %}
</div>
</div>
</div>

0 comments on commit fc42fba

Please sign in to comment.