diff --git a/LICENSE.txt b/LICENSE.txt index 0e58e4e3401..4eb1394ef49 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -22,9 +22,9 @@ Note ==== CKAN is sometimes packaged directly with other software (listed in -requires/lucid_conflict.txt). In these cases, we are required to list the -licenses of the packaged softare too. They are all AGPL compatible and read as -follows in the next sections. +pip-requirements.txt, pip-requirements-test.txt and pip-requirements-docs.txt). +In these cases, we are required to list the licenses of the packaged softare +too. They are all AGPL compatible and read as follows in the next sections. WebHelpers ---------- diff --git a/ckan/lib/cli.py b/ckan/lib/cli.py index 3aef7efe8e8..baaa4674ba9 100644 --- a/ckan/lib/cli.py +++ b/ckan/lib/cli.py @@ -977,13 +977,13 @@ def update_tracking(self, engine, summary_date): FROM tracking_summary t2 WHERE t1.url = t2.url AND t2.tracking_date <= t1.tracking_date - ) + t1.count + ) ,recent_views = ( SELECT sum(count) FROM tracking_summary t2 WHERE t1.url = t2.url AND t2.tracking_date <= t1.tracking_date AND t2.tracking_date >= t1.tracking_date - 14 - ) + t1.count + ) WHERE t1.running_total = 0 AND tracking_type = 'resource';''' engine.execute(sql) @@ -1000,7 +1000,7 @@ def update_tracking(self, engine, summary_date): FROM tracking_summary t2 WHERE t1.package_id = t2.package_id AND t2.tracking_date <= t1.tracking_date AND t2.tracking_date >= t1.tracking_date - 14 - ) + t1.count + ) WHERE t1.running_total = 0 AND tracking_type = 'page' AND t1.package_id IS NOT NULL AND t1.package_id != '~~not~found~~';''' diff --git a/ckan/lib/create_test_data.py b/ckan/lib/create_test_data.py index 7b4a7815359..99e2874c295 100644 --- a/ckan/lib/create_test_data.py +++ b/ckan/lib/create_test_data.py @@ -397,8 +397,8 @@ def create(cls, auth_profile="", package_type=None): ) model.Session.add(pr1) model.Session.add(pr2) - pkg1.resources.append(pr1) - pkg1.resources.append(pr2) + pkg1.resource_groups_all[0].resources_all.append(pr1) + pkg1.resource_groups_all[0].resources_all.append(pr2) pkg1.notes = u'''Some test notes ### A 3rd level heading diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 8ed55bb5e80..c59de6e46fb 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -928,6 +928,7 @@ def render_markdown(data): 'dashboard_activity_stream', 'get_request_param', 'render_markdown', + # imported into ckan.lib.helpers 'literal', 'link_to', 'get_available_locales', diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index a33e9c05830..c7bf9f60a62 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -266,8 +266,12 @@ def group_list(context, data_dict): '''Return a list of the names of the site's groups. :param order_by: the field to sort the list by, must be ``'name'`` or - ``'packages'`` (optional, default: ``'name'``) + ``'packages'`` (optional, default: ``'name'``) Deprecated use sort. :type order_by: string + :param sort: sorting of the search results. Optional. Default: + "name asc" string of field name and sort-order. The allowed fields are + 'name' and 'packages' + :type sort: string :param groups: a list of names of the groups to return, if given only groups whose names are in this list will be returned (optional) :type groups: list of strings @@ -278,14 +282,30 @@ def group_list(context, data_dict): :rtype: list of strings ''' + model = context['model'] api = context.get('api_version') groups = data_dict.get('groups') ref_group_by = 'id' if api == 2 else 'name'; - order_by = data_dict.get('order_by', 'name') - if order_by not in set(('name', 'packages')): - raise logic.ParameterError('"order_by" value %r not implemented.' % order_by) - all_fields = data_dict.get('all_fields',None) + + sort = data_dict.get('sort', 'name') + # order_by deprecated in ckan 1.8 + # if it is supplied and sort isn't use order_by and raise a warning + order_by = data_dict.get('order_by') + if order_by: + log.warn('`order_by` deprecated please use `sort`') + if not data_dict.get('sort'): + sort = order_by + # if the sort is packages and no sort direction is supplied we want to do a + # reverse sort to maintain compatibility. + if sort.strip() == 'packages': + sort = 'packages desc' + + sort_info = _unpick_search(sort, + allowed_fields=['name', 'packages'], + total=1) + + all_fields = data_dict.get('all_fields', None) _check_access('group_list', context, data_dict) @@ -295,16 +315,10 @@ def group_list(context, data_dict): if groups: query = query.filter(model.GroupRevision.name.in_(groups)) - if order_by == 'name': - sort_by, reverse = 'name', False - groups = query.all() - - if order_by == 'packages': - sort_by, reverse = 'packages', True - group_list = model_dictize.group_list_dictize(groups, context, - lambda x:x[sort_by], reverse) + lambda x:x[sort_info[0][0]], + sort_info[0][1] == 'desc') if not all_fields: group_list = [group[ref_group_by] for group in group_list] @@ -1009,7 +1023,7 @@ def package_search(context, data_dict): returned datasets should begin. :type start: int :param qf: the dismax query fields to search within, including boosts. See - the `Solr Dismax Documentation + the `Solr Dismax Documentation `_ for further details. :type qf: string @@ -2219,3 +2233,30 @@ def dashboard_activity_list_html(context, data_dict): ''' activity_stream = dashboard_activity_list(context, data_dict) return _activity_list_to_html(context, activity_stream) + + +def _unpick_search(sort, allowed_fields=None, total=None): + ''' This is a helper function that takes a sort string + eg 'name asc, last_modified desc' and returns a list of + split field order eg [('name', 'asc'), ('last_modified', 'desc')] + allowed_fields can limit which field names are ok. + total controls how many sorts can be specifed ''' + sorts = [] + split_sort = sort.split(',') + for part in split_sort: + split_part = part.strip().split() + field = split_part[0] + if len(split_part) > 1: + order = split_part[1].lower() + else: + order = 'asc' + if allowed_fields: + if field not in allowed_fields: + raise logic.ParameterError('Cannot sort by field `%s`' % field) + if order not in ['asc', 'desc']: + raise logic.ParameterError('Invalid sort direction `%s`' % order) + sorts.append((field, order)) + if total and len(sorts) > total: + raise logic.ParameterError( + 'Too many sort criteria provided only %s allowed' % total) + return sorts diff --git a/ckan/model/authz.py b/ckan/model/authz.py index 7c952760ba4..6929cd49e30 100644 --- a/ckan/model/authz.py +++ b/ckan/model/authz.py @@ -3,6 +3,7 @@ ''' import simplejson as json +import weakref from sqlalchemy import orm, types, Column, Table, ForeignKey from pylons import config @@ -413,7 +414,7 @@ def give_all_packages_default_user_roles(): } global _default_user_roles_cache -_default_user_roles_cache = {} +_default_user_roles_cache = weakref.WeakKeyDictionary() def get_default_user_roles(_domain_object): # TODO: Should this func go in lib rather than model now? diff --git a/ckan/model/license.py b/ckan/model/license.py index c31fb218ebf..2766d97c38d 100644 --- a/ckan/model/license.py +++ b/ckan/model/license.py @@ -49,207 +49,21 @@ def __init__(self): self.load_licenses(group_url) else: default_license_list = [ - { - "domain_content": False, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "notspecified", - "is_generic": True, - "is_okd_compliant": False, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": _("License Not Specified"), - "url": "" - }, - { - "domain_content": False, - "domain_data": True, - "domain_software": False, - "family": "", - "id": "odc-pddl", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "Open Data Commons Public Domain Dedication and Licence (PDDL)", - "url": "http://www.opendefinition.org/licenses/odc-pddl" - }, - { - "domain_content": False, - "domain_data": True, - "domain_software": False, - "family": "", - "id": "odc-odbl", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "Open Data Commons Open Database License (ODbL)", - "url": "http://www.opendefinition.org/licenses/odc-odbl" - }, - { - "domain_content": False, - "domain_data": True, - "domain_software": False, - "family": "", - "id": "odc-by", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "Open Data Commons Attribution License", - "url": "http://www.opendefinition.org/licenses/odc-by" - }, - { - "domain_content": True, - "domain_data": True, - "domain_software": False, - "family": "", - "id": "cc-zero", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "Creative Commons CCZero", - "url": "http://www.opendefinition.org/licenses/cc-zero" - }, - { - "domain_content": True, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "cc-by", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "Creative Commons Attribution", - "url": "http://www.opendefinition.org/licenses/cc-by" - }, - { - "domain_content": True, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "cc-by-sa", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "Creative Commons Attribution Share-Alike", - "url": "http://www.opendefinition.org/licenses/cc-by-sa" - }, - { - "domain_content": True, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "gfdl", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "GNU Free Documentation License", - "url": "http://www.opendefinition.org/licenses/gfdl" - }, - { - "domain_content": True, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "other-open", - "is_generic": True, - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": _("Other (Open)"), - "url": "" - }, - { - "domain_content": True, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "other-pd", - "is_generic": True, - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": _("Other (Public Domain)"), - "url": "" - }, - { - "domain_content": True, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "other-at", - "is_generic": True, - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": _("Other (Attribution)"), - "url": "" - }, - { - "domain_content": True, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "uk-ogl", - "is_okd_compliant": True, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "UK Open Government Licence (OGL)", - "url": "http://reference.data.gov.uk/id/open-government-licence" - }, - { - "domain_content": False, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "cc-nc", - "is_okd_compliant": False, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": "Creative Commons Non-Commercial (Any)", - "url": "http://creativecommons.org/licenses/by-nc/2.0/" - }, - { - "domain_content": False, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "other-nc", - "is_generic": True, - "is_okd_compliant": False, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": _("Other (Non-Commercial)"), - "url": "" - }, - { - "domain_content": False, - "domain_data": False, - "domain_software": False, - "family": "", - "id": "other-closed", - "is_generic": True, - "is_okd_compliant": False, - "is_osi_compliant": False, - "maintainer": "", - "status": "active", - "title": _("Other (Not Open)"), - "url": "" - } + LicenseNotSpecified(), + LicenseOpenDataCommonsPDDL(), + LicenseOpenDataCommonsOpenDatabase(), + LicenseOpenDataAttribution(), + LicenseCreativeCommonsZero(), + LicenseCreativeCommonsAttribution(), + LicenseCreativeCommonsAttributionShareAlike(), + LicenseGNUFreeDocument(), + LicenseOtherOpen(), + LicenseOtherPublicDomain(), + LicenseOtherAttribution(), + LicenseOpenGovernment(), + LicenseCreativeCommonsNonCommercial(), + LicenseOtherNonCommercial(), + LicenseOtherClosed(), ] self._create_license_list(default_license_list) @@ -302,3 +116,191 @@ def __iter__(self): def __len__(self): return len(self.licenses) + + + +class DefaultLicense(dict): + ''' The license was a dict but this did not allow translation of the + title. This is a slightly changed dict that allows us to have the title + as a property and so translated. ''' + + domain_content = False + domain_data = False + domain_software = False + family = "" + is_generic = False + is_okd_compliant = False + is_osi_compliant = False + maintainer = "" + status = "active" + url = "" + title = '' + id = '' + + keys = ['domain_content', + 'id', + 'domain_data', + 'domain_software', + 'family', + 'is_generic', + 'is_okd_compliant', + 'is_osi_compliant', + 'maintainer', + 'status', + 'url', + 'title'] + + def __getitem__(self, key): + ''' behave like a dict but get from attributes ''' + if key in self.keys: + return unicode(getattr(self, key)) + else: + raise KeyError() + + def copy(self): + ''' create a dict of the license used by the licenses api ''' + out = {} + for key in self.keys: + out[key] = unicode(getattr(self, key)) + return out + +class LicenseNotSpecified(DefaultLicense): + id = "notspecified" + is_generic = True + + @property + def title(self): + return _("License Not Specified") + +class LicenseOpenDataCommonsPDDL(DefaultLicense): + domain_data = True + id = "odc-pddl" + is_okd_compliant = True + url = "http://www.opendefinition.org/licenses/odc-pddl" + + @property + def title(self): + return _("Open Data Commons Public Domain Dedication and Licence (PDDL)") + +class LicenseOpenDataCommonsOpenDatabase(DefaultLicense): + domain_data = True + id = "odc-odbl" + is_okd_compliant = True + url = "http://www.opendefinition.org/licenses/odc-odbl" + + @property + def title(self): + return _("Open Data Commons Open Database License (ODbL)") + +class LicenseOpenDataAttribution(DefaultLicense): + domain_data = True + id = "odc-by" + is_okd_compliant = True + url = "http://www.opendefinition.org/licenses/odc-by" + + @property + def title(self): + return _("Open Data Commons Attribution License") + +class LicenseCreativeCommonsZero(DefaultLicense): + domain_content = True + domain_data = True + id = "cc-zero" + is_okd_compliant = True + url = "http://www.opendefinition.org/licenses/cc-zero" + + @property + def title(self): + return _("Creative Commons CCZero") + +class LicenseCreativeCommonsAttribution(DefaultLicense): + id = "cc-by" + is_okd_compliant = True + url = "http://www.opendefinition.org/licenses/cc-by" + + @property + def title(self): + return _("Creative Commons Attribution") + +class LicenseCreativeCommonsAttributionShareAlike(DefaultLicense): + domain_content = True + id = "cc-by-sa" + is_okd_compliant = True + url = "http://www.opendefinition.org/licenses/cc-by-sa" + + @property + def title(self): + return _("Creative Commons Attribution Share-Alike") + +class LicenseGNUFreeDocument(DefaultLicense): + domain_content = True + id = "gfdl" + is_okd_compliant = True + url = "http://www.opendefinition.org/licenses/gfdl" + @property + def title(self): + return _("GNU Free Documentation License") + +class LicenseOtherOpen(DefaultLicense): + domain_content = True + id = "other-open" + is_generic = True + is_okd_compliant = True + + @property + def title(self): + return _("Other (Open)") + +class LicenseOtherPublicDomain(DefaultLicense): + domain_content = True + id = "other-pd" + is_generic = True + is_okd_compliant = True + + @property + def title(self): + return _("Other (Public Domain)") + +class LicenseOtherAttribution(DefaultLicense): + domain_content = True + id = "other-at" + is_generic = True + is_okd_compliant = True + + @property + def title(self): + return _("Other (Attribution)") + +class LicenseOpenGovernment(DefaultLicense): + domain_content = True + id = "uk-ogl" + is_okd_compliant = True + url = "http://reference.data.gov.uk/id/open-government-licence" + + @property + def title(self): + return _("UK Open Government Licence (OGL)") + +class LicenseCreativeCommonsNonCommercial(DefaultLicense): + id = "cc-nc" + url = "http://creativecommons.org/licenses/by-nc/2.0/" + + @property + def title(self): + return _("Creative Commons Non-Commercial (Any)") + +class LicenseOtherNonCommercial(DefaultLicense): + id = "other-nc" + is_generic = True + + @property + def title(self): + return _("Other (Non-Commercial)") + +class LicenseOtherClosed(DefaultLicense): + id = "other-closed" + is_generic = True + + @property + def title(self): + return _("Other (Not Open)") diff --git a/ckan/model/package.py b/ckan/model/package.py index 1bab7fb3797..b59476feac3 100644 --- a/ckan/model/package.py +++ b/ckan/model/package.py @@ -62,7 +62,7 @@ def __init__(self, **kw): from ckan import model super(Package, self).__init__(**kw) resource_group = model.ResourceGroup(label="default") - self.resource_groups.append(resource_group) + self.resource_groups_all.append(resource_group) @classmethod def search_by_name(cls, text_query): @@ -85,82 +85,23 @@ def resources(self): return [] assert len(self.resource_groups_all) == 1, "can only use resources on packages if there is only one resource_group" - return self.resource_groups_all[0].resources - - def update_resources(self, res_dicts, autoflush=True): - '''Change this package\'s resources. - @param res_dicts - ordered list of dicts, each detailing a resource - The resource dictionaries contain 'url', 'format' etc. Optionally they - can also provide the 'id' of the Resource, to help matching - res_dicts to existing Resources. Otherwise, it searches - for an otherwise exactly matching Resource. - The caller is responsible for creating a revision and committing.''' - from ckan import model - assert isinstance(res_dicts, (list, tuple)) - # Map the incoming res_dicts (by index) to existing resources - index_to_res = {} - # Match up the res_dicts by id - def get_resource_identity(resource_obj_or_dict): - if isinstance(resource_obj_or_dict, dict): - # Convert dict into a Resource object, since that ensures - # all columns exist when you redictize it. This object is - # garbage collected as it isn't added to the Session. - res_keys = set(resource_obj_or_dict.keys()) - \ - set(('id', 'position')) - res_dict = dict([(res_key, resource_obj_or_dict[res_key]) \ - for res_key in res_keys]) - resource = model.Resource(**res_dict) - else: - resource = resource_obj_or_dict - res_dict = resource.as_dict(core_columns_only=True) - del res_dict['created'] - return res_dict - existing_res_identites = [get_resource_identity(res) \ - for res in self.resources] - for i, res_dict in enumerate(res_dicts): - assert isinstance(res_dict, dict) - id = res_dict.get('id') - if id: - res = meta.Session.query(model.Resource).autoflush(autoflush).get(id) - if res: - index_to_res[i] = res - else: - res_identity = get_resource_identity(res_dict) - try: - matching_res_index = existing_res_identites.index(res_identity) - except ValueError: - continue - index_to_res[i] = self.resources[matching_res_index] - - # Edit resources and create the new ones - new_res_list = [] - - for i, res_dict in enumerate(res_dicts): - if i in index_to_res: - res = index_to_res[i] - for col in set(res_dict.keys()) - set(('id', 'position')): - setattr(res, col, res_dict[col]) - else: - # ignore particular keys that disrupt creation of new resource - for key in set(res_dict.keys()) & set(('id', 'position')): - del res_dict[key] - res = model.Resource(**res_dict) - meta.Session.add(res) - new_res_list.append(res) - self.resource_groups[0].resources = new_res_list + return [resource for resource in + self.resource_groups_all[0].resources_all + if resource.state <> 'deleted'] def related_packages(self): return [self] def add_resource(self, url, format=u'', description=u'', hash=u'', **kw): import resource - self.resources.append(resource.Resource( - resource_group_id=self.resource_groups[0].id, + self.resource_groups_all[0].resources_all.append(resource.Resource( + resource_group_id=self.resource_groups_all[0].id, url=url, format=format, description=description, hash=hash, - **kw)) + **kw) + ) def add_tag(self, tag): import ckan.model as model diff --git a/ckan/model/resource.py b/ckan/model/resource.py index 297e74136de..3d4665bd997 100644 --- a/ckan/model/resource.py +++ b/ckan/model/resource.py @@ -176,6 +176,7 @@ def __init__(self, package_id=None, sort_order=u'', label=u'', self.sort_order = sort_order self.label = label self.extras = extras or {} + self.state = 'active' extra_columns = self.get_extra_columns() for field in extra_columns: @@ -260,36 +261,9 @@ def get_extra_columns(cls): ResourceGroupRevision.related_packages = lambda self: [self.continuity.package] ResourceRevision.related_packages = lambda self: [self.continuity.resouce_group.package] -# TODO: move this into vdm -def add_stateful_m21(object_to_alter, m21_property_name, - underlying_m21_attrname, identifier, **kwargs): - def _f(obj_to_delete): - sess = orm.object_session(obj_to_delete) - if sess: # for tests at least must support obj not being sqlalchemy - sess.expunge(obj_to_delete) - - active_list = vdm.sqlalchemy.stateful.DeferredProperty( - underlying_m21_attrname, - vdm.sqlalchemy.stateful.StatefulList, - # these args are passed to StatefulList - # identifier if url (could use id but have issue with None) - identifier=identifier, - unneeded_deleter=_f, - base_modifier=lambda x: x.get_as_of() - ) - setattr(object_to_alter, m21_property_name, active_list) - def resource_identifier(obj): return obj.id - -add_stateful_m21(_package.Package, 'resource_groups', 'resource_groups_all', - resource_identifier) -add_stateful_m21(ResourceGroup, 'resources', 'resources_all', - resource_identifier) - - - class DictProxy(object): def __init__(self, target_key, target_dict, data_type=unicode): diff --git a/ckan/tests/forms/test_package.py b/ckan/tests/forms/test_package.py index ad32506ecb3..e69de29bb2d 100644 --- a/ckan/tests/forms/test_package.py +++ b/ckan/tests/forms/test_package.py @@ -1,411 +0,0 @@ -import ckan.model as model -import ckan.forms -from ckan.tests import * -from ckan.tests.pylons_controller import PylonsTestCase -from ckan.tests.html_check import HtmlCheckMethods -from ckan.lib.helpers import escape - -def _get_blank_param_dict(pkg=None): - return ckan.forms.get_package_dict(pkg=pkg, blank=True, user_editable_groups=[]) - -# These tests check the package form build in formalchemy. For the new forms, -# see equivalents: -# * form renders with correctly populated values (TestForms 1&2) in ckan/tests/functional/test_package.py:TestPackageForm -# * form post updates db correctly (TestForms 3&4) in ckan/tests/functional/api/test_action.py:test_03_create_update_package -# * validation tests (TestValidation) in ckan/tests/schema/test_schema.py - -class TestForms(PylonsTestCase, HtmlCheckMethods): - - @classmethod - def setup_class(cls): - super(TestForms, cls).setup_class() - model.Session.remove() - ckan.tests.CreateTestData.create() - - @classmethod - def teardown_class(cls): - super(TestForms, cls).teardown_class() - model.Session.remove() - model.repo.rebuild_db() - - def _get_standard_fieldset(self): - fs = ckan.forms.get_standard_fieldset(user_editable_groups=[]) - return fs - - def test_0_get_package_dict(self): - d = ckan.forms.get_package_dict(user_editable_groups=[]) - assert 'Package--title' in d - assert not 'Package--all_revisions' in d - - anna = model.Package.by_name(u'annakarenina') - prefix = 'Package-%s-' % anna.id - d = ckan.forms.get_package_dict(anna, user_editable_groups=[]) - assert prefix+'title' in d - assert d[prefix+'title'] == anna.title - assert d[prefix+'version'] == anna.version - assert d[prefix+'url'] == anna.url - - changes = {'title':'newtitle', 'url':'newurl'} - d2 = ckan.forms.edit_package_dict(d, changes, anna.id) - assert d2[prefix+'title'] == changes['title'] - assert d2[prefix+'version'] == anna.version - assert d2[prefix+'url'] == changes['url'] - assert d2[prefix+'name'] == anna.name - - def test_1_render(self): - fs = self._get_standard_fieldset() - anna = model.Package.by_name(u'annakarenina') - fs = fs.bind(anna) - out = fs.render() - assert out - assert 'Revision' not in out, out -# assert 'revision_id' not in out, out - assert 'All revisions' not in out, out -# assert 'all_revisions' not in out, out - assert 'Package tags' not in out, out - - def test_1_render_markdown(self): - fs = self._get_standard_fieldset() - anna = model.Package.by_name(u'annakarenina') - fs = fs.bind(anna) - out = fs.notes.render() - - def test_2_name(self): - fs = self._get_standard_fieldset() - anna = model.Package.by_name(u'annakarenina') - fs = fs.bind(anna) - out = fs.render() - assert 'Name' in out, out - assert '*' in out, out - - def test_2_tags(self): - fs = self._get_standard_fieldset() - anna = model.Package.by_name(u'annakarenina') - fs = fs.bind(anna) - out = fs.tags.render() - assert out - assert 'tags' in out - self.check_tag(out, 'input', 'russian', 'tolstoy') - - out = fs.tags.render_readonly() - self.check_tag(out, 'a', '/tag/russian') - self.check_tag(out, 'a', '/tag/tolstoy') - self.check_named_element(out, 'a', '>russian<') - - def test_2_resources(self): - fs = self._get_standard_fieldset() - anna = model.Package.by_name(u'annakarenina') - fs = fs.bind(anna) - out = fs.resources.render() - out_printable = out.encode('utf8') # encoded utf8 - for res in anna.resources: - assert escape(res.url) in out, out_printable - assert res.format in out, out_printable - assert u'Full text. Needs escaping: " Umlaut: \xfc"' in out, out_printable - assert res.hash in out, out_printable - assert res.alt_url in out, out_printable - assert res.extras['size_extra'] in out, out_printable - - def test_2_fields(self): - fs = self._get_standard_fieldset() - anna = model.Package.by_name(u'annakarenina') - fs = fs.bind(anna) - out = fs.render() -# print out - assert out - assert 'title' in fs.title.render(), fs.title.render() - assert anna.title in fs.title.render(), fs.title.render() - assert 'version' in fs.version.render(), fs.version.render() - assert anna.version in fs.version.render(), fs.version.render() - assert 'notes' in fs.notes.render(), fs.notes.render() - assert anna.notes[:10] in fs.notes.render(), fs.notes.render() - assert 'name' in fs.name.render(), fs.name.render() - assert anna.name in fs.name.render(), fs.name.render() - assert 'tags' in fs.tags.render(), fs.tags.render() - assert 'extras' in fs.extras.render(), fs.extras.render() - extra = anna._extras.values()[0] - assert extra.key in fs.extras.render(), fs.extras.render() - assert extra.value in fs.extras.render(), fs.extras.render() - assert 'Delete' in fs.extras.render(), fs.extras.render() - - def test_3_sync_new(self): - newtagname = 'newtagname' - indict = _get_blank_param_dict() - indict['Package--name'] = u'testname' - indict['Package--notes'] = u'some new notes' - indict['Package--tags'] = u'russian, tolstoy, ' + newtagname, - indict['Package--license_id'] = u'odc-by' - indict['Package--extras-newfield0-key'] = u'testkey' - indict['Package--extras-newfield0-value'] = u'testvalue' - indict['Package--resources-0-url'] = u'http:/1' - indict['Package--resources-0-format'] = u'xml' - indict['Package--resources-0-description'] = u'test desc' - indict['Package--resources-0-size'] = 10 - indict['Package--resources-0-alt_url'] = u'http:/2' - - fs = self._get_standard_fieldset().bind(model.Package, data=indict) - - model.repo.new_revision() - fs.sync() - model.repo.commit_and_remove() - - outpkg = model.Package.by_name(u'testname') - assert outpkg.notes == indict['Package--notes'] - - # test tags - taglist = [ tag.name for tag in outpkg.get_tags() ] - assert u'russian' in taglist, taglist - assert u'tolstoy' in taglist, taglist - assert newtagname in taglist - - # test licenses - assert indict['Package--license_id'] == outpkg.license_id, outpkg.license_id - #assert outpkg.license - #assert indict['Package--license_id'] == outpkg.license.id, outpkg.license - - # test extra - assert outpkg._extras.keys() == [u'testkey'], outpkg._extras.keys() - assert outpkg._extras.values()[0].key == u'testkey', outpkg._extras.values()[0].key - assert outpkg._extras.values()[0].value == u'testvalue', outpkg._extras.values()[0].value - - # test resources - assert len(outpkg.resources) == 1, outpkg.resources - res = outpkg.resources[0] - assert res.url == u'http:/1', res.url - assert res.description == u'test desc', res.description - assert res.format == u'xml', res.format - assert res.alt_url == u'http:/2', res.format - - - def test_4_sync_update(self): - newtagname = 'newtagname' - anna = model.Package.by_name(u'annakarenina') - prefix = 'Package-%s-' % anna.id - indict = _get_blank_param_dict(anna) - indict[prefix + 'name'] = u'annakaren' - indict[prefix + 'notes'] = u'new notes' - indict[prefix + 'tags'] = u'russian ,' + newtagname - indict[prefix + 'license_id'] = u'odc-by' - indict[prefix + 'extras-newfield0-key'] = u'testkey' - indict[prefix + 'extras-newfield0-value'] = u'testvalue' - indict[prefix + 'resources-0-url'] = u'http:/1' - indict[prefix + 'resources-0-format'] = u'xml' - indict[prefix + 'resources-0-description'] = u'test desc' - indict[prefix + 'resources-0-alt_url'] = u'alt' - - fs = self._get_standard_fieldset().bind(anna, data=indict) - model.repo.new_revision() - fs.sync() - model.repo.commit_and_remove() - outpkg = model.Package.by_name(u'annakaren') - assert outpkg - outpkg1 = model.Package.by_name(u'annakarenina') - assert not outpkg1 - - assert outpkg.notes == indict[prefix+'notes'] - - # test tags - taglist = [ tag.name for tag in outpkg.get_tags() ] - assert u'russian' in taglist, taglist - assert u'tolstoy' not in taglist, taglist - assert newtagname in taglist - - # test licenses - assert outpkg.license - assert indict[prefix+'license_id'] == outpkg.license.id, outpkg.license - - # test extra - assert outpkg.extras.has_key(u'testkey'), outpkg._extras.keys() - assert outpkg.extras[u'testkey'] == u'testvalue', outpkg._extras.values()[0].value - - # test resources - assert len(outpkg.resources) == 1, outpkg.resources - res = outpkg.resources[0] - assert res.url == u'http:/1', res.url - assert res.description == u'test desc', res.description - assert res.format == u'xml', res.format - assert res.alt_url == u'alt', res.description - - model.repo.commit_and_remove() - -class TestFormErrors(PylonsTestCase): - @classmethod - def setup_class(self): - model.Session.remove() - ckan.tests.CreateTestData.create() - - @classmethod - def teardown_class(self): - model.repo.rebuild_db() - - def _get_standard_fieldset(self): - fs = ckan.forms.get_standard_fieldset(user_editable_groups=[]) - return fs - - def test_1_dup_name(self): - assert model.Package.by_name(u'annakarenina') - indict = _get_blank_param_dict() - prefix = 'Package--' - indict[prefix + 'name'] = u'annakarenina' - indict[prefix + 'title'] = u'Some title' - fs = self._get_standard_fieldset().bind(model.Package, data=indict) - model.repo.new_revision() - assert not fs.validate() - - def test_1_new_name(self): - indict = _get_blank_param_dict() - prefix = 'Package--' - indict[prefix + 'name'] = u'annakarenina123' - indict[prefix + 'title'] = u'Some title' - fs = self._get_standard_fieldset().bind(model.Package, data=indict) - model.repo.new_revision() - assert fs.validate() - - def test_2_extra_no_key(self): - indict = _get_blank_param_dict() - prefix = 'Package--' - indict[prefix + 'name'] = u'annakarenina123' - indict[prefix + 'title'] = u'Some title' - indict[prefix + 'extras-newfield0-value'] = u'testvalue' - fs = self._get_standard_fieldset().bind(model.Package, data=indict) - model.repo.new_revision() - assert not fs.validate() - - def test_2_extra_no_value(self): - indict = _get_blank_param_dict() - prefix = 'Package--' - indict[prefix + 'name'] = u'annakarenina123' - indict[prefix + 'title'] = u'Some title' - indict[prefix + 'extras-newfield0-key'] = u'testkey' - fs = self._get_standard_fieldset().bind(model.Package, data=indict) - model.repo.new_revision() - assert fs.validate() - - def test_2_extra_key(self): - indict = _get_blank_param_dict() - prefix = 'Package--' - indict[prefix + 'name'] = u'annakarenina123' - indict[prefix + 'title'] = u'Some title' - indict[prefix + 'extras-newfield0-value'] = u'testvalue' - indict[prefix + 'extras-newfield0-key'] = u'testkey' - fs = self._get_standard_fieldset().bind(model.Package, data=indict) - model.repo.new_revision() - assert fs.validate(), fs.errors - - def test_3_resource_no_url(self): - indict = _get_blank_param_dict() - prefix = 'Package--' - indict[prefix + 'name'] = u'annakarenina123' - indict[prefix + 'title'] = u'Some title' - indict[prefix + 'resources-'] = u'testvalue' - indict['Package--resources-0-url'] = u'' - indict['Package--resources-0-format'] = u'xml' - indict['Package--resources-0-description'] = u'test desc' - fs = self._get_standard_fieldset().bind(model.Package, data=indict) - model.repo.new_revision() - assert not fs.validate() - -class TestValidation: - @classmethod - def setup_class(self): - model.Session.remove() - ckan.tests.CreateTestData.create() - - @classmethod - def teardown_class(self): - model.repo.rebuild_db() - - def _get_standard_fieldset(self): - fs = ckan.forms.get_standard_fieldset(user_editable_groups=[]) - return fs - - def test_1_package_name(self): - anna = model.Package.by_name(u'annakarenina') - prefix = 'Package-%s-' % anna.id - indict = _get_blank_param_dict(anna) - - good_names = [ 'blah', 'ab', 'ab1', 'some-random-made-up-name', 'has_underscore', 'annakarenina' ] - bad_names = [ '', 'blAh', 'a', 'dot.in.name', u'unicode-\xe0', 'percent%' ] - - for i, name in enumerate(good_names): - indict[prefix + 'name'] = unicode(name) - fs = self._get_standard_fieldset().bind(anna, data=indict) - assert fs.validate() - - for i, name in enumerate(bad_names): - indict[prefix + 'name'] = unicode(name) - fs = self._get_standard_fieldset().bind(anna, data=indict) - assert not fs.validate() - - def test_1_tag_name(self): - anna = model.Package.by_name(u'annakarenina') - prefix = 'Package-%s-' % anna.id - indict = _get_blank_param_dict(anna) - - good_names = [ 'blah', 'ab', 'ab1', 'some-random-made-up-name',\ - 'has_underscore', u'unicode-\xe0', 'dot.in.name',\ - 'multiple words', u'with Greek omega \u03a9', 'CAPITALS'] - bad_names = [ 'a', ' ,leading comma', 'trailing comma,',\ - 'empty,,tag' 'quote"character'] - - for i, name in enumerate(good_names): - indict[prefix + 'name'] = u'okname' - indict[prefix + 'tags'] = unicode(name) - fs = self._get_standard_fieldset().bind(anna, data=indict) - out = fs.validate() - assert out, fs.errors - - for i, name in enumerate(bad_names): - indict[prefix + 'name'] = u'okname' - indict[prefix + 'tags'] = unicode(name) - fs = self._get_standard_fieldset().bind(anna, data=indict) - out = fs.validate() - assert not out, fs.errors - - def test_2_tag_names_are_stripped_of_leading_and_trailing_spaces(self): - """ - Asserts that leading and trailing spaces are stripped from tag names - """ - anna = model.Package.by_name(u'annakarenina') - prefix = 'Package-%s-' % anna.id - indict = _get_blank_param_dict(anna) - indict[prefix + 'name'] = u'annakarenina' - - whitespace_characters = u'\t\n\r\f\v' - for ch in whitespace_characters: - indict[prefix + 'tags'] = ch + u'tag name' + ch - fs = self._get_standard_fieldset().bind(anna, data=indict) - out = fs.validate() - assert out, fs.errors - - model.repo.new_revision() - fs.sync() - model.repo.commit_and_remove() - anna = model.Package.by_name(u'annakarenina') - taglist = [ tag.name for tag in anna.get_tags() ] - assert len(taglist) == 1 - assert u'tag name' in taglist - - def test_3_tag_names_are_split_by_commas(self): - """ - Asserts that tag names are split by commas. - """ - anna = model.Package.by_name(u'annakarenina') - prefix = 'Package-%s-' % anna.id - indict = _get_blank_param_dict(anna) - indict[prefix + 'name'] = u'annakarenina' - - indict[prefix + 'tags'] = u'tag name one, tag name two' - fs = self._get_standard_fieldset().bind(anna, data=indict) - out = fs.validate() - assert out, fs.errors - - model.repo.new_revision() - fs.sync() - model.repo.commit_and_remove() - anna = model.Package.by_name(u'annakarenina') - taglist = [ tag.name for tag in anna.get_tags() ] - assert len(taglist) == 2 - assert u'tag name one' in taglist - assert u'tag name two' in taglist - diff --git a/ckan/tests/functional/test_group.py b/ckan/tests/functional/test_group.py index ec12115d30f..098ae250c00 100644 --- a/ckan/tests/functional/test_group.py +++ b/ckan/tests/functional/test_group.py @@ -7,7 +7,7 @@ from ckan import plugins import ckan.model as model from ckan.lib.create_test_data import CreateTestData -from ckan.logic import check_access, NotAuthorized +from ckan.logic import check_access, NotAuthorized, get_action from pylons import config @@ -99,6 +99,55 @@ def test_children(self): parent = model.Group.by_name("parent_group") assert_equal(len(parent.get_children_groups()), 0) + def test_sorting(self): + model.repo.rebuild_db() + + pkg1 = model.Package(name="pkg1") + pkg2 = model.Package(name="pkg2") + model.Session.add(pkg1) + model.Session.add(pkg2) + + CreateTestData.create_groups([{'name': "alpha", 'packages': []}, + {'name': "beta", + 'packages': ["pkg1", "pkg2"]}, + {'name': "delta", + 'packages': ["pkg1"]}, + {'name': "gamma", 'packages': []}], + admin_user_name='russianfan') + + context = {'model': model, 'session': model.Session, + 'user': 'russianfan', 'for_view': True, + 'with_private': False} + data_dict = {'all_fields': True} + results = get_action('group_list')(context, data_dict) + assert results[0]['name'] == u'alpha', results[0]['name'] + assert results[-1]['name'] == u'gamma', results[-1]['name'] + + # Test name reverse + data_dict = {'all_fields': True, 'sort': 'name desc'} + results = get_action('group_list')(context, data_dict) + assert results[0]['name'] == u'gamma', results[0]['name'] + assert results[-1]['name'] == u'alpha', results[-1]['name'] + + # Test packages reversed + data_dict = {'all_fields': True, 'sort': 'packages desc'} + results = get_action('group_list')(context, data_dict) + assert results[0]['name'] == u'beta', results[0]['name'] + assert results[1]['name'] == u'delta', results[1]['name'] + + # Test packages forward + data_dict = {'all_fields': True, 'sort': 'packages asc'} + results = get_action('group_list')(context, data_dict) + assert results[-2]['name'] == u'delta', results[-2]['name'] + assert results[-1]['name'] == u'beta', results[-1]['name'] + + # Default ordering for packages + data_dict = {'all_fields': True, 'sort': 'packages'} + results = get_action('group_list')(context, data_dict) + assert results[0]['name'] == u'beta', results[0]['name'] + assert results[1]['name'] == u'delta', results[1]['name'] + + def test_mainmenu(self): # the home page does a package search so have to skip this test if # search is not supported diff --git a/ckan/tests/functional/test_revision.py b/ckan/tests/functional/test_revision.py index b8a27262060..56121c783da 100644 --- a/ckan/tests/functional/test_revision.py +++ b/ckan/tests/functional/test_revision.py @@ -103,11 +103,12 @@ def create_updating_revision(self, name, **kwds): package = self.get_package(name) if 'resources' in kwds: resources = kwds.pop('resources') - package.resource_groups[0].resources = [] + for resource in package.resource_groups_all[0].resources_all: + resource.state = 'deleted' for resource in resources: resource = model.Resource(**resource) model.Session.add(resource) - package.resources.append(resource) + package.resource_groups_all[0].resources_all.append(resource) if 'extras' in kwds: extras_data = kwds.pop('extras') # extras = [] diff --git a/ckan/tests/lib/test_dictization.py b/ckan/tests/lib/test_dictization.py index aadfcbebe18..ae95f0220ee 100644 --- a/ckan/tests/lib/test_dictization.py +++ b/ckan/tests/lib/test_dictization.py @@ -175,7 +175,7 @@ def test_01_dictize_main_objects_simple(self): ## resource - resource = pkg.resource_groups[0].resources[0] + resource = pkg.resource_groups_all[0].resources_all[0] result = resource_dictize(resource, context) self.remove_changable_columns(result) @@ -375,7 +375,7 @@ def test_09_package_alter(self): package_dictized = package_dictize(pkg, context) - resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all() + resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups_all[0].id).all() sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1] for res in sorted_resources: @@ -426,7 +426,7 @@ def test_10_package_alter_pending(self): assert str(sorted_packages[1].expired_timestamp) != '9999-12-31 00:00:00' assert str(sorted_packages[2].expired_timestamp) != '9999-12-31 00:00:00' - resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all() + resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups_all[0].id).all() sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1] for pkg in sorted_resources: @@ -507,7 +507,7 @@ def test_11_add_pending(self): model.Session.commit() model.Session.remove() - resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all() + resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups_all[0].id).all() sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1] pprint(anna_dictized['resources']) @@ -593,7 +593,7 @@ def test_12_make_active(self): assert sorted_packages[2].state == 'active' assert sorted_packages[3].state == 'active' - resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all() + resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups_all[0].id).all() sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1] assert len(sorted_resources) == 5 diff --git a/ckan/tests/models/test_package.py b/ckan/tests/models/test_package.py index ffc49cb4709..7515b73f7df 100644 --- a/ckan/tests/models/test_package.py +++ b/ckan/tests/models/test_package.py @@ -409,7 +409,7 @@ def setup_class(self): # edit pkg - ResourceRevision rev = model.repo.new_revision() pkg1 = model.Package.by_name(self.name) - pkg1.resources.append(model.Resource(url=u'http://url1.com', + pkg1.resource_groups_all[0].resources_all.append(model.Resource(url=u'http://url1.com', format=u'xls', description=u'It is.', hash=u'abc123')) @@ -419,8 +419,8 @@ def setup_class(self): # edit pkg - ResourceRevision rev = model.repo.new_revision() pkg1 = model.Package.by_name(self.name) - pkg1.resources[0].url = u'http://url1.com/edited' - pkg1.resources.append(model.Resource(url=u'http://url2.com')) + pkg1.resource_groups_all[0].resources_all[0].url = u'http://url1.com/edited' + pkg1.resource_groups_all[0].resources_all.append(model.Resource(url=u'http://url2.com')) rev.message = u'Added resource' model.repo.commit_and_remove() diff --git a/ckan/tests/models/test_resource.py b/ckan/tests/models/test_resource.py index 464cfc776b8..c1342ffc539 100644 --- a/ckan/tests/models/test_resource.py +++ b/ckan/tests/models/test_resource.py @@ -22,7 +22,7 @@ def setup(self): rev = model.repo.new_revision() pkg = model.Package(name=self.pkgname) model.Session.add(pkg) - rg = pkg.resource_groups[0] + rg = pkg.resource_groups_all[0] for url in self.urls: pr = model.Resource(url=url, format=self.format, @@ -31,13 +31,13 @@ def setup(self): alt_url=self.alt_url, extras={u'size':self.size}, ) - rg.resources.append(pr) + rg.resources_all.append(pr) pr = model.Resource(url="no_extra", format=self.format, description=self.description, hash=self.hash, ) - rg.resources.append(pr) + rg.resources_all.append(pr) model.repo.commit_and_remove() def teardown(self): @@ -46,14 +46,14 @@ def teardown(self): def test_01_create_package_resources(self): pkg = model.Package.by_name(self.pkgname) - assert len(pkg.resource_groups) == 1 - assert len(pkg.resource_groups[0].resources) == 3, pkg.resource_groups[0].resources + assert len(pkg.resource_groups_all) == 1 + assert len(pkg.resource_groups_all[0].resources_all) == 3, pkg.resource_groups_all[0].resources - resource_group_0 = pkg.resource_groups[0] + resource_group_0 = pkg.resource_groups_all[0] assert resource_group_0.label == 'default', resource_group_0 assert resource_group_0.sort_order == '' , resource_group_0 - resource_0 = resource_group_0.resources[0] + resource_0 = resource_group_0.resources_all[0] assert resource_0.url == self.urls[0], resource_0 assert resource_0.description == self.description, resource_0 @@ -99,26 +99,26 @@ def test_01_create_package_resources(self): def test_02_delete_resource(self): pkg = model.Package.by_name(self.pkgname) - rg = pkg.resource_groups[0] - res = rg.resources[0] - assert len(rg.resources) == 3, rg.resources + rg = pkg.resource_groups_all[0] + res = rg.package.resources[0] + assert len(rg.package.resources) == 3, rg.resources rev = model.repo.new_revision() res.delete() model.repo.commit_and_remove() pkg = model.Package.by_name(self.pkgname) - rg = pkg.resource_groups[0] - assert len(rg.resources) == 2, rg.resources + rg = pkg.resource_groups_all[0] + assert len(rg.package.resources) == 2, rg.resources assert len(rg.resources_all) == 3, rg.resources_all def test_03_reorder_resources(self): rev = model.repo.new_revision() pkg = model.Package.by_name(self.pkgname) - rg = pkg.resource_groups[0] + rg = pkg.resource_groups_all[0] - res0 = rg.resources[0] - del rg.resources[0] - rg.resources.append(res0) + res0 = rg.resources_all[0] + del rg.resources_all[0] + rg.resources_all.append(res0) # this assert will fail # assert pkg.resources[1].position == 1 # Why? According to docs for ordering list it does not reorder appended @@ -126,17 +126,15 @@ def test_03_reorder_resources(self): # http://www.sqlalchemy.org/trac/browser/lib/sqlalchemy/ext/orderinglist.py#L197) # so we have to call reorder directly in supported versions # of sqlalchemy and set position to None in older ones. - if sqav.startswith('0.4'): - rg.resources[1].position = None - else: - rg.resources.target.reorder() + rg.resources_all.reorder() model.repo.commit_and_remove() pkg = model.Package.by_name(self.pkgname) - assert len(rg.resources) == 3, rg.resources - lastres = rg.resources[2] + assert len(rg.resources_all) == 3, len(rg.package.resources) + lastres = rg.package.resources[2] assert lastres.position == 2, lastres - assert lastres.url == self.urls[0], lastres.url + print lastres + assert lastres.url == self.urls[0], (self.urls, lastres.url) def test_04_insert_resource(self): @@ -145,15 +143,15 @@ def test_04_insert_resource(self): newurl = u'http://xxxxxxxxxxxxxxx' resource = model.Resource(url=newurl) - rg = pkg.resource_groups[0] + rg = pkg.resource_groups_all[0] - rg.resources.insert(0, resource) + rg.resources_all.insert(0, resource) model.repo.commit_and_remove() - rg = model.Package.by_name(self.pkgname).resource_groups[0] - assert len(rg.resources) == 4, rg.resources - assert rg.resources[1].url == self.urls[0] - assert len(rg.resources[1].all_revisions) == 2 + rg = model.Package.by_name(self.pkgname).resource_groups_all[0] + assert len(rg.package.resources) == 4, rg.resources + assert rg.resources_all[1].url == self.urls[0] + assert len(rg.resources_all[1].all_revisions) == 2 def test_05_delete_package(self): pkg = model.Package.by_name(self.pkgname) @@ -175,7 +173,7 @@ def test_05_delete_package(self): def test_06_not_allow_two_resource_groups(self): pkg = model.Package.by_name(self.pkgname) resource_group = model.ResourceGroup(label="new") - pkg.resource_groups.append(resource_group) + pkg.resource_groups_all.append(resource_group) pkg.resources def test_07_purge_package(self): @@ -192,205 +190,3 @@ def test_07_purge_package(self): assert len(all_resources) == 0, pkg.resources -class TestResourceEdit: - @classmethod - def setup(self): - self.pkgname = u'geodata' - self.urls = [u'http://somewhere.com/', - u'http://elsewhere.com/', - u'http://third.com'] - self.format = u'csv' - self.description = u'Important part.' - self.hash = u'abc123' - rev = model.repo.new_revision() - self.pkg = model.Package(name=self.pkgname) - model.Session.add(self.pkg) - model.repo.commit_and_remove() - - pkg = model.Package.by_name(self.pkgname) - rev = model.repo.new_revision() - - rg = pkg.resource_groups[0] - for url in self.urls: - rg.resources.append(model.Resource(url=url, - format=self.format, - description=self.description, - hash=self.hash)) - model.repo.commit_and_remove() - - @classmethod - def teardown(self): - model.Session.remove() - model.repo.rebuild_db() - - def test_1_update_resources_no_ids(self): - pkg = model.Package.by_name(self.pkgname) - rg = pkg.resource_groups[0] - offset = len(self.urls) - assert len(rg.resources) == offset, rg.resources - original_res_ids = [res.id for res in rg.resources] - def print_resources(caption, resources): - print '%s\n%s' % (caption, '\n'.join(['' % (res.id[:3], res.url, res.format) for res in resources])) - print_resources('BEFORE', rg.resources) - - rev = model.repo.new_revision() - res_dicts = [ - { #unchanged - 'url':self.urls[0], 'format':self.format, - 'description':self.description, 'hash':self.hash}, - { #slightly different - 'url':self.urls[1], 'format':u'OTHER FORMAT', - 'description':self.description, 'hash':self.hash}, - ] - pkg.update_resources(res_dicts) - model.repo.commit_and_remove() - - pkg = model.Package.by_name(self.pkgname) - rg = pkg.resource_groups[0] - print_resources('AFTER', rg.resources) - assert len(rg.resources) == len(res_dicts), rg.resources - for i, res in enumerate(pkg.resources): - assert res.url == res_dicts[i]['url'] - assert res.format == res_dicts[i]['format'] - assert_equal(pkg.resources[0].id, original_res_ids[0]) - assert pkg.resources[1].id != original_res_ids[1] - - # package resource revisions - prr_q = model.Session.query(model.ResourceRevision) - if prr_q.count() != offset + 2 + 1: # 2 deletions, 1 new one - print offset, prr_q.count() - for prr in prr_q: - print prr - raise AssertionError - prr1 = prr_q.\ - filter_by(revision_id=rev.id).\ - order_by(model.resource_revision_table.c.position).\ - filter_by(state=model.State.ACTIVE).one() - assert prr1.resource_group == rg, prr1.resource_group - assert prr1.url == self.urls[1], '%s != %s' % (prr1.url, self.urls[1]) - assert prr1.revision.id == rev.id, '%s != %s' % (prr1.revision.id, rev.id) - # revision contains package resource revisions - rev_prrs = model.repo.list_changes(rev)[model.Resource] - assert len(rev_prrs) == 3, rev_prrs # 2 deleted, 1 new - - # previous revision still contains previous ones - previous_rev = model.repo.history()[1] - previous_rev_prrs = model.repo.list_changes(previous_rev)[model.Resource] - assert len(previous_rev_prrs) == offset, rev_prrs - - def test_2_update_resources_with_ids(self): - pkg = model.Package.by_name(self.pkgname) - offset = len(self.urls) - assert len(pkg.resources) == offset, pkg.resources - original_res_ids = [res.id for res in pkg.resources] - def print_resources(caption, resources): - print caption, '\n'.join(['' % (res.url, res.format) for res in resources]) - print_resources('BEFORE', pkg.resources) - - rev = model.repo.new_revision() - res_dicts = [ - { #unchanged - 'url':self.urls[0], 'format':self.format, - 'description':self.description, 'hash':self.hash, - 'id':original_res_ids[0]}, - { #id from res 2, but url from res 1 - 'url':self.urls[1], 'format':u'OTHER FORMAT', - 'description':self.description, 'hash':self.hash, - 'id':original_res_ids[2]}, - ] - pkg.update_resources(res_dicts) - model.repo.commit_and_remove() - - pkg = model.Package.by_name(self.pkgname) - print_resources('AFTER', pkg.resources) - assert len(pkg.resources) == len(res_dicts), pkg.resources - for i, res in enumerate(pkg.resources): - assert res.url == res_dicts[i]['url'] - assert res.format == res_dicts[i]['format'] - assert pkg.resources[0].id == original_res_ids[0] - assert pkg.resources[1].id == original_res_ids[2] - -class TestResourceEditOrdering: - @classmethod - def setup(cls): - cls.pkgname = 'test3' - CreateTestData.create_arbitrary({'name':cls.pkgname}) - cls.res_dicts = [ - {'url':'0'}, - {'url':'1'}, - {'url':'2'}, - {'url':'3'}, - {'url':'4'}, - {'url':'5'}, - ] - pkg = model.Package.by_name(cls.pkgname) - resource_group = pkg.resource_groups[0] - rev = model.repo.new_revision() - for res_dict in cls.res_dicts: - res = model.Resource(**res_dict) - model.Session.add(res) - resource_group.resources.append(res) - model.Session.flush() # to create the id - res_dict['id'] = res.id - model.repo.commit_and_remove() - - @classmethod - def teardown(cls): - model.repo.rebuild_db() - - def _try_resources(self, res_dicts, expected_order): - pkg = model.Package.by_name(self.pkgname) - # comment this print to make tests work! - print "RESOURCES", pkg.resource_groups[0].resources - rev = model.repo.new_revision() - pkg.update_resources(res_dicts) - model.repo.commit_and_remove() - - pkg = model.Package.by_name(self.pkgname) - urls = ''.join([res.url for res in pkg.resources]) - assert_equal(urls, expected_order) - - def test_0_new(self): - self._try_resources( - self.res_dicts + [{'url':'6'}], - '0123456') - - def test_1_new_inserted(self): - self._try_resources( - self.res_dicts[:3] + [{'url':'6'}] + self.res_dicts[3:], - '0126345') - - def test_2_delete(self): - self._try_resources( - self.res_dicts[1:4], - '123') - - def test_3_reverse(self): - self._try_resources( - self.res_dicts[::-1], - '543210') - - def test_4_two_new(self): - self._try_resources( - self.res_dicts[0:1] + [{'url':'6'}] + self.res_dicts[1:3] + \ - [{'url':'7'}] + self.res_dicts[4:], - '0612745') - - def test_5_change(self): - self.res_dicts[2]['url'] = '2a' - self._try_resources( - self.res_dicts, - '012a345') - - def test_6_change_and_move(self): - self.res_dicts[2]['url'] = '2a' - self._try_resources( - self.res_dicts[0:2] + self.res_dicts[3:4] + \ - self.res_dicts[2:3] + self.res_dicts[4:], - '0132a45') - - def test_7_change_and_new(self): - self.res_dicts[2]['url'] = '2a' - self._try_resources( - self.res_dicts + [{'url':'6'}], - '012a3456')