diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py
index 3a89d7b3c1b..ce75e0fdd24 100644
--- a/ckan/controllers/package.py
+++ b/ckan/controllers/package.py
@@ -200,13 +200,23 @@ def _content_type_for_format(self, fmt):
it accurately. TextTemplate must be used for non-xml templates
whilst all that are some sort of XML should use MarkupTemplate.
"""
- from genshi.template import MarkupTemplate, TextTemplate
+ from genshi.template import MarkupTemplate
+ from genshi.template.text import NewTextTemplate
+
types = {
- "html": ("text/html; charset=utf-8", MarkupTemplate),
- "rdf" : ("application/rdf+xml; charset=utf-8", MarkupTemplate),
+ "html": ("text/html; charset=utf-8", MarkupTemplate, 'html'),
+ "rdf" : ("application/rdf+xml; charset=utf-8", MarkupTemplate, 'rdf'),
+ "n3" : ("text/n3; charset=utf-8", NewTextTemplate, 'n3'),
+ "application/rdf+xml" : ("application/rdf+xml; charset=utf-8", MarkupTemplate,'rdf'),
+ "text/n3": ("text/n3; charset=utf-8", NewTextTemplate, 'n3'),
}
+ # Check the accept header first
+ accept = request.headers.get('Accept', '')
+ if accept and accept in types:
+ return types[accept][0], types[accept][2], types[accept][1]
+
if fmt in types:
- return types[fmt][0], fmt, types[fmt][1]
+ return types[fmt][0], types[fmt][2], types[fmt][1]
return None, "html", (types["html"][1])
@@ -219,6 +229,9 @@ def read(self, id, format='html'):
ctype = "text/html; charset=utf-8"
id = "%s.%s" % (id, format)
format = 'html'
+ else:
+ format = extension
+
response.headers['Content-Type'] = ctype
package_type = self._get_package_type(id.split('@')[0])
@@ -268,14 +281,6 @@ def read(self, id, format='html'):
ckan.logic.action.get.package_activity_list_html(context,
{'id': c.current_package_id})
- if config.get('rdf_packages'):
- accept_header = request.headers.get('Accept', '*/*')
- for content_type, exts in negotiate(autoneg_cfg, accept_header):
- if "html" not in exts:
- rdf_url = '%s%s.%s' % (config['rdf_packages'], c.pkg.id, exts[0])
- redirect(rdf_url, code=303)
- break
-
PackageSaver().render_package(c.pkg_dict, context)
template = self._read_template( package_type )
@@ -529,22 +534,11 @@ def _get_package_type(self, id):
based on the package's type name (type). The plugin found
will be returned, or None if there is no plugin associated with
the type.
-
- Uses a minimal context to do so. The main use of this method
- is for figuring out which plugin to delegate to.
-
- aborts if an exception is raised.
"""
- context = {'model': model, 'session': model.Session,
- 'user': c.user or c.author}
- try:
- data = get_action('package_show')(context, {'id': id})
- except NotFound:
- abort(404, _('Dataset not found'))
- except NotAuthorized:
- abort(401, _('Unauthorized to read package %s') % id)
-
- return data.get('type', 'package')
+ pkg = model.Package.get(id)
+ if pkg:
+ return pkg.type or 'package'
+ return None
def _save_new(self, context, package_type=None):
from ckan.lib.search import SearchIndexError
diff --git a/ckan/controllers/storage.py b/ckan/controllers/storage.py
index 56218d945b5..34af131086c 100644
--- a/ckan/controllers/storage.py
+++ b/ckan/controllers/storage.py
@@ -48,6 +48,9 @@ def fix_stupid_pylons_encoding(data):
def create_pairtree_marker(folder):
""" Creates the pairtree marker for tests if it doesn't exist """
+ if not folder[:-1] == '/':
+ folder = folder + '/'
+
directory = os.path.dirname(folder)
if not os.path.exists(directory):
os.makedirs(directory)
diff --git a/ckan/lib/base.py b/ckan/lib/base.py
index 2c346d8acf8..61ffa2e52a4 100644
--- a/ckan/lib/base.py
+++ b/ckan/lib/base.py
@@ -16,6 +16,7 @@
from pylons.i18n import _, ungettext, N_, gettext
from pylons.templating import cached_template, pylons_globals
from genshi.template import MarkupTemplate
+from genshi.template.text import NewTextTemplate
from webhelpers.html import literal
import ckan
@@ -39,14 +40,14 @@ def abort(status_code=None, detail='', headers=None, comment=None):
# #1267 Convert detail to plain text, since WebOb 0.9.7.1 (which comes
# with Lucid) causes an exception when unicode is received.
detail = detail.encode('utf8')
- return _abort(status_code=status_code,
+ return _abort(status_code=status_code,
detail=detail,
- headers=headers,
+ headers=headers,
comment=comment)
-def render(template_name, extra_vars=None, cache_key=None, cache_type=None,
+def render(template_name, extra_vars=None, cache_key=None, cache_type=None,
cache_expire=None, method='xhtml', loader_class=MarkupTemplate):
-
+
def render_template():
globs = extra_vars or {}
globs.update(pylons_globals())
@@ -60,21 +61,23 @@ def render_template():
template = globs['app_globals'].genshi_loader.load(template_name,
cls=loader_class)
stream = template.generate(**globs)
-
+
for item in PluginImplementations(IGenshiStreamFilter):
stream = item.filter(stream)
-
+
+ if loader_class == NewTextTemplate:
+ return literal(stream.render(method="text", encoding=None))
return literal(stream.render(method=method, encoding=None, strip_whitespace=False))
-
+
if 'Pragma' in response.headers:
del response.headers["Pragma"]
if cache_key is not None or cache_type is not None:
- response.headers["Cache-Control"] = "public"
-
+ response.headers["Cache-Control"] = "public"
+
if cache_expire is not None:
response.headers["Cache-Control"] = "max-age=%s, must-revalidate" % cache_expire
-
- return cached_template(template_name, render_template, cache_key=cache_key,
+
+ return cached_template(template_name, render_template, cache_key=cache_key,
cache_type=cache_type, cache_expire=cache_expire)
#, ns_options=('method'), method=method)
@@ -136,7 +139,7 @@ def __call__(self, environ, start_response):
"""Invoke the Controller"""
# WSGIController.__call__ dispatches to the Controller method
# the request is routed to. This routing information is
- # available in environ['pylons.routes_dict']
+ # available in environ['pylons.routes_dict']
try:
return WSGIController.__call__(self, environ, start_response)
finally:
@@ -260,13 +263,13 @@ def _get_timing_cache_path(self):
return path
@classmethod
- def _get_user_editable_groups(cls):
+ def _get_user_editable_groups(cls):
if not hasattr(c, 'user'):
c.user = model.PSEUDO_USER__VISITOR
import ckan.authz # Todo: Move import to top of this file?
- groups = ckan.authz.Authorizer.authorized_query(c.user, model.Group,
+ groups = ckan.authz.Authorizer.authorized_query(c.user, model.Group,
action=model.Action.EDIT).all()
- return [g for g in groups if g.state==model.State.ACTIVE]
+ return [g for g in groups if g.state==model.State.ACTIVE]
def _get_package_dict(self, *args, **kwds):
import ckan.forms
@@ -325,7 +328,7 @@ def _handle_update_of_authz(self, domain_object):
update_or_add = 'add'
else:
user_or_authgroup = None
- update_or_add = None
+ update_or_add = None
# Work out what role checkboxes are checked or unchecked
checked_roles = [ box_id for (box_id, value) in request.params.items() \
diff --git a/ckan/templates/layout_base.html b/ckan/templates/layout_base.html
index 51fa8088c1c..61ba69c2f14 100644
--- a/ckan/templates/layout_base.html
+++ b/ckan/templates/layout_base.html
@@ -31,7 +31,7 @@
-
+
diff --git a/ckan/templates/package/read.n3 b/ckan/templates/package/read.n3
new file mode 100644
index 00000000000..911e83229cb
--- /dev/null
+++ b/ckan/templates/package/read.n3
@@ -0,0 +1,44 @@
+@prefix : .
+@prefix dcat: .
+@prefix dct: .
+@prefix foaf: .
+@prefix owl: .
+@prefix rdf: .
+
+<${ g.site_url + h.url_for(controller='package',action='read',id=c.pkg_dict['name'])}> a dcat:Dataset;
+ dct:description "${c.pkg_dict['notes']}";
+ dct:identifier "${c.pkg_dict['name']}";
+ dct:relation [
+ rdf:value "";
+ :label "change_note" ],
+ [
+ rdf:value "";
+ :label "definition_note" ],
+ [
+ rdf:value "";
+ :label "editorial_note" ],
+ [
+ rdf:value "";
+ :label "example_note" ],
+ [
+ rdf:value "";
+ :label "history_note" ],
+ [
+ rdf:value "";
+ :label "scope_note" ],
+ [
+ rdf:value "";
+ :label "skos_note" ],
+ [
+ rdf:value "";
+ :label "temporal_granularity" ],
+ [
+ rdf:value "";
+ :label "type_of_dataset" ],
+ [
+ rdf:value "";
+ :label "update_frequency" ];
+ dct:title "${c.pkg_dict['title']}";
+ :label "${c.pkg_dict['name']}";
+ = ;
+ foaf:homepage .
\ No newline at end of file
diff --git a/ckan/tests/functional/test_autoneg.py b/ckan/tests/functional/test_autoneg.py
index 7732f7cb055..0d1181498a1 100644
--- a/ckan/tests/functional/test_autoneg.py
+++ b/ckan/tests/functional/test_autoneg.py
@@ -59,20 +59,18 @@ def test_html_rdf(self):
assert response.status == 200, response.status
content_type = response.header("Content-Type")
assert "html" in content_type, content_type
-
+
def test_rdfxml(self):
url = url_for(controller='package', action='read', id='annakarenina')
response = self.app.get(url, headers={"Accept": "application/rdf+xml"})
- assert response.status == 303, response.status
- location = response.header("Location")
- assert location.endswith(".rdf"), location
- assert location.startswith('http://test.com/package/'), location
+ assert response.status == 200, response.status
+ ctype = response.header("Content-Type")
+ assert 'application/rdf+xml' in ctype, ctype
- def test_turtle(self):
+ def test_n3(self):
url = url_for(controller='package', action='read', id='annakarenina')
- response = self.app.get(url, headers={"Accept": "application/turtle"})
- assert response.status == 303, response.status
- location = response.header("Location")
- assert location.endswith(".ttl"), location
- assert location.startswith('http://test.com/package/'), location
+ response = self.app.get(url, headers={"Accept": "text/n3"})
+ assert response.status == 200, response.status
+ ctype = response.header("Content-Type")
+ assert 'text/n3' in ctype, ctype
diff --git a/ckan/tests/functional/test_package.py b/ckan/tests/functional/test_package.py
index d03979b3942..fe77095af5f 100644
--- a/ckan/tests/functional/test_package.py
+++ b/ckan/tests/functional/test_package.py
@@ -375,7 +375,7 @@ def test_read_plugin_hook(self):
# existed before. I don't know if this is a problem? I expect it
# can be fixed by allowing the package to be passed in to the plugin,
# either via the function argument, or adding it to the c object.
- assert plugin.calls['read'] == 2, plugin.calls
+ assert plugin.calls['read'] == 1, plugin.calls
plugins.unload(plugin)
def test_resource_list(self):
diff --git a/ckan/tests/functional/test_storage.py b/ckan/tests/functional/test_storage.py
index 1d6168b512c..27182b55e0c 100644
--- a/ckan/tests/functional/test_storage.py
+++ b/ckan/tests/functional/test_storage.py
@@ -18,6 +18,7 @@ def setup_class(cls):
config.local_conf['ofs.impl'] = 'pairtree'
config.local_conf['ckan.storage.bucket'] = 'ckantest'
config.local_conf['ofs.storage_dir'] = '/tmp/ckan-test-ckanext-storage'
+
create_pairtree_marker( config.local_conf['ofs.storage_dir'] )
wsgiapp = make_app(config.global_conf, **config.local_conf)
cls.app = paste.fixture.TestApp(wsgiapp)
diff --git a/doc/linked-data-and-rdf.rst b/doc/linked-data-and-rdf.rst
index 19442ef73f8..c236ba6496f 100644
--- a/doc/linked-data-and-rdf.rst
+++ b/doc/linked-data-and-rdf.rst
@@ -17,9 +17,7 @@ In CKAN >= 1.6.1, basic RDF support will be available directly in core.
Configuration
-------------
-.. todo:: fill this in (imagine that config instructions for extension will be
- in extension but for >= 1.6.1 we have stuff in core)
-
+When using the built-in RDF support (CKAN >= 1.6.1) there is no configuration required. By default requests for RDF data will return the RDF generated from the built-in 'packages/read.rdf' template, which can be overridden using the extra-templates directive.
Accessing Linked Data
=====================
@@ -31,6 +29,11 @@ example::
curl -L -H "Accept: application/rdf+xml" http://thedatahub.org/dataset/gold-prices
curl -L -H "Accept: text/n3" http://thedatahub.org/dataset/gold-prices
+An alternative method of retrieving the data is to add .rdf to the name of the dataset to download::
+
+ curl -L http://thedatahub.org/dataset/gold-prices.rdf
+ curl -L http://thedatahub.org/dataset/gold-prices.n3
+
Schema Mapping
==============