diff --git a/ckan/config/routing.py b/ckan/config/routing.py index 02eb3a3d92c..0e1f2f27277 100644 --- a/ckan/config/routing.py +++ b/ckan/config/routing.py @@ -158,8 +158,10 @@ def make_map(): ##map.connect('/package/edit/{id}', controller='package_formalchemy', action='edit') with SubMapper(map, controller='related') as m: + m.connect('related_new', '/dataset/{id}/related/new', action='new') + m.connect('related_edit', '/dataset/{id}/related/edit/{related_id}', + action='edit') m.connect('related_list', '/dataset/{id}/related', action='list') - m.connect('related_read', '/dataset/{id}/related/{related_id}', action='read') with SubMapper(map, controller='package') as m: m.connect('/dataset', action='search') diff --git a/ckan/controllers/related.py b/ckan/controllers/related.py index f1ea1b70ac4..3a5aac03116 100644 --- a/ckan/controllers/related.py +++ b/ckan/controllers/related.py @@ -1,14 +1,23 @@ - - import ckan.model as model import ckan.logic as logic import ckan.lib.base as base import ckan.lib.helpers as h +import ckan.lib.navl.dictization_functions as df + +import pylons.i18n as i18n c = base.c +_ = i18n._ class RelatedController(base.BaseController): + + def new(self,id): + return self._edit_or_new(id, None, False) + + def edit(self,id,related_id): + return self._edit_or_new(id, related_id, True) + def list(self, id): context = {'model': model, 'session': model.Session, @@ -44,3 +53,85 @@ def list(self, id): return base.render( "package/related_list.html") + + def _edit_or_new(self, id, related_id, is_edit): + """ + Edit and New were too similar and so I've put the code together + and try and do as much up front as possible. + """ + context = {'model': model, 'session': model.Session, + 'user': c.user or c.author, 'for_view': True} + data_dict = {} + + if is_edit: + tpl = 'related/edit.html' + auth_name = 'related_update' + auth_dict = {'id': related_id} + action_name = 'related_update' + + try: + related = logic.get_action('related_show')(context, {'id': related_id}) + except logic.NotFound: + base.abort(404, _('Related item not found')) + else: + tpl = 'related/new.html' + auth_name = 'related_create' + auth_dict = {} + action_name = 'related_create' + + try: + logic.check_access(auth_name, context, auth_dict) + except logic.NotAuthorized: + base.abort(401, base._('Not authorized')) + + try: + package = logic.get_action('package_show')(context, {'id': id}) + except logic.NotFound: + base.abort(404, _('Package not found')) + + data, errors, error_summary = {}, {}, {} + + if base.request.method == "POST": + try: + data = logic.clean_dict( + df.unflatten( + logic.tuplize_dict( + logic.parse_params(base.request.params) + ))) + + if is_edit: + data['id'] = related_id + else: + data['dataset_id'] = id + data['owner_id'] = c.userobj.id + + related = logic.get_action(action_name)(context, data) + + if not is_edit: + h.flash_success(_("Related item was successfully created")) + else: + h.flash_success(_("Related item was successfully updated")) + + h.redirect_to(controller='related', + action='list', + id=package['name']) + except df.DataError: + base.abort(400, _(u'Integrity Error')) + except logic.ValidationError, e: + errors = e.error_dict + error_summary = e.error_summary + else: + if is_edit: + data = related + + c.types = ( + ("Application", "application"), + ("Idea", "idea"), + ("News Article", "news_article"), + ("Paper", "paper"), + ("Visualization", "visualization") + ) + + vars = {'data': data, 'errors': errors, 'error_summary': error_summary} + c.form = base.render("related/edit_form.html", extra_vars=vars) + return base.render(tpl) \ No newline at end of file diff --git a/ckan/logic/schema.py b/ckan/logic/schema.py index 61a4b4abe2c..628cf9367f0 100644 --- a/ckan/logic/schema.py +++ b/ckan/logic/schema.py @@ -243,7 +243,7 @@ def default_related_schema(): 'description': [ignore_missing, unicode], 'type': [not_empty, unicode], 'image_url': [ignore_missing, unicode], - 'url': [ignore_missing, unicode], + 'url': [not_empty, unicode], 'owner_id': [not_empty, unicode], 'created': [ignore], } diff --git a/ckan/templates/related/edit.html b/ckan/templates/related/edit.html new file mode 100644 index 00000000000..08c05ee1f7c --- /dev/null +++ b/ckan/templates/related/edit.html @@ -0,0 +1,39 @@ + + + + + + + + Edit a related item + + +
+ ${h.snippet('package/new_breadcrumb.html')} +
+ +
+
+
+ ${c.form} +
+
+
+ +
+
+

