diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7cf68d846c0..21a87c73948 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,26 @@ CKAN CHANGELOG ++++++++++++++ +v1.7.1 2012-06-20 +================= + +Minor: +* Documentation enhancements regarding install and extensions (#2505) +* Home page and search results speed improvements (#2402,#2403) +* I18n: Added Greek translation and updated other ones (#2506) + +Bug fixes: +* UI fixes (#2507) +* Fixes for i18n login and logout issues (#2497) +* Date on add/edit resource breaks if offset is specified (#2383) +* Fix in organizations read page (#2509) +* Add synchronous_search plugin to deployment.ini template (#2521) +* Inconsistent language on license dropdown (#2575) +* Fix bug in translating lists in multilingual plugin +* Group autocomplete doesn't work with multiple words (#2373) +* Other minor fixes + + v1.7 2012-05-09 =============== diff --git a/ckan/config/environment.py b/ckan/config/environment.py index 46ff3073d94..cc56477b79f 100644 --- a/ckan/config/environment.py +++ b/ckan/config/environment.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Pylons environment configuration""" import os import logging @@ -176,6 +177,89 @@ def template_loaded(template): config['pylons.app_globals'].genshi_loader = TemplateLoader( template_paths, auto_reload=True, callback=template_loaded) + ################################################################# + # # + # HORRIBLE GENSHI HACK # + # # + ################################################################# + # # + # Genshi does strange things to get stuff out of the template # + # variables. This stops it from handling properties in the # + # correct way as it returns the property rather than the actual # + # value of the property. # + # # + # By overriding lookup_attr() in the LookupBase class we are # + # able to get the required behaviour. Using @property allows # + # us to move functionality out of templates whilst maintaining # + # backwards compatability. # + # # + ################################################################# + + + + ''' + This code is based on Genshi code + + Copyright © 2006-2012 Edgewall Software + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ''' + from genshi.template.eval import LookupBase + + @classmethod + def genshi_lookup_attr(cls, obj, key): + __traceback_hide__ = True + try: + val = getattr(obj, key) + except AttributeError: + if hasattr(obj.__class__, key): + raise + else: + try: + val = obj[key] + except (KeyError, TypeError): + val = cls.undefined(key, owner=obj) + if isinstance(val, property): + val = val.fget() + return val + + setattr(LookupBase, 'lookup_attr', genshi_lookup_attr) + del genshi_lookup_attr + del LookupBase + + ################################################################# + # # + # END OF GENSHI HACK # + # # + ################################################################# + # CONFIGURATION OPTIONS HERE (note: all config options will override # any Pylons config options) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index 21c5bab155c..8bb6119f513 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -13,11 +13,13 @@ from ckan.logic import tuplize_dict, clean_dict, parse_params import ckan.forms import ckan.logic.action.get +import ckan.lib.search as search from ckan.lib.plugins import lookup_group_plugin log = logging.getLogger(__name__) + class GroupController(BaseController): ## hooks for subclasses @@ -34,12 +36,13 @@ def _db_to_form_schema(self, group_type=None): return lookup_group_plugin(group_type).form_to_db_schema() def _setup_template_variables(self, context, data_dict, group_type=None): - return lookup_group_plugin(group_type).setup_template_variables(context,data_dict) + return lookup_group_plugin(group_type).\ + setup_template_variables(context, data_dict) - def _new_template(self,group_type): + def _new_template(self, group_type): return lookup_group_plugin(group_type).new_template() - def _index_template(self,group_type): + def _index_template(self, group_type): return lookup_group_plugin(group_type).index_template() def _read_template(self, group_type): @@ -67,7 +70,6 @@ def _guess_group_type(self, expecting_name=False): return gt - def index(self): group_type = self._guess_group_type() @@ -90,8 +92,7 @@ def index(self): url=h.pager_url, items_per_page=20 ) - return render( self._index_template(group_type) ) - + return render(self._index_template(group_type)) def read(self, id): from ckan.lib.search import SearchError @@ -101,7 +102,8 @@ def read(self, id): 'schema': self._form_to_db_schema(group_type=group_type), 'for_view': True} data_dict = {'id': id} - q = c.q = request.params.get('q', '') # unicode format (decoded from utf8) + # unicode format (decoded from utf8) + q = c.q = request.params.get('q', '') try: c.group_dict = get_action('group_show')(context, data_dict) @@ -115,10 +117,12 @@ def read(self, id): q += ' groups: "%s"' % c.group_dict.get('name') try: - description_formatted = ckan.misc.MarkdownFormat().to_html(c.group_dict.get('description','')) + description_formatted = ckan.misc.MarkdownFormat().to_html( + c.group_dict.get('description', '')) c.description_formatted = genshi.HTML(description_formatted) except Exception, e: - error_msg = "%s" % _("Cannot render description") + error_msg = "%s" %\ + _("Cannot render description") c.description_formatted = genshi.HTML(error_msg) c.group_admins = self.authorizer.get_admins(c.group) @@ -132,12 +136,14 @@ def read(self, id): abort(400, ('"page" parameter must be an integer')) # most search operations should reset the page counter: - params_nopage = [(k, v) for k,v in request.params.items() if k != 'page'] + params_nopage = [(k, v) for k, v in request.params.items() + if k != 'page'] def search_url(params): - url = h.url_for(controller='group', action='read', id=c.group_dict.get('name')) - params = [(k, v.encode('utf-8') if isinstance(v, basestring) else str(v)) \ - for k, v in params] + url = h.url_for(controller='group', action='read', + id=c.group_dict.get('name')) + params = [(k, v.encode('utf-8') if isinstance(v, basestring) + else str(v)) for k, v in params] return url + u'?' + urlencode(params) def drill_down_url(**by): @@ -171,22 +177,21 @@ def pager_url(q=None, page=None): else: search_extras[param] = value - fq = 'capacity:"public"' if (c.userobj and c.group and c.userobj.is_in_group(c.group)): fq = '' context['ignore_capacity_check'] = True data_dict = { - 'q':q, - 'fq':fq, - 'facet.field':g.facets, - 'rows':limit, - 'start':(page-1)*limit, - 'extras':search_extras + 'q': q, + 'fq': fq, + 'facet.field': g.facets, + 'rows': limit, + 'start': (page - 1) * limit, + 'extras': search_extras } - query = get_action('package_search')(context,data_dict) + query = get_action('package_search')(context, data_dict) c.page = h.Page( collection=query['results'], @@ -207,10 +212,10 @@ def pager_url(q=None, page=None): # Add the group's activity stream (already rendered to HTML) to the # template context for the group/read.html template to retrieve later. c.group_activity_stream = \ - ckan.logic.action.get.group_activity_list_html(context, - {'id': c.group_dict['id']}) + get_action('group_activity_list_html')(context, + {'id': c.group_dict['id']}) - return render( self._read_template(c.group_dict['type']) ) + return render(self._read_template(c.group_dict['type'])) def new(self, data=None, errors=None, error_summary=None): group_type = self._guess_group_type(True) @@ -222,7 +227,7 @@ def new(self, data=None, errors=None, error_summary=None): 'save': 'save' in request.params, 'parent': request.params.get('parent', None)} try: - check_access('group_create',context) + check_access('group_create', context) except NotAuthorized: abort(401, _('Unauthorized to create a group')) @@ -234,8 +239,9 @@ def new(self, data=None, errors=None, error_summary=None): error_summary = error_summary or {} vars = {'data': data, 'errors': errors, 'error_summary': error_summary} - self._setup_template_variables(context,data) - c.form = render(self._group_form(group_type=group_type), extra_vars=vars) + self._setup_template_variables(context, data) + c.form = render(self._group_form(group_type=group_type), + extra_vars=vars) return render(self._new_template(group_type)) def edit(self, id, data=None, errors=None, error_summary=None): @@ -265,7 +271,7 @@ def edit(self, id, data=None, errors=None, error_summary=None): c.group = group try: - check_access('group_update',context) + check_access('group_update', context) except NotAuthorized, e: abort(401, _('User %r not authorized to edit %s') % (c.user, id)) @@ -281,10 +287,9 @@ def _get_group_type(self, id): Given the id of a group it determines the type of a group given a valid id/name for the group. """ - group = model.Group.get( id ) + group = model.Group.get(id) if not group: return None - return group.type def _save_new(self, context, group_type=None): @@ -297,7 +302,7 @@ def _save_new(self, context, group_type=None): group = get_action('group_create')(context, data_dict) # Redirect to the appropriate _read route for the type of group - h.redirect_to( group['type'] + '_read', id=group['name']) + h.redirect_to(group['type'] + '_read', id=group['name']) except NotAuthorized: abort(401, _('Unauthorized to read group %s') % '') except NotFound, e: @@ -309,6 +314,15 @@ def _save_new(self, context, group_type=None): error_summary = e.error_summary return self.new(data_dict, errors, error_summary) + def _force_reindex(self, grp): + ''' When the group name has changed, we need to force a reindex + of the datasets within the group, otherwise they will stop + appearing on the read page for the group (as they're connected via + the group name)''' + group = model.Group.get(grp['name']) + for dataset in group.active_packages().all(): + search.rebuild(dataset.name) + def _save_edit(self, id, context): try: data_dict = clean_dict(unflatten( @@ -316,6 +330,10 @@ def _save_edit(self, id, context): context['message'] = data_dict.get('log_message', '') data_dict['id'] = id group = get_action('group_update')(context, data_dict) + + if id != group['name']: + self._force_reindex(group) + h.redirect_to('%s_read' % str(group['type']), id=group['name']) except NotAuthorized: abort(401, _('Unauthorized to read group %s') % id) @@ -336,31 +354,34 @@ def authz(self, id): c.grouptitle = group.display_name try: - context = {'model':model,'user':c.user or c.author, 'group':group} - check_access('group_edit_permissions',context) + context = \ + {'model': model, 'user': c.user or c.author, 'group': group} + check_access('group_edit_permissions', context) c.authz_editable = True c.group = context['group'] except NotAuthorized: c.authz_editable = False if not c.authz_editable: - abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id)) + abort(401, + gettext('User %r not authorized to edit %s authorizations') % + (c.user, id)) roles = self._handle_update_of_authz(group) self._prepare_authz_info_for_render(roles) return render('group/authz.html') - def history(self, id): if 'diff' in request.params or 'selected1' in request.params: try: - params = {'id':request.params.getone('group_name'), - 'diff':request.params.getone('selected1'), - 'oldid':request.params.getone('selected2'), + params = {'id': request.params.getone('group_name'), + 'diff': request.params.getone('selected1'), + 'oldid': request.params.getone('selected2'), } except KeyError, e: - if dict(request.params).has_key('group_name'): + if 'group_name' in dict(request.params): id = request.params.getone('group_name') - c.error = _('Select two revisions before doing the comparison.') + c.error = \ + _('Select two revisions before doing the comparison.') else: params['diff_entity'] = 'group' h.redirect_to(controller='revision', action='diff', **params) @@ -371,7 +392,8 @@ def history(self, id): data_dict = {'id': id} try: c.group_dict = get_action('group_show')(context, data_dict) - c.group_revisions = get_action('group_revision_list')(context, data_dict) + c.group_revisions = get_action('group_revision_list')(context, + data_dict) #TODO: remove # Still necessary for the authz check in group/layout.html c.group = context['group'] @@ -386,13 +408,15 @@ def history(self, id): from webhelpers.feedgenerator import Atom1Feed feed = Atom1Feed( title=_(u'CKAN Group Revision History'), - link=h.url_for(controller='group', action='read', id=c.group_dict['name']), + link=h.url_for(controller='group', action='read', + id=c.group_dict['name']), description=_(u'Recent changes to CKAN Group: ') + - c.group_dict['display_name'], + c.group_dict['display_name'], language=unicode(get_lang()), ) for revision_dict in c.group_revisions: - revision_date = h.date_str_to_datetime(revision_dict['timestamp']) + revision_date = h.date_str_to_datetime( + revision_dict['timestamp']) try: dayHorizon = int(request.params.get('days')) except: @@ -401,10 +425,12 @@ def history(self, id): if dayAge >= dayHorizon: break if revision_dict['message']: - item_title = u'%s' % revision_dict['message'].split('\n')[0] + item_title = u'%s' % revision_dict['message'].\ + split('\n')[0] else: item_title = u'%s' % revision_dict['id'] - item_link = h.url_for(controller='revision', action='read', id=revision_dict['id']) + item_link = h.url_for(controller='revision', action='read', + id=revision_dict['id']) item_description = _('Log message: ') item_description += '%s' % (revision_dict['message'] or '') item_author_name = revision_dict['author'] @@ -418,7 +444,7 @@ def history(self, id): ) feed.content_type = 'application/atom+xml' return feed.writeString('utf-8') - return render( self._history_template(c.group_dict['type']) ) + return render(self._history_template(c.group_dict['type'])) def _render_edit_form(self, fs): # errors arrive in c.error and fs.errors diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py index ab0e9cd8cd3..874abc88685 100644 --- a/ckan/logic/action/create.py +++ b/ckan/logic/action/create.py @@ -178,16 +178,14 @@ def package_create_validate(context, data_dict): return data def resource_create(context, data_dict): - '''Add a resource to a dataset. + '''Appends a new resource to a datasets list of resources. - TODO: This function doesn't actually do anything yet. - - :param id: (optional) - :type id: string + :param package_id: id of package that the resource needs should be added to. + :type package_id: string + :param url: url of resource + :type url: string :param revision_id: (optional) :type revisiion_id: string - :param url: (optional) - :type url: string :param description: (optional) :type description: string :param format: (optional) @@ -224,9 +222,24 @@ def resource_create(context, data_dict): model = context['model'] user = context['user'] - data, errors = _validate(data_dict, - ckan.logic.schema.default_resource_schema(), - context) + package_id = _get_or_bust(data_dict, 'package_id') + data_dict.pop('package_id') + + pkg_dict = _get_action('package_show')(context, {'id': package_id}) + + _check_access('resource_create', context, data_dict) + + if not 'resources' in pkg_dict: + pkg_dict['resources'] = [] + pkg_dict['resources'].append(data_dict) + + try: + pkg_dict = _get_action('package_update')(context, pkg_dict) + except ValidationError, e: + errors = e.error_dict['resources'][-1] + raise ValidationError(errors, _error_summary(errors)) + + return pkg_dict['resources'][-1] def related_create(context, data_dict): diff --git a/ckan/logic/auth/create.py b/ckan/logic/auth/create.py index 546a1b872ac..d62faafe49d 100644 --- a/ckan/logic/auth/create.py +++ b/ckan/logic/auth/create.py @@ -27,9 +27,17 @@ def related_create(context, data_dict=None): return {'success': False, 'msg': _('You must be logged in to add a related item')} - def resource_create(context, data_dict): - return {'success': False, 'msg': 'Not implemented yet in the auth refactor'} + # resource_create runs through package_update, no need to + # check users eligibility to add resource to package here. + model = context['model'] + user = context['user'] + userobj = model.User.get(user) + + if userobj: + return {'success': True} + return {'success': False, + 'msg': _('You must be logged in to create a resource')} def package_relationship_create(context, data_dict): model = context['model'] diff --git a/ckan/logic/auth/publisher/create.py b/ckan/logic/auth/publisher/create.py index d1bece7ebaf..ea371571b0a 100644 --- a/ckan/logic/auth/publisher/create.py +++ b/ckan/logic/auth/publisher/create.py @@ -32,7 +32,16 @@ def related_create(context, data_dict=None): def resource_create(context, data_dict): - return {'success': False, 'msg': 'Not implemented yet in the auth refactor'} + # resource_create runs through package_update, no need to + # check users eligibility to add resource to package here + model = context['model'] + user = context['user'] + userobj = model.User.get(user) + + if userobj: + return {'success': True} + return {'success': False, + 'msg': _('You must be logged in to create a resource')} def package_relationship_create(context, data_dict): """ diff --git a/ckan/tests/functional/test_group.py b/ckan/tests/functional/test_group.py index e71cd286b1e..d83ff35ee8a 100644 --- a/ckan/tests/functional/test_group.py +++ b/ckan/tests/functional/test_group.py @@ -2,6 +2,7 @@ from nose.tools import assert_equal +from ckan.tests import setup_test_search_index from ckan.plugins import SingletonPlugin, implements, IGroupController from ckan import plugins import ckan.model as model @@ -15,6 +16,7 @@ from base import FunctionalTestCase from ckan.tests import search_related, is_search_supported + class MockGroupControllerPlugin(SingletonPlugin): implements(IGroupController) @@ -44,6 +46,7 @@ def before_view(self, data_dict): self.calls['before_view'] += 1 return data_dict + class TestGroup(FunctionalTestCase): @classmethod @@ -78,7 +81,9 @@ def test_index(self): group_title = group.title group_packages_count = len(group.active_packages().all()) group_description = group.description - self.check_named_element(res, 'tr', group_title, group_packages_count, group_description) + self.check_named_element(res, 'tr', group_title, + group_packages_count, + group_description) res = res.click(group_title) assert groupname in res @@ -92,7 +97,8 @@ def test_read_plugin_hook(self): plugins.load(plugin) name = u'david' offset = url_for(controller='group', action='read', id=name) - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert plugin.calls['read'] == 1, plugin.calls plugins.unload(plugin) @@ -111,6 +117,7 @@ def test_new_page(self): res = self.app.get(offset, extra_environ={'REMOTE_USER': 'russianfan'}) assert 'Add A Group' in res, res + class TestGroupWithSearch(FunctionalTestCase): @classmethod @@ -138,12 +145,14 @@ def test_read(self): assert 'Administrators' in res, res assert 'russianfan' in main_res, main_res assert name in res, res - no_datasets_found = int(re.search('(\d*) datasets found', main_res).groups()[0]) + no_datasets_found = int(re.search('(\d*) datasets found', + main_res).groups()[0]) assert_equal(no_datasets_found, 2) pkg = model.Package.by_name(pkgname) res = res.click(pkg.title) assert '%s - Datasets' % pkg.title in res + class TestEdit(FunctionalTestCase): @classmethod @@ -156,6 +165,8 @@ def setup_class(self): model.Session.add(model.Package(name=self.packagename)) model.repo.commit_and_remove() + setup_test_search_index() + @classmethod def teardown_class(self): model.Session.remove() @@ -173,7 +184,8 @@ def test_2_edit(self): group = model.Group.by_name(self.groupname) offset = url_for(controller='group', action='edit', id=self.groupname) print offset - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert 'Edit: %s' % group.title in res, res form = res.forms['group-edit'] @@ -190,7 +202,8 @@ def test_2_edit(self): pkg = model.Package.by_name(self.packagename) form['packages__2__name'] = pkg.name - res = form.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'}) + res = form.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) # should be read page # assert 'Groups - %s' % self.groupname in res, res @@ -205,7 +218,8 @@ def test_2_edit(self): def test_3_edit_form_has_new_package(self): # check for dataset in autocomplete offset = url_for(controller='package', action='autocomplete', q='an') - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert 'annakarenina' in res, res assert not 'newone' in res, res model.repo.new_revision() @@ -219,7 +233,8 @@ def test_3_edit_form_has_new_package(self): model.setup_default_user_roles(pkg, [user]) model.repo.commit_and_remove() - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert 'annakarenina' in res, res assert 'newone' in res @@ -234,10 +249,12 @@ def test_4_new_duplicate_package(self): # Add same package again offset = url_for(controller='group', action='edit', id=group_name) - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) fv = res.forms['group-edit'] fv['packages__1__name'] = self.packagename - res = fv.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'}) + res = fv.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) res = res.follow() assert group_name in res, res model.Session.remove() @@ -251,28 +268,82 @@ def test_edit_plugin_hook(self): plugin = MockGroupControllerPlugin() plugins.load(plugin) offset = url_for(controller='group', action='edit', id=self.groupname) - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) form = res.forms['group-edit'] group = model.Group.by_name(self.groupname) form['title'] = "huhuhu" - res = form.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'}) + res = form.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) assert plugin.calls['edit'] == 1, plugin.calls plugins.unload(plugin) def test_edit_image_url(self): group = model.Group.by_name(self.groupname) offset = url_for(controller='group', action='edit', id=self.groupname) - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) form = res.forms['group-edit'] image_url = u'http://url.to/image_url' form['image_url'] = image_url - res = form.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'}) + res = form.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) model.Session.remove() group = model.Group.by_name(self.groupname) assert group.image_url == image_url, group + def test_edit_change_name(self): + group = model.Group.by_name(self.groupname) + offset = url_for(controller='group', action='edit', id=self.groupname) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) + assert 'Edit: %s' % group.title in res, res + + def update_group(res, name, with_pkg=True): + form = res.forms['group-edit'] + titlefn = 'title' + descfn = 'description' + newtitle = 'xxxxxxx' + newdesc = '''### Lots of stuff here + + Ho ho ho + ''' + form[titlefn] = newtitle + form[descfn] = newdesc + form['name'] = name + if with_pkg: + pkg = model.Package.by_name(self.packagename) + form['packages__2__name'] = pkg.name + + res = form.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) + update_group(res, self.groupname, True) + update_group(res, 'newname', False) + + model.Session.remove() + group = model.Group.by_name('newname') + + # We have the datasets in the DB, but we should also see that many + # on the group read page. + assert len(group.active_packages().all()) == 3 + + offset = url_for(controller='group', action='read', id='newname') + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) + + ds = res.body + ds = ds[ds.index('datasets') - 10: ds.index('datasets') + 10] + assert '3 datasets found' in res, ds + + # reset the group to how we found it + offset = url_for(controller='group', action='edit', id='newname') + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) + + update_group(res, self.groupname, True) + def test_edit_non_existent(self): name = u'group_does_not_exist' offset = url_for(controller='group', action='edit', id=name) @@ -286,7 +357,8 @@ def test_delete(self): group = model.Group.by_name(group_name) offset = url_for(controller='group', action='edit', id=group_name) - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) main_res = self.main_div(res) assert 'Edit: %s' % group.title in main_res, main_res assert 'value="active" selected' in main_res, main_res @@ -294,7 +366,8 @@ def test_delete(self): # delete form = res.forms['group-edit'] form['state'] = 'deleted' - res = form.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'}) + res = form.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) group = model.Group.by_name(group_name) assert_equal(group.state, 'deleted') @@ -302,6 +375,7 @@ def test_delete(self): res = res.follow() assert res.request.url.startswith('/user/login'), res.request.url + class TestNew(FunctionalTestCase): groupname = u'david' @@ -336,21 +410,24 @@ def test_2_new(self): # Open 'Add A Group' page offset = url_for(controller='group', action='new') - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert 'Add A Group' in res, res fv = res.forms['group-edit'] - assert fv[prefix+'name'].value == '', fv.fields - assert fv[prefix+'title'].value == '' - assert fv[prefix+'description'].value == '' - assert fv['packages__0__name'].value == '', fv['Member--package_name'].value + assert fv[prefix + 'name'].value == '', fv.fields + assert fv[prefix + 'title'].value == '' + assert fv[prefix + 'description'].value == '' + assert fv['packages__0__name'].value == '', \ + fv['Member--package_name'].value # Edit form - fv[prefix+'name'] = group_name - fv[prefix+'title'] = group_title - fv[prefix+'description'] = group_description + fv[prefix + 'name'] = group_name + fv[prefix + 'title'] = group_title + fv[prefix + 'description'] = group_description pkg = model.Package.by_name(self.packagename) fv['packages__0__name'] = pkg.name - res = fv.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'}) + res = fv.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) res = res.follow() assert '%s' % group_title in res, res @@ -368,12 +445,14 @@ def test_3_new_duplicate_group(self): # Create group group_name = u'testgrp1' offset = url_for(controller='group', action='new') - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert 'Add A Group' in res, res fv = res.forms['group-edit'] - assert fv[prefix+'name'].value == '', fv.fields - fv[prefix+'name'] = group_name - res = fv.submit('save', status=302, extra_environ={'REMOTE_USER': 'russianfan'}) + assert fv[prefix + 'name'].value == '', fv.fields + fv[prefix + 'name'] = group_name + res = fv.submit('save', status=302, + extra_environ={'REMOTE_USER': 'russianfan'}) res = res.follow() assert group_name in res, res model.Session.remove() @@ -381,12 +460,14 @@ def test_3_new_duplicate_group(self): # Create duplicate group group_name = u'testgrp1' offset = url_for(controller='group', action='new') - res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + res = self.app.get(offset, status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert 'Add A Group' in res, res fv = res.forms['group-edit'] - assert fv[prefix+'name'].value == '', fv.fields - fv[prefix+'name'] = group_name - res = fv.submit('save', status=200, extra_environ={'REMOTE_USER': 'russianfan'}) + assert fv[prefix + 'name'].value == '', fv.fields + fv[prefix + 'name'] = group_name + res = fv.submit('save', status=200, + extra_environ={'REMOTE_USER': 'russianfan'}) assert 'Group name already exists' in res, res self.check_tag(res, 'description
- Written by Puccini\n+ Written off
' in main_res, main_res + assert "description
- Written by Puccini\n+" + \
+               " Written off
" in main_res, main_res def test_2_atom_feed(self): - offset = url_for(controller='group', action='history', id=self.grp.name) + offset = url_for(controller='group', action='history', + id=self.grp.name) offset = "%s?format=atom" % offset res = self.app.get(offset) assert '