What are related items?

+
+

Related Media is any app, article, visualisation or idea related to this dataset. For example, it could be a custom visualisation, pictograph or bar chart, an app using all or part of the data or even a news story that references this dataset.

+
+
+
+ + + + + diff --git a/ckan/templates/related/edit_form.html b/ckan/templates/related/edit_form.html new file mode 100644 index 00000000000..aded0a9837e --- /dev/null +++ b/ckan/templates/related/edit_form.html @@ -0,0 +1,63 @@ +
+
+

Errors in form

+

The form contains invalid entries:

+
    +
  • ${"%s: %s" % (key if not key=='Name' else 'URL', error)}
  • +
+
+ +
+ +
+ + ${error} +
+
+ +
+ +
+ + ${error} +
+
+ +
+ +
+ + You can use Markdown editing here + ${error} +
+
+ +
+ +
+ + ${error} +
+
+ +
+ +
+ + ${error} +
+
+ +
+ Cancel + +
+
\ No newline at end of file diff --git a/ckan/templates/related/new.html b/ckan/templates/related/new.html new file mode 100644 index 00000000000..f5820f4bf7c --- /dev/null +++ b/ckan/templates/related/new.html @@ -0,0 +1,37 @@ + + + + + + + + Create a related item + + +
+ ${h.snippet('package/new_breadcrumb.html')} +
+ +
+
+
+ ${c.form} +
+
+
+ +
+
+

What are related items?

+
+

Related Media is any app, article, visualisation or idea related to this dataset. For example, it could be a custom visualisation, pictograph or bar chart, an app using all or part of the data or even a news story that references this dataset.

+
+
+
+ + + diff --git a/ckan/tests/functional/test_related.py b/ckan/tests/functional/test_related.py index 1827038eea8..ef27d37e9f6 100644 --- a/ckan/tests/functional/test_related.py +++ b/ckan/tests/functional/test_related.py @@ -3,7 +3,59 @@ import ckan.tests as tests import ckan.model as model import ckan.logic as logic -import ckan.tests.functional.api.base as base +import ckan.lib.helpers as h +import ckan.tests.functional.base as base +import ckan.tests.functional.api.base as apibase + + +class TestRelatedUI(base.FunctionalTestCase): + @classmethod + def setup_class(self): + model.Session.remove() + tests.CreateTestData.create() + + @classmethod + def teardown_class(self): + model.repo.rebuild_db() + + def test_related_new(self): + offset = h.url_for(controller='related', + action='new', id='warandpeace') + res = self.app.get(offset, status=200, + extra_environ={"REMOTE_USER": "testsysadmin"}) + assert 'URL' in res, "URL missing in response text" + assert 'Title' in res, "Title missing in response text" + + data = { + "title": "testing_create", + "url": u"http://ckan.org/feed/", + } + res = self.app.post(offset, params=data, + status=[200,302], + extra_environ={"REMOTE_USER": "testsysadmin"}) + + def test_related_new_missing(self): + offset = h.url_for(controller='related', + action='new', id='non-existent dataset') + res = self.app.get(offset, status=404, + extra_environ={"REMOTE_USER": "testsysadmin"}) + + def test_related_new_fail(self): + offset = h.url_for(controller='related', + action='new', id='warandpeace') + res = self.app.get(offset, status=200, + extra_environ={"REMOTE_USER": "testsysadmin"}) + assert 'URL' in res, "URL missing in response text" + assert 'Title' in res, "Title missing in response text" + + data = { + "title": "testing_create", + } + res = self.app.post(offset, params=data, + status=[200,302], + extra_environ={"REMOTE_USER": "testsysadmin"}) + assert 'error' in res, res + class TestRelated: @@ -148,7 +200,7 @@ def test_related_list(self): result = logic.get_action('related_list')(context,data_dict) assert len(result) == len(p.related) -class TestRelatedActionAPI(base.BaseModelApiTestCase): +class TestRelatedActionAPI(apibase.BaseModelApiTestCase): @classmethod def setup_class(cls):