[ckan-changes] commit/ckan: 3 new changesets

Bitbucket commits-noreply at bitbucket.org
Sun Jul 10 21:58:34 UTC 2011


3 new changesets in ckan:

http://bitbucket.org/okfn/ckan/changeset/23cf6fe77e82/
changeset:   23cf6fe77e82
user:        kindly
date:        2011-07-10 23:52:16
summary:     [js] ticket 1215 fix it so cant remove last row from resources
affected #:  1 file (90 bytes)

--- a/ckan/public/scripts/flexitable.js	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/public/scripts/flexitable.js	Sun Jul 10 22:52:16 2011 +0100
@@ -62,7 +62,11 @@
     if (confirm('Are you sure you wish to remove this row?')) {
       var row = $(this).parents('tr'),
           following = row.nextAll();
-      
+
+      if (following.length == 0) {
+          row.find('input').val('')
+          return
+      }
       row.remove();
       following.each(function () {
         setRowNumber(this, getRowNumber(this) - 1);


http://bitbucket.org/okfn/ckan/changeset/8a317eadbb36/
changeset:   8a317eadbb36
user:        kindly
date:        2011-07-10 23:57:07
summary:     [js] ticket 1215 fix so can now remove last row also
affected #:  1 file (47 bytes)

--- a/ckan/public/scripts/flexitable.js	Sun Jul 10 22:52:16 2011 +0100
+++ b/ckan/public/scripts/flexitable.js	Sun Jul 10 22:57:07 2011 +0100
@@ -62,8 +62,9 @@
     if (confirm('Are you sure you wish to remove this row?')) {
       var row = $(this).parents('tr'),
           following = row.nextAll();
+          prev = row.prevAll();
 
-      if (following.length == 0) {
+      if (following.length + prev.length  == 0) {
           row.find('input').val('')
           return
       }


http://bitbucket.org/okfn/ckan/changeset/2e0c769d8639/
changeset:   2e0c769d8639
user:        kindly
date:        2011-07-10 23:58:18
summary:     [merge] default
affected #:  33 files (39.6 KB)

--- a/README.txt	Sun Jul 10 22:57:07 2011 +0100
+++ b/README.txt	Sun Jul 10 22:58:18 2011 +0100
@@ -358,6 +358,19 @@
 
    A common error when wanting to run tests against a particular database is to change the sqlalchemy.url in test.ini or test-core.ini. The problem is that these are versioned files and people have checked in these by mistake, creating problems for all other developers and the buildbot. This is easily avoided by only changing the sqlalchemy.url in your local development.ini and testing --with-pylons=test-core.ini.
 
+Common problems running tests
+-----------------------------
+
+* `nose.config.ConfigError: Error reading config file 'setup.cfg': no such option 'with-pylons'`
+
+   This error can result when you run nosetests for two reasons:
+
+   1. Pylons nose plugin failed to run. If this is the case, then within a couple of lines of running `nosetests` you'll see this warning: `Unable to load plugin pylons` followed by an error message. Fix the error here first.
+
+   2. The Python module 'Pylons' is not installed into you Python environment. Confirm this with::
+
+        python -c "import pylons"
+
 Testing extensions
 ------------------
 


--- a/ckan/__init__.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/__init__.py	Sun Jul 10 22:58:18 2011 +0100
@@ -1,4 +1,4 @@
-__version__ = '1.4.2a'
+__version__ = '1.4.3a'
 __description__ = 'Comprehensive Knowledge Archive Network (CKAN) Software'
 __long_description__ = \
 '''The CKAN software is used to run the Comprehensive Knowledge Archive


--- a/ckan/controllers/api.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/controllers/api.py	Sun Jul 10 22:58:18 2011 +0100
@@ -30,10 +30,6 @@
     }
 class ApiController(BaseController):
 
-    content_type_text = 'text/;charset=utf-8'
-    content_type_html = 'text/html;charset=utf-8'
-    content_type_json = 'application/json;charset=utf-8'
-
     def __call__(self, environ, start_response):
         self._identify_user()
         if not self.authorizer.am_authorized(c, model.Action.SITE_READ, model.System):
@@ -45,6 +41,8 @@
             start_response(body, response.headers.items())
             return [response_msg]
         else:
+            # avoid status_code_redirect intercepting error responses
+            environ['pylons.status_code_redirect'] = True
             return BaseController.__call__(self, environ, start_response)
 
     def _finish(self, status_int, response_data=None,
@@ -106,6 +104,14 @@
                             response_data=response_data,
                             content_type='json')
 
+    def _finish_bad_request(self, extra_msg=None):
+        response_data = _('Bad request')
+        if extra_msg:
+            response_data = '%s - %s' % (response_data, extra_msg)
+        return self._finish(status_int=400,
+                            response_data=response_data,
+                            content_type='json')
+
     def _wrap_jsonp(self, callback, response_msg):
         return '%s(%s);' % (callback, response_msg)
 
@@ -140,8 +146,8 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot list entity of this type: %s') % register
+            return self._finish_bad_request(
+                gettext('Cannot list entity of this type: %s') % register)
         try:
             return self._finish_ok(action(context))
         except NotFound, e:
@@ -151,7 +157,6 @@
             return self._finish_not_authz()
 
     def show(self, ver=None, register=None, subregister=None, id=None, id2=None):
-
         action_map = {
             'revision': get.revision_show,
             'group': get.group_show_rest,
@@ -171,8 +176,8 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot read entity of this type: %s') % register
+            return self._finish_bad_request(
+                gettext('Cannot read entity of this type: %s') % register)
         try:
             
             return self._finish_ok(action(context))
@@ -205,15 +210,16 @@
         try:
             request_data = self._get_request_data()
         except ValueError, inst:
-            response.status_int = 400
-            return gettext('JSON Error: %s') % str(inst)
+            return self._finish_bad_request(
+                gettext('JSON Error: %s') % str(inst))
 
         action = action_map.get((register, subregister)) 
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot create new entity of this type: %s %s') % (register, subregister)
+            return self._finish_bad_request(
+                gettext('Cannot create new entity of this type: %s %s') % \
+                (register, subregister))
         try:
             response_data = action(request_data, context)
             location = None
@@ -251,15 +257,15 @@
         try:
             request_data = self._get_request_data()
         except ValueError, inst:
-            response.status_int = 400
-            return gettext('JSON Error: %s') % str(inst)
+            return self._finish_bad_request(
+                gettext('JSON Error: %s') % str(inst))
         action = action_map.get((register, subregister)) 
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot update entity of this type: %s') % \
-                    register.encode('utf-8')
+            return self._finish_bad_request(
+                gettext('Cannot update entity of this type: %s') % \
+                    register.encode('utf-8'))
         try:
             response_data = action(request_data, context)
             return self._finish_ok(response_data)
@@ -294,8 +300,9 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot delete entity of this type: %s %s') % (register, subregister or '')
+            return self._finish_bad_request(
+                gettext('Cannot delete entity of this type: %s %s') %\
+                (register, subregister or ''))
         try:
             response_data = action(context)
             return self._finish_ok(response_data)
@@ -315,29 +322,31 @@
             since_time = None
             if request.params.has_key('since_id'):
                 id = request.params['since_id']
+                if not id:
+                    return self._finish_bad_request(
+                        gettext(u'No revision specified'))
                 rev = model.Session.query(model.Revision).get(id)
                 if rev is None:
-                    response.status_int = 400
-                    return gettext(u'There is no revision with id: %s') % id
+                    return self._finish_not_found(
+                        gettext(u'There is no revision with id: %s') % id)
                 since_time = rev.timestamp
             elif request.params.has_key('since_time'):
                 since_time_str = request.params['since_time']
                 try:
                     since_time = model.strptimestamp(since_time_str)
                 except ValueError, inst:
-                    response.status_int = 400
-                    return 'ValueError: %s' % inst
+                    return self._finish_bad_request('ValueError: %s' % inst)
             else:
-                response.status_int = 400
-                return gettext("Missing search term ('since_id=UUID' or 'since_time=TIMESTAMP')")
+                return self._finish_bad_request(
+                    gettext("Missing search term ('since_id=UUID' or 'since_time=TIMESTAMP')"))
             revs = model.Session.query(model.Revision).filter(model.Revision.timestamp>since_time)
             return self._finish_ok([rev.id for rev in revs])
         elif register == 'package' or register == 'resource':
             try:
                 params = self._get_search_params(request.params)
             except ValueError, e:
-                response.status_int = 400
-                return gettext('Could not read parameters: %r' % e)
+                return self._finish_bad_request(
+                    gettext('Could not read parameters: %r' % e))
             options = QueryOptions()
             for k, v in params.items():
                 if (k in DEFAULT_OPTIONS.keys()):
@@ -373,11 +382,11 @@
                 return self._finish_ok(results)
             except SearchError, e:
                 log.exception(e)
-                response.status_int = 400
-                return gettext('Bad search option: %s') % e
+                return self._finish_bad_request(
+                    gettext('Bad search option: %s') % e)
         else:
-            response.status_int = 404
-            return gettext('Unknown register: %s') % register
+            return self._finish_not_found(
+                gettext('Unknown register: %s') % register)
 
     @classmethod
     def _get_search_params(cls, request_params):


--- a/ckan/controllers/package.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/controllers/package.py	Sun Jul 10 22:58:18 2011 +0100
@@ -377,6 +377,8 @@
                 data, errors = validate(data, schema)
         except NotAuthorized:
             abort(401, _('Unauthorized to read package %s') % '')
+        except NotFound:
+            abort(404, _('Package not found'))
 
         ## hack as db_to_form schema should have this
         data['tag_string'] = ' '.join([tag['name'] for tag in data.get('tags', [])])


--- a/ckan/lib/base.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/lib/base.py	Sun Jul 10 22:58:18 2011 +0100
@@ -146,22 +146,25 @@
         '''
         cls.log.debug('Retrieving request params: %r' % request.params)
         cls.log.debug('Retrieving request POST: %r' % request.POST)
-
+        request_data = None
         if request.POST:
             try:
-                request_data = request.POST.keys() or request.body
+                request_data = request.POST.keys()
             except Exception, inst:
                 msg = _("Could not find the POST data: %r : %s") % \
                       (request.POST, inst)
                 raise ValueError, msg
             request_data = request_data[0]
-        elif request.body:
-            cls.log.debug('Retrieving request POST body: %r' % request.body)
+        else:
             try:
                 request_data = request.body
             except Exception, inst:
-                msg = _("Could not find the POST data: %r : %s") % \
-                      (request.POST, inst)
+                msg = _("Could not extract request body data: %s") % \
+                      (inst)
+                raise ValueError, msg
+            cls.log.debug('Retrieved request body: %r' % request.body)
+            if not request_data:
+                msg = _("No request body data")
                 raise ValueError, msg
         if request_data:
             request_data = json.loads(request_data, encoding='utf8')


--- a/ckan/lib/dictization/__init__.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/lib/dictization/__init__.py	Sun Jul 10 22:58:18 2011 +0100
@@ -122,7 +122,10 @@
 
     if context.get('pending'):
         if session.is_modified(obj, include_collections=False):
-            obj.state = 'pending'
+            if table_dict.get('state', '') == 'deleted':
+                obj.state = 'pending-deleted'
+            else:
+                obj.state = 'pending'
 
     session.add(obj)
 


--- a/ckan/lib/dictization/model_save.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/lib/dictization/model_save.py	Sun Jul 10 22:58:18 2011 +0100
@@ -67,9 +67,6 @@
 
 def package_extras_save(extra_dicts, obj, context):
 
-    allow_partial_update = context.get("allow_partial_update", False)
-    if not extra_dicts and allow_partial_update:
-        return
     model = context["model"]
     session = context["session"]
 
@@ -81,7 +78,10 @@
     for extra_dict in extra_dicts:
         if extra_dict.get("deleted"):
             continue
-        if extras_as_string:
+        
+        if extra_dict['value'] is None:
+            pass
+        elif extras_as_string:
             new_extras[extra_dict["key"]] = extra_dict["value"]
         else:
             new_extras[extra_dict["key"]] = json.loads(extra_dict["value"])
@@ -318,10 +318,15 @@
             updated_extras.update(value)
 
             new_value = []
+            
             for extras_key, extras_value in updated_extras.iteritems():
                 if extras_value is not None:
                     new_value.append({"key": extras_key,
                                       "value": json.dumps(extras_value)})
+                else:
+                    new_value.append({"key": extras_key,
+                                      "value": None})
+
         dictized[key] = new_value
 
     groups = dictized.pop('groups', None)


--- a/ckan/lib/search/sql.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/lib/search/sql.py	Sun Jul 10 22:58:18 2011 +0100
@@ -222,7 +222,7 @@
 
         document_a = u' '.join((pkg_dict.get('name') or u'', pkg_dict.get('title') or u''))
         document_b_items = []
-        for field_name in ['notes', 'tags', 'groups', 'author', 'maintainer']:
+        for field_name in ['notes', 'tags', 'groups', 'author', 'maintainer', 'url']:
             val = pkg_dict.get(field_name)
             if val:
                 document_b_items.append(val)


--- a/ckan/logic/action/create.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/logic/action/create.py	Sun Jul 10 22:58:18 2011 +0100
@@ -91,7 +91,7 @@
     user = context['user']
     id = context["id"]
     id2 = context["id2"]
-    rel = context["rel"]
+    rel_type = context["rel"]
     api = context.get('api_version') or '1'
     ref_package_by = 'id' if api == '2' else 'name'
 
@@ -112,14 +112,14 @@
     ##FIXME should have schema
     comment = data_dict.get('comment', u'')
 
-    existing_rels = pkg1.get_relationships_with(pkg2, rel)
+    existing_rels = pkg1.get_relationships_with(pkg2, rel_type)
     if existing_rels:
         return _update_package_relationship(existing_rels[0],
                                             comment, context)
     rev = model.repo.new_revision()
     rev.author = user
-    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel, pkg2)
-    rel = pkg1.add_relationship(rel, pkg2, comment=comment)
+    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel_type, pkg2)
+    rel = pkg1.add_relationship(rel_type, pkg2, comment=comment)
     model.repo.commit_and_remove()
     relationship_dicts = rel.as_dict(ref_package_by=ref_package_by)
     return relationship_dicts


--- a/ckan/logic/action/get.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/logic/action/get.py	Sun Jul 10 22:58:18 2011 +0100
@@ -30,6 +30,9 @@
     limit = context.get("limit")
 
     q = ckan.authz.Authorizer().authorized_query(user, model.PackageRevision)
+    q = q.filter(model.PackageRevision.state=='active')
+    q = q.filter(model.PackageRevision.current==True)
+
     q = q.order_by(model.package_revision_table.c.revision_timestamp.desc())
     if limit:
         q = q.limit(limit)
@@ -212,7 +215,7 @@
 
 def tag_show(context):
     model = context['model']
-    api = context.get('api') or '1'
+    api = context.get('api_version') or '1'
     id = context['id']
     ref_package_by = 'id' if api == '2' else 'name'
     obj = model.Tag.get(id) #TODO tags


--- a/ckan/logic/action/update.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/logic/action/update.py	Sun Jul 10 22:58:18 2011 +0100
@@ -6,7 +6,12 @@
 from ckan.plugins import PluginImplementations, IGroupController, IPackageController
 from ckan.logic import NotFound, check_access, NotAuthorized, ValidationError
 from ckan.lib.base import _
-from ckan.lib.dictization.model_dictize import group_dictize, package_dictize
+from ckan.lib.dictization.model_dictize import (package_dictize,
+                                                package_to_api1,
+                                                package_to_api2,
+                                                group_dictize,
+                                                group_to_api1,
+                                                group_to_api2)
 from ckan.lib.dictization.model_save import (group_api_to_dict,
                                              package_api_to_dict,
                                              group_dict_save,
@@ -154,6 +159,7 @@
 
     if pkg is None:
         raise NotFound(_('Package was not found.'))
+    context["id"] = pkg.id
 
     check_access(pkg, model.Action.EDIT, context)
 
@@ -171,7 +177,7 @@
         if 'message' in context:
             rev.message = context['message']
         else:
-            rev.message = _(u'REST API: Create object %s') % data.get("name")
+            rev.message = _(u'REST API: Update object %s') % data.get("name")
 
     pkg = package_dict_save(data, context)
 
@@ -255,7 +261,7 @@
     if 'message' in context:
         rev.message = context['message']
     else:
-        rev.message = _(u'REST API: Create object %s') % data.get("name")
+        rev.message = _(u'REST API: Update object %s') % data.get("name")
 
     group = group_dict_save(data, context)
 
@@ -274,19 +280,38 @@
 
     model = context['model']
     id = context["id"]
+    api = context.get('api_version') or '1'
     pkg = model.Package.get(id)
     context["package"] = pkg
     context["allow_partial_update"] = True
     dictized_package = package_api_to_dict(data_dict, context)
-    return package_update(dictized_package, context)
+    dictized_after = package_update(dictized_package, context)
+
+    pkg = context['package']
+
+    if api == '1':
+        package_dict = package_to_api1(pkg, context)
+    else:
+        package_dict = package_to_api2(pkg, context)
+
+    return package_dict
 
 def group_update_rest(data_dict, context):
 
     model = context['model']
     id = context["id"]
+    api = context.get('api_version') or '1'
     group = model.Group.get(id)
     context["group"] = group
     context["allow_partial_update"] = True
     dictized_package = group_api_to_dict(data_dict, context)
-    return group_update(dictized_package, context)
+    dictized_after = group_update(dictized_package, context)
 
+    group = context['group']
+
+    if api == '1':
+        group_dict = group_to_api1(group, context)
+    else:
+        group_dict = group_to_api2(group, context)
+
+    return group_dict


--- a/ckan/logic/schema.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/logic/schema.py	Sun Jul 10 22:58:18 2011 +0100
@@ -167,7 +167,7 @@
     schema = {
         'id': [ignore],
         'key': [not_empty, unicode],
-        'value': [not_missing, unicode],
+        'value': [not_missing],
         'state': [ignore],
         'deleted': [ignore_missing],
         'revision_timestamp': [ignore],


--- a/ckan/model/package.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/model/package.py	Sun Jul 10 22:58:18 2011 +0100
@@ -225,7 +225,7 @@
             subject = related_package
             object_ = self
         else:
-            raise NotImplementedError, 'Package relationship type: %r' % type_
+            raise KeyError, 'Package relationship type: %r' % type_
 
         rels = self.get_relationships(with_package=related_package,
                                       type=type_, active=False, direction="forward")


--- a/ckan/tests/functional/api/base.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/tests/functional/api/base.py	Sun Jul 10 22:58:18 2011 +0100
@@ -69,7 +69,7 @@
         return '%s%s' % (base, path)
 
     def package_offset(self, package_name=None):
-        if package_name == None:
+        if package_name is None:
             # Package Register
             return self.offset('/rest/package')
         else:
@@ -79,14 +79,14 @@
 
     def package_ref_from_name(self, package_name):
         package = self.get_package_by_name(unicode(package_name))
-        if package == None:
+        if package is None:
             return package_name
         else:
             return self.ref_package(package)
 
     def package_id_from_ref(self, package_name):
         package = self.get_package_by_name(unicode(package_name))
-        if package == None:
+        if package is None:
             return package_name
         else:
             return self.ref_package(package)
@@ -96,7 +96,7 @@
         return getattr(package, self.ref_package_by)
 
     def group_offset(self, group_name=None):
-        if group_name == None:
+        if group_name is None:
             # Group Register
             return self.offset('/rest/group')
         else:
@@ -106,7 +106,7 @@
 
     def group_ref_from_name(self, group_name):
         group = self.get_group_by_name(unicode(group_name))
-        if group == None:
+        if group is None:
             return group_name
         else:
             return self.ref_group(group)
@@ -115,11 +115,52 @@
         assert self.ref_group_by in ['id', 'name']
         return getattr(group, self.ref_group_by)
 
+    def revision_offset(self, revision_id=None):
+        if revision_id is None:
+            # Revision Register
+            return self.offset('/rest/revision')
+        else:
+            # Revision Entity
+            return self.offset('/rest/revision/%s' % revision_id)
+
+    def rating_offset(self, package_name=None):
+        if package_name is None:
+            # Revision Register
+            return self.offset('/rest/rating')
+        else:
+            # Revision Entity
+            package_ref = self.package_ref_from_name(package_name)
+            return self.offset('/rest/rating/%s' % package_ref)
+
+    def relationship_offset(self, package_1_name=None,
+                            relationship_type=None,
+                            package_2_name=None,
+                            ):
+        assert package_1_name
+        package_1_ref = self.package_ref_from_name(package_1_name)
+        if package_2_name is None:
+            if not relationship_type:
+                return self.offset('/rest/package/%s/relationships' % \
+                                   package_1_ref)
+            else:
+                return self.offset('/rest/package/%s/%s' %
+                                   (package_1_ref, relationship_type))
+        else:
+            package_2_ref = self.package_ref_from_name(package_2_name)
+            if not relationship_type:
+                return self.offset('/rest/package/%s/relationships/%s' % \
+                                   (package_1_ref, package_2_ref))
+            else:
+                return self.offset('/rest/package/%s/%s/%s' % \
+                                   (package_1_ref,
+                                    relationship_type,
+                                    package_2_ref))
+
     def anna_offset(self, postfix=''):
         return self.package_offset('annakarenina') + postfix
 
     def tag_offset(self, tag_name=None):
-        if tag_name == None:
+        if tag_name is None:
             # Tag Register
             return self.offset('/rest/tag')
         else:
@@ -129,7 +170,7 @@
 
     def tag_ref_from_name(self, tag_name):
         tag = self.get_tag_by_name(unicode(tag_name))
-        if tag == None:
+        if tag is None:
             return tag_name
         else:
             return self.ref_tag(tag)
@@ -199,9 +240,10 @@
     def assert_msg_represents_russian(self, msg):
         data = self.loads(msg)
         pkgs = set(data)
-        expected_pkgs = set(['annakarenina', 'warandpeace'])
-        missing_pkgs = expected_pkgs - pkgs
-        assert not missing_pkgs, missing_pkgs
+        expected_pkgs = set([self.package_ref_from_name('annakarenina'),
+                             self.package_ref_from_name('warandpeace')])
+        differences = expected_pkgs ^ pkgs
+        assert not differences, '%r != %r' % (pkgs, expected_pkgs)
 
     def data_from_res(self, res):
         return self.loads(res.body)
@@ -218,6 +260,16 @@
         except ValueError, inst:
             raise Exception, "Couldn't loads string '%s': %s" % (chars, inst)
 
+    def assert_json_response(self, res, expected_in_body=None):
+        content_type = res.header_dict['Content-Type']
+        assert 'application/json' in content_type, content_type
+        res_json = self.loads(res.body)
+        if expected_in_body:
+            assert expected_in_body in res_json or \
+                   expected_in_body in str(res_json), \
+                   'Expected to find %r in JSON response %r' % \
+                   (expected_in_body, res_json)
+
 # Todo: Rename to Version1TestCase.
 class Api1TestCase(ApiTestCase):
 
@@ -297,8 +349,6 @@
     def teardown(self):
         model.Session.remove()
         model.repo.rebuild_db()
-        #self.delete_common_fixtures()
-        #self.commit_remove()
         super(BaseModelApiTestCase, self).teardown()
 
     def init_extra_environ(self):
@@ -312,10 +362,38 @@
         application/x-www-form-urlencoded)
 
         '''
+        return self.http_request(offset, data, content_type='application/json',
+                                 request_method='POST',
+                                 content_length=len(data),
+                                 status=status, extra_environ=extra_environ)
+
+    def delete_request(self, offset, status=None, extra_environ=None):
+        ''' Sends a delete request. Similar to the paste.delete but it
+        does not send the content type or content length.
+        '''
+        return self.http_request(offset, data='', content_type=None,
+                                 request_method='DELETE',
+                                 content_length=None,
+                                 status=status,
+                                 extra_environ=extra_environ)
+
+    def http_request(self, offset, data,
+                     content_type='application/json',
+                     request_method='POST',
+                     content_length=None,
+                     status=None,
+                     extra_environ=None):
+        ''' Posts data in the body in a user-specified format.
+        (rather than Paste Fixture\'s default Content-Type of
+        application/x-www-form-urlencoded)
+
+        '''
         environ = self.app._make_environ()
-        environ['CONTENT_TYPE'] = 'application/json'
-        environ['CONTENT_LENGTH'] = str(len(data))
-        environ['REQUEST_METHOD'] = 'POST'
+        if content_type:
+            environ['CONTENT_TYPE'] = content_type
+        if content_length is not None:
+            environ['CONTENT_LENGTH'] = str(content_length)
+        environ['REQUEST_METHOD'] = request_method
         environ['wsgi.input'] = StringIO(data)
         if extra_environ:
             environ.update(extra_environ)


--- a/ckan/tests/functional/api/model/test_group.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/tests/functional/api/model/test_group.py	Sun Jul 10 22:58:18 2011 +0100
@@ -1,5 +1,7 @@
 import copy
 
+from ckan import model
+
 from nose.tools import assert_equal 
 
 from ckan.tests.functional.api.base import BaseModelApiTestCase
@@ -29,6 +31,10 @@
         # check group object
         group = self.get_group_by_name(self.testgroupvalues['name'])
         assert group
+        assert group.title == self.testgroupvalues['title'], group
+        assert group.description == self.testgroupvalues['description'], group
+        pkg_names = [pkg.name for pkg in group.packages]
+        assert set(pkg_names) == set(('annakarenina', 'warandpeace')), pkg_names
 
         # check register updated
         res = self.app.get(offset, status=self.STATUS_200_OK)
@@ -53,11 +59,15 @@
         res = self.app.post(offset, params=postparams,
                             status=self.STATUS_409_CONFLICT,
                             extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'Group name already exists')
     
     def test_entity_get_ok(self):
         offset = self.group_offset(self.roger.name)
         res = self.app.get(offset, status=self.STATUS_200_OK)
         self.assert_msg_represents_roger(msg=res.body)
+        assert self.package_ref_from_name('annakarenina') in res, res
+        assert self.group_ref_from_name('roger') in res, res
+        assert not self.package_ref_from_name('warandpeace') in res, res
 
     def test_entity_get_then_post(self):
         # (ticket 662) Ensure an entity you 'get' from a register can be
@@ -70,6 +80,123 @@
                             status=self.STATUS_200_OK,
                             extra_environ=self.extra_environ)
 
+    def test_05_get_group_entity_not_found(self):
+        offset = self.offset('/rest/group/22222')
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+    def test_10_edit_group(self):
+        # create a group with testgroupvalues
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        if not group:
+            offset = self.offset('/rest/group')
+            postparams = '%s=1' % self.dumps(self.testgroupvalues)
+            res = self.app.post(offset, params=postparams, status=[201],
+                    extra_environ=self.extra_environ)
+            model.Session.remove()
+            group = model.Group.by_name(self.testgroupvalues['name'])
+        assert group
+        assert len(group.packages) == 2, group.packages
+        user = model.User.by_name(self.user_name)
+        model.setup_default_user_roles(group, [user])
+
+        # edit it
+        group_vals = {'name':u'somethingnew', 'title':u'newtesttitle',
+                      'packages':[u'annakarenina']}
+        offset = self.group_offset(self.testgroupvalues['name'])
+        postparams = '%s=1' % self.dumps(group_vals)
+        res = self.app.post(offset, params=postparams, status=[200],
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        group = model.Session.query(model.Group).filter_by(name=group_vals['name']).one()
+        assert group.name == group_vals['name']
+        assert group.title == group_vals['title']
+        assert len(group.packages) == 1, group.packages
+        assert group.packages[0].name == group_vals['packages'][0]
+
+    def test_10_edit_group_name_duplicate(self):
+        # create a group with testgroupvalues
+        if not model.Group.by_name(self.testgroupvalues['name']):
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = self.testgroupvalues['name']
+            model.Session.commit()
+
+            group = model.Group.by_name(self.testgroupvalues['name'])
+            model.setup_default_user_roles(group, [self.user])
+            rev = model.repo.new_revision()
+            model.repo.commit_and_remove()
+        assert model.Group.by_name(self.testgroupvalues['name'])
+        
+        # create a group with name 'dupname'
+        dupname = u'dupname'
+        if not model.Group.by_name(dupname):
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = dupname
+            model.Session.commit()
+        assert model.Group.by_name(dupname)
+
+        # edit first group to have dupname
+        group_vals = {'name':dupname}
+        offset = self.group_offset(self.testgroupvalues['name'])
+        postparams = '%s=1' % self.dumps(group_vals)
+        res = self.app.post(offset, params=postparams, status=[409],
+                            extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'Group name already exists')
+        
+    def test_11_delete_group(self):
+        # Test Groups Entity Delete 200.
+
+        # create a group with testgroupvalues
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        if not group:
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = self.testgroupvalues['name']
+            model.repo.commit_and_remove()
+
+            rev = model.repo.new_revision()
+            group = model.Group.by_name(self.testgroupvalues['name'])
+            model.setup_default_user_roles(group, [self.user])
+            model.repo.commit_and_remove()
+        assert group
+        user = model.User.by_name(self.user_name)
+        model.setup_default_user_roles(group, [user])
+
+        # delete it
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.delete(offset, status=[200],
+                extra_environ=self.extra_environ)
+
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        assert group
+        assert group.state == 'deleted', group.state
+
+        res = self.app.get(offset, status=[403])
+        self.assert_json_response(res, 'Access denied')
+        res = self.app.get(offset, status=[200],
+                           extra_environ=self.extra_environ)
+
+    def test_12_get_group_404(self):
+        # Test Package Entity Get 404.
+        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+    def test_13_delete_group_404(self):
+        # Test Packages Entity Delete 404.
+        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.delete(offset, status=[404],
+                              extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'not found')
+
+
 class TestGroupsVersion1(Version1TestCase, GroupsTestCase): pass
 class TestGroupsVersion2(Version2TestCase, GroupsTestCase): pass
 class TestGroupsUnversioned(UnversionedTestCase, GroupsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_licenses.py	Sun Jul 10 22:58:18 2011 +0100
@@ -0,0 +1,32 @@
+from nose.tools import assert_equal 
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class LicensesTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = False
+
+    def test_register_get_ok(self):
+        from ckan.model.license import LicenseRegister
+        register = LicenseRegister()
+        assert len(register), "No changesets found in model."
+        offset = self.offset('/rest/licenses')
+        res = self.app.get(offset, status=[200])
+        licenses_data = self.data_from_res(res)
+        assert len(licenses_data) == len(register), (len(licenses_data), len(register))
+        for license_data in licenses_data:
+            id = license_data['id']
+            license = register[id]
+            assert license['title'] == license.title
+            assert license['url'] == license.url
+
+
+class TestLicensesVersion1(Version1TestCase, LicensesTestCase): pass
+class TestLicensesVersion2(Version2TestCase, LicensesTestCase): pass
+class TestLicensesUnversioned(UnversionedTestCase, LicensesTestCase): pass


--- a/ckan/tests/functional/api/model/test_package.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/tests/functional/api/model/test_package.py	Sun Jul 10 22:58:18 2011 +0100
@@ -1,3 +1,5 @@
+import copy
+
 from nose.tools import assert_equal
 
 from ckan.tests.functional.api.base import BaseModelApiTestCase
@@ -35,6 +37,15 @@
         res = self.app.post(offset, params=postparams,
                             status=self.STATUS_201_CREATED,
                             extra_environ=self.extra_environ)
+        
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], self.package_fixture_data['name'])
+        assert_equal(pkg['title'], self.package_fixture_data['title'])
+        assert_equal(set(pkg['tags']), set(self.package_fixture_data['tags']))
+        assert_equal(len(pkg['resources']), len(self.package_fixture_data['resources']))
+        assert_equal(pkg['extras'], self.package_fixture_data['extras'])
+
         # Check the value of the Location header.
         location = res.header('Location')
         assert offset in location
@@ -103,7 +114,37 @@
         assert package
         self.assert_equal(package.title, self.package_fixture_data['title'])
         
+    def test_register_post_bad_content_type(self):
+        assert not self.get_package_by_name(self.package_fixture_data['name'])
+        offset = self.package_offset()
+        data = self.dumps(self.package_fixture_data)
+        res = self.http_request(offset, data,
+                                content_type='something/unheard_of',
+                                status=[self.STATUS_400_BAD_REQUEST,
+                                        self.STATUS_201_CREATED],
+                                extra_environ=self.extra_environ)
+        self.remove()
+        # Some versions of webob work, some don't. No matter, we record this
+        # behaviour.
+        package = self.get_package_by_name(self.package_fixture_data['name'])
+        if res.status == self.STATUS_400_BAD_REQUEST:
+            # Check there is no database record.
+            assert not package
+        else:
+            assert package        
 
+    def test_register_post_json(self):
+        assert not self.get_package_by_name(self.package_fixture_data['name'])
+        offset = self.package_offset()
+        data = self.dumps(self.package_fixture_data)
+        res = self.post_json(offset, data, status=self.STATUS_201_CREATED,
+                             extra_environ=self.extra_environ)
+        # Check the database record.
+        self.remove()
+        package = self.get_package_by_name(self.package_fixture_data['name'])
+        assert package
+        self.assert_equal(package.title, self.package_fixture_data['title'])
+        
     def test_register_post_bad_request(self):
         test_params = {
             'name':u'testpackage06_400',
@@ -200,7 +241,8 @@
     def create_package_roles_revision(self, package_data):
         self.create_package(admins=[self.user], data=package_data)
 
-    def assert_package_update_ok(self, package_ref_attribute):
+    def assert_package_update_ok(self, package_ref_attribute,
+                                 method_str):
         old_fixture_data = {
             'name': self.package_fixture_data['name'],
             'url': self.package_fixture_data['url'],
@@ -237,10 +279,28 @@
             'tags': [u'tag1', u'tag2', u'tag4', u'tag5'],
         }
         self.create_package_roles_revision(old_fixture_data)
-        offset = self.package_offset(old_fixture_data['name'])
+        pkg = self.get_package_by_name(old_fixture_data['name'])
+        # This is the one occasion where we reference package explicitly
+        # by name or ID, rather than use the value from self.ref_package_by
+        # because you should be able to specify the package both ways round
+        # for both versions of the API.
+        package_ref = getattr(pkg, package_ref_attribute)
+        offset = self.offset('/rest/package/%s' % package_ref)
         params = '%s=1' % self.dumps(new_fixture_data)
-        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
-                            extra_environ=self.extra_environ)
+        method_func = getattr(self.app, method_str)
+        res = method_func(offset, params=params, status=self.STATUS_200_OK,
+                          extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        assert_equal(pkg['title'], new_fixture_data['title'])
+        assert_equal(set(pkg['tags']), set(new_fixture_data['tags']))
+        assert_equal(len(pkg['resources']), len(new_fixture_data['resources']))
+        expected_extras = copy.deepcopy(new_fixture_data['extras'])
+        del expected_extras['key2']
+        expected_extras['key1'] = old_fixture_data['extras']['key1']
+        assert_equal(pkg['extras'], expected_extras)
 
         # Check submitted field have changed.
         self.remove()
@@ -285,10 +345,78 @@
         assert not package.extras.has_key('key2')
 
     def test_package_update_ok_by_id(self):
-        self.assert_package_update_ok('id')
+        self.assert_package_update_ok('id', 'post')
 
     def test_entity_update_ok_by_name(self):
-        self.assert_package_update_ok('name')
+        self.assert_package_update_ok('name', 'post')
+
+    def test_package_update_ok_by_id_by_put(self):
+        self.assert_package_update_ok('id', 'put')
+
+    def test_entity_update_ok_by_name_by_put(self):
+        self.assert_package_update_ok('name', 'put')
+
+    def test_package_update_delete_last_extra(self):
+        old_fixture_data = {
+            'name': self.package_fixture_data['name'],
+            'extras': {
+                u'key1': u'val1',
+            },
+        }
+        new_fixture_data = {
+            'name':u'somethingnew',
+            'extras': {
+                u'key1': None, 
+                },
+        }
+        self.create_package_roles_revision(old_fixture_data)
+        offset = self.package_offset(old_fixture_data['name'])
+        params = '%s=1' % self.dumps(new_fixture_data)
+        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
+                            extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        expected_extras = copy.deepcopy(new_fixture_data['extras'])
+        del expected_extras['key1']
+        assert_equal(pkg['extras'], expected_extras)
+
+        # Check extra was deleted
+        self.remove()
+        package = self.get_package_by_name(new_fixture_data['name'])
+        # - title
+        self.assert_equal(package.extras, {})
+
+    def test_package_update_do_not_delete_last_extra(self):
+        old_fixture_data = {
+            'name': self.package_fixture_data['name'],
+            'extras': {
+                u'key1': u'val1',
+            },
+        }
+        new_fixture_data = {
+            'name':u'somethingnew',
+            'extras': {}, # no extras specified, but existing
+                          # ones should be left alone
+        }
+        self.create_package_roles_revision(old_fixture_data)
+        offset = self.package_offset(old_fixture_data['name'])
+        params = '%s=1' % self.dumps(new_fixture_data)
+        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
+                            extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        expected_extras = {u'key1': u'val1'} # should not be deleted
+        assert_equal(pkg['extras'], expected_extras)
+
+        # Check extra was not deleted
+        self.remove()
+        package = self.get_package_by_name(new_fixture_data['name'])
+        # - title
+        assert len(package.extras) == 1, package.extras
 
     def test_entity_update_conflict(self):
         package1_name = self.package_fixture_data['name']
@@ -300,18 +428,19 @@
         package1_offset = self.package_offset(package1_name)
         self.post(package1_offset, package2_data, self.STATUS_409_CONFLICT)
 
+    def test_entity_update_empty(self):
+        package1_name = self.package_fixture_data['name']
+        package1_data = {'name': package1_name}
+        package1 = self.create_package_roles_revision(package1_data)
+        package2_data = '' # this is the error
+        package1_offset = self.package_offset(package1_name)
+        self.app.put(package1_offset, package2_data,
+                     status=self.STATUS_400_BAD_REQUEST)
+
     def test_entity_delete_ok(self):
         # create a package with package_fixture_data
         if not self.get_package_by_name(self.package_fixture_data['name']):
-            rev = model.repo.new_revision()
-            package = model.Package()
-            model.Session.add(package)
-            package.name = self.package_fixture_data['name']
-            model.repo.commit_and_remove()
-            rev = model.repo.new_revision()
-            package = self.get_package_by_name(self.package_fixture_data['name'])
-            model.setup_default_user_roles(package, [self.user])
-            model.repo.commit_and_remove()
+            self.create_package(admins=[self.user], name=self.package_fixture_data['name'])
         assert self.get_package_by_name(self.package_fixture_data['name'])
         # delete it
         offset = self.package_offset(self.package_fixture_data['name'])
@@ -321,6 +450,19 @@
         self.assert_equal(package.state, 'deleted')
         model.Session.remove()
 
+    def test_entity_delete_ok_without_request_headers(self):
+        # create a package with package_fixture_data
+        if not self.get_package_by_name(self.package_fixture_data['name']):
+            self.create_package(admins=[self.user], name=self.package_fixture_data['name'])
+        assert self.get_package_by_name(self.package_fixture_data['name'])
+        # delete it
+        offset = self.package_offset(self.package_fixture_data['name'])
+        res = self.delete_request(offset, status=self.STATUS_200_OK,
+                                  extra_environ=self.extra_environ)
+        package = self.get_package_by_name(self.package_fixture_data['name'])
+        self.assert_equal(package.state, 'deleted')
+        model.Session.remove()
+
     def test_entity_delete_not_found(self):
         package_name = u'random_one'
         assert not model.Session.query(model.Package).filter_by(name=package_name).count()
@@ -363,7 +505,52 @@
         assert len(revisions) == 3, len(revisions)
 
 
-class TestPackagesVersion1(Version1TestCase, PackagesTestCase): pass
+class TestPackagesVersion1(Version1TestCase, PackagesTestCase):
+    def test_06_create_pkg_using_download_url(self):
+        test_params = {
+            'name':u'testpkg06',
+            'download_url':u'ftp://ftp.monash.edu.au/pub/nihongo/JMdict.gz',
+            }
+        offset = self.package_offset()
+        postparams = '%s=1' % self.dumps(test_params)
+        res = self.app.post(offset, params=postparams, 
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(test_params['name'])
+        assert pkg
+        assert pkg.name == test_params['name'], pkg
+        assert len(pkg.resources) == 1, pkg.resources
+        assert pkg.resources[0].url == test_params['download_url'], pkg.resources[0]
+
+    def test_10_edit_pkg_with_download_url(self):
+        test_params = {
+            'name':u'testpkg10',
+            'download_url':u'testurl',
+            }
+        rev = model.repo.new_revision()
+        pkg = model.Package()
+        model.Session.add(pkg)
+        pkg.name = test_params['name']
+        pkg.download_url = test_params['download_url']
+        model.Session.commit()
+
+        pkg = self.get_package_by_name(test_params['name'])
+        model.setup_default_user_roles(pkg, [self.user])
+        rev = model.repo.new_revision()
+        model.repo.commit_and_remove()
+        assert self.get_package_by_name(test_params['name'])
+
+        # edit it
+        pkg_vals = {'download_url':u'newurl'}
+        offset = self.package_offset(test_params['name'])
+        postparams = '%s=1' % self.dumps(pkg_vals)
+        res = self.app.post(offset, params=postparams, status=[200],
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = model.Session.query(model.Package).filter_by(name=test_params['name']).one()
+        assert len(pkg.resources) == 1, pkg.resources
+        assert pkg.resources[0].url == pkg_vals['download_url']
+
 class TestPackagesVersion2(Version2TestCase, PackagesTestCase): pass
 class TestPackagesUnversioned(UnversionedTestCase, PackagesTestCase): pass
 


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_ratings.py	Sun Jul 10 22:58:18 2011 +0100
@@ -0,0 +1,94 @@
+from nose.tools import assert_equal 
+from nose.plugins.skip import SkipTest
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RatingsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+
+    def test_register_get(self):
+        raise SkipTest('"Rating register get" functionality is not implemented')
+        rating1 = model.Rating(user_ip_address='1.2.3.4',
+                               package=self.anna,
+                               rating=4.0)
+        rating2 = model.Rating(user=model.User.by_name(u'annafan'),
+                               package=self.anna,
+                               rating=2.0)
+        model.Session.add_all((rating1, rating2))
+        model.repo.commit_and_remove()
+
+        offset = self.rating_offset()
+        res = self.app.get(offset, status=[200])
+
+    def test_entity_get(self):
+        raise SkipTest('"Rating entity get" functionality is not implemented')
+        rating = model.Rating(user_ip_address='1.2.3.4',
+                              package=self.anna,
+                              rating=4.0)
+        model.Session.add(rating)
+        model.repo.commit_and_remove()
+
+        offset = self.rating_offset(self.anna.name)
+        res = self.app.get(offset, status=[200])
+        assert_equal(res, rating_opts['rating'])
+
+    def test_register_post(self):
+        # Test Rating Register Post 200.
+        self.clear_all_tst_ratings()
+        offset = self.rating_offset()
+        rating_opts = {'package':u'warandpeace',
+                       'rating':5}
+        pkg_name = rating_opts['package']
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[201],
+                extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(pkg_name)
+        assert pkg
+        assert len(pkg.ratings) == 1
+        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
+
+        # Get package to see rating
+        offset = self.package_offset(pkg_name)
+        res = self.app.get(offset, status=[200])
+        assert pkg_name in res, res
+        assert '"ratings_average": %s.0' % rating_opts['rating'] in res, res
+        assert '"ratings_count": 1' in res, res
+        
+        model.Session.remove()
+        
+        # Rerate package
+        offset = self.rating_offset()
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[201],
+                extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(pkg_name)
+        assert pkg
+        assert len(pkg.ratings) == 1
+        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
+
+    def test_entity_post_invalid(self):
+        self.clear_all_tst_ratings()
+        offset = self.rating_offset()
+        rating_opts = {'package':u'warandpeace',
+                       'rating':0}
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[409],
+                            extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'rating')
+        model.Session.remove()
+        pkg = self.get_package_by_name(rating_opts['package'])
+        assert pkg
+        assert len(pkg.ratings) == 0
+
+class TestRatingsVersion1(Version1TestCase, RatingsTestCase): pass
+class TestRatingsVersion2(Version2TestCase, RatingsTestCase): pass
+class TestRatingsUnversioned(UnversionedTestCase, RatingsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_relationships.py	Sun Jul 10 22:58:18 2011 +0100
@@ -0,0 +1,242 @@
+from nose.tools import assert_equal 
+from nose.plugins.skip import SkipTest
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RelationshipsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+
+    @classmethod
+    def setup_class(cls):
+#        super(RelationshipsTestCase, cls).setup_class()
+        cls.testsysadmin = model.User.by_name(u'testsysadmin')
+        cls.comment = u'Comment umlaut: \xfc.'
+
+    def test_01_create_and_read_relationship(self):
+        # check anna has no existing relationships
+        assert not self.anna.get_relationships()
+        assert self.get_relationships(package1_name='annakarenina') == [], self.get_relationships(package1_name='annakarenina')
+        assert self.get_relationships(package1_name='annakarenina',
+                                       package2_name='warandpeace') == []
+        assert self.get_relationships(package1_name='annakarenina',
+                                       type='child_of',
+                                       package2_name='warandpeace') == 404
+        assert self.get_relationships_via_package('annakarenina') == []
+
+        # Create a relationship.
+        self.create_annakarenina_parent_of_war_and_peace()
+
+        # Check package relationship register.
+        rels = self.get_relationships(package1_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # Todo: Name this?
+        # Check '/api/VER/rest/package/annakarenina/relationships/warandpeace'
+        rels = self.get_relationships(package1_name='annakarenina',
+                                       package2_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # Todo: Name this?
+        # check '/api/VER/rest/package/annakarenina/parent_of/warandpeace'
+        rels = self.get_relationships(package1_name='annakarenina',
+                                       type='parent_of',
+                                       package2_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # same checks in reverse direction
+        rels = self.get_relationships(package1_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        rels = self.get_relationships(package1_name='warandpeace',
+                                       package2_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        rels = self.get_relationships(package1_name='warandpeace',
+                                       type='child_of',
+                                      package2_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        # Check package entity relationships.
+        rels = self.get_relationships_via_package('annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+        
+    def test_03_update_relationship(self):
+        # Create a relationship.
+        self.create_annakarenina_parent_of_war_and_peace()
+
+        # Check the relationship before update.
+        self.check_relationships_rest('warandpeace', 'annakarenina',
+                                      [{'type': 'child_of',
+                                        'comment': self.comment}])
+
+        # Update the relationship.
+        new_comment = u'New comment.'
+        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
+
+        # Check the relationship after update.
+        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
+
+        # Repeat update with same values, to check it remains the same?
+
+        # Update the relationship.
+        new_comment = u'New comment.'
+        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
+
+        # Check the relationship after update.
+        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
+
+    def test_05_delete_relationship(self):
+        self.create_annakarenina_parent_of_war_and_peace()
+        self.update_annakarenina_parent_of_war_and_peace()
+        expected = [ {'type': 'child_of', 'comment': u'New comment.'} ]
+        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
+
+        self.delete_annakarenina_parent_of_war_and_peace()
+
+        expected = []
+        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
+
+    def test_create_relationship_unknown(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'unheard_of_type', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':self.comment})
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
+    def test_create_relationship_incorrectly(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'relationships', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'type':'parent_of'})
+        # type should do in the URL offset, not the params.
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
+    def create_annakarenina_parent_of_war_and_peace(self):
+        # Create package relationship.
+        # Todo: Redesign this in a RESTful style, so that a relationship is 
+        # created by posting a relationship to a relationship **register**.
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':self.comment})
+        res = self.app.post(offset, params=postparams, status=[201],
+                            extra_environ=self.extra_environ)
+        # Check the response
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+        
+        # Check the model, directly.
+        rels = self.anna.get_relationships()
+        assert len(rels) == 1, rels
+        assert rels[0].type == 'child_of'
+        assert rels[0].subject.name == 'warandpeace'
+        assert rels[0].object.name == 'annakarenina'
+
+    def update_annakarenina_parent_of_war_and_peace(self, comment=u'New comment.'):
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':comment})
+        res = self.app.post(offset, params=postparams, status=[201], extra_environ=self.extra_environ)
+        # Check the response (normalised to 'child_of')
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+
+        # Check the model, directly (normalised to 'child_of')
+        rels = self.anna.get_relationships()
+        assert len(rels) == 1, rels
+        assert rels[0].type == 'child_of'
+        assert rels[0].subject.name == 'warandpeace'
+        assert rels[0].object.name == 'annakarenina'
+        return res
+
+    def delete_annakarenina_parent_of_war_and_peace(self):
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        res = self.app.delete(offset, status=[200], extra_environ=self.extra_environ)
+        assert not res.body
+
+    def get_relationships(self, package1_name=u'annakarenina', type='relationships', package2_name=None):
+        offset = self.relationship_offset(package1_name, type, package2_name)
+        allowable_statuses = [200]
+        if type:
+            allowable_statuses.append(404)
+        res = self.app.get(offset, status=allowable_statuses)
+        if res.status == 200:
+            res_dict = self.data_from_res(res) if res.body else []
+            return res_dict
+        else:
+            return 404
+
+    def get_relationships_via_package(self, package1_name):
+        offset = self.package_offset(package1_name)
+        res = self.app.get(offset, status=200)
+        res_dict = self.data_from_res(res)
+        return res_dict['relationships']
+
+    def assert_len_relationships(self, relationships, expected_relationships):
+        len_relationships = len(relationships)
+        len_expected_relationships = len(expected_relationships)
+        if len_relationships != len_expected_relationships:
+            msg = 'Found %i relationships, ' % len_relationships
+            msg += 'but expected %i.' % len_expected_relationships
+            if len_relationships:
+                msg += ' Found: '
+                for r in relationships:
+                    msg += '%s %s %s; ' % (r['subject'], r['type'], r['object'])
+                msg += '.'
+            raise Exception, msg
+
+    def check_relationships_rest(self, pkg1_name, pkg2_name=None,
+                                 expected_relationships=[]):
+        rels = self.get_relationships(package1_name=pkg1_name,
+                                      package2_name=pkg2_name)
+        self.assert_len_relationships(rels, expected_relationships) 
+        for rel in rels:
+            the_expected_rel = None
+            for expected_rel in expected_relationships:
+                if expected_rel['type'] == rel['type'] and \
+                   (pkg2_name or expected_rel['object'] == pkg2_name):
+                    the_expected_rel = expected_rel
+                    break
+            if not the_expected_rel:
+                raise Exception('Unexpected relationship: %s %s %s' %
+                                (rel['subject'], rel['type'], rel['object']))
+            for field in ('subject', 'object', 'type', 'comment'):
+                if the_expected_rel.has_key(field):
+                    value = rel[field]
+                    expected = the_expected_rel[field]
+                    assert value == expected, (value, expected, field, rel)
+
+    def check_relationship_dict(self, rel_dict, subject_name, type, object_name, comment):
+        subject_ref = self.package_ref_from_name(subject_name)
+        object_ref = self.package_ref_from_name(object_name)
+
+        assert rel_dict['subject'] == subject_ref, (rel_dict, subject_ref)
+        assert rel_dict['object'] == object_ref, (rel_dict, object_ref)
+        assert rel_dict['type'] == type, (rel_dict, type)
+        assert rel_dict['comment'] == comment, (rel_dict, comment)
+
+class TestRelationshipsVersion1(Version1TestCase, RelationshipsTestCase): pass
+class TestRelationshipsVersion2(Version2TestCase, RelationshipsTestCase): pass
+class TestRelationshipsUnversioned(UnversionedTestCase, RelationshipsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_revisions.py	Sun Jul 10 22:58:18 2011 +0100
@@ -0,0 +1,49 @@
+from nose.tools import assert_equal 
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RevisionsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+    
+    def test_register_get_ok(self):
+        # Check mock register behaviour.
+        offset = self.revision_offset()
+        res = self.app.get(offset, status=200)
+        revs = model.Session.query(model.Revision).all()
+        assert revs, 'There are no revisions in the model.'
+        res_dict = self.data_from_res(res)
+        for rev in revs:
+            assert rev.id in res_dict, (rev.id, res_dict)
+
+    def test_entity_get_ok(self):
+        rev = model.repo.history().all()[-2] # 2nd revision is the creation of pkgs
+        assert rev.id
+        assert rev.timestamp.isoformat()
+        offset = self.revision_offset(rev.id)
+        response = self.app.get(offset, status=[200])
+        response_data = self.data_from_res(response)
+        assert_equal(rev.id, response_data['id'])
+        assert_equal(rev.timestamp.isoformat(), response_data['timestamp'])
+        assert 'packages' in response_data
+        packages = response_data['packages']
+        assert isinstance(packages, list)
+        #assert len(packages) != 0, "Revision packages is empty: %s" % packages
+        assert self.ref_package(self.anna) in packages, packages
+        assert self.ref_package(self.war) in packages, packages
+
+    def test_entity_get_404(self):
+        revision_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
+        offset = self.revision_offset(revision_id)
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+class TestRevisionsVersion1(Version1TestCase, RevisionsTestCase): pass
+class TestRevisionsVersion2(Version2TestCase, RevisionsTestCase): pass
+class TestRevisionsUnversioned(UnversionedTestCase, RevisionsTestCase): pass


--- a/ckan/tests/functional/api/model/test_tag.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/tests/functional/api/model/test_tag.py	Sun Jul 10 22:58:18 2011 +0100
@@ -23,6 +23,10 @@
         res = self.app.get(offset, status=self.STATUS_200_OK)
         self.assert_msg_represents_russian(msg=res.body)
 
+    def test_entity_get_not_found(self):
+        offset = self.tag_offset('doesntexist')
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
 
 class TestTagsVersion1(Version1TestCase, TagsTestCase): pass
 class TestTagsVersion2(Version2TestCase, TagsTestCase): pass


--- a/ckan/tests/functional/api/test_model.py	Sun Jul 10 22:57:07 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,578 +0,0 @@
-from ckan.tests.functional.api.base import *
-from ckan.lib.create_test_data import CreateTestData
-from ckan.tests import TestController as ControllerTestCase
-
-class ModelApiTestCase(BaseModelApiTestCase):
-
-    def setup(self):
-        self.create_common_fixtures()
-        self.init_extra_environ()
-        self.source = None
-        self.source1 = None
-        self.source2 = None
-        self.source3 = None
-        self.source4 = None
-        self.source5 = None
-        self.job = None
-        self.job1 = None
-        self.job2 = None
-        self.job3 = None
-
-    def teardown(self):
-        model.repo.rebuild_db()
-
-    def test_02_get_tag_register_ok(self):
-        # Test Packages Register Get 200.
-        offset = self.offset('/rest/tag')
-        res = self.app.get(offset, status=[200])
-        assert 'russian' in res, res
-        assert 'tolstoy' in res, res
-
-    def test_02_get_group_register_ok(self):
-        offset = self.offset('/rest/group')
-        res = self.app.get(offset, status=[200])
-        assert self.group_ref_from_name('david') in res, res
-        assert self.group_ref_from_name('roger') in res, res
-
-    def test_04_get_tag(self):
-        offset = self.offset('/rest/tag/tolstoy')
-        res = self.app.get(offset, status=[200])
-        assert 'annakarenina' in res, res
-        assert not 'warandpeace' in res, res
-
-    def test_04_get_group(self):
-        offset = self.offset('/rest/group/roger')
-        res = self.app.get(offset, status=[200])
-        assert self.package_ref_from_name('annakarenina') in res, res
-        assert self.group_ref_from_name('roger') in res, res
-        assert not self.package_ref_from_name('warandpeace') in res, res
-        
-    def test_05_get_group_entity_not_found(self):
-        offset = self.offset('/rest/group/22222')
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_05_get_tag_entity_not_found(self):
-        offset = self.offset('/rest/tag/doesntexist')
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_06_create_group_entity_ok(self):
-        offset = self.offset('/rest/group')
-        postparams = '%s=1' % self.dumps(self.testgroupvalues)
-        res = self.app.post(offset, params=postparams, status=201,
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        rev = model.repo.new_revision()
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        model.setup_default_user_roles(group, [self.user])
-        model.repo.commit_and_remove()
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert group.title == self.testgroupvalues['title'], group
-        assert group.description == self.testgroupvalues['description'], group
-        assert len(group.packages) == 2, len(group.packages)
-        anna = self.anna
-        warandpeace = self.war
-        assert anna in group.packages
-        assert warandpeace in group.packages
-
-        # Check group packages.
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.get(offset, status=[200])
-        assert self.testgroupvalues['name'] in res, res
-        assert self.package_ref_from_name(self.testgroupvalues['packages'][0]) in res, res
-        ref = self.package_ref_from_name(self.testgroupvalues['packages'][0])
-        assert ref in res, res
-        ref = self.package_ref_from_name(self.testgroupvalues['packages'][1])
-        assert ref in res, res
-        model.Session.remove()
-        
-        # Check create group entity conflict.
-        offset = self.offset('/rest/group')
-        postparams = '%s=1' % self.dumps(self.testgroupvalues)
-        res = self.app.post(offset, params=postparams, status=[409],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-
-    def test_06_rate_package(self):
-        # Test Rating Register Post 200.
-        self.clear_all_tst_ratings()
-        offset = self.offset('/rest/rating')
-        rating_opts = {'package':u'warandpeace',
-                       'rating':5}
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[201],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 1
-        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
-
-        # Get package to see rating
-        offset = self.offset('/rest/package/%s' % rating_opts['package'])
-        res = self.app.get(offset, status=[200])
-        assert rating_opts['package'] in res, res
-        assert '"ratings_average": %s.0' % rating_opts['rating'] in res, res
-        assert '"ratings_count": 1' in res, res
-        
-        model.Session.remove()
-        
-        # Rerate package
-        offset = self.offset('/rest/rating')
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[201],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 1
-        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
-
-    def test_06_rate_package_out_of_range(self):
-        self.clear_all_tst_ratings()
-        offset = self.offset('/rest/rating')
-        rating_opts = {'package':u'warandpeace',
-                       'rating':0}
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[409],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 0
-
-    def test_10_edit_group(self):
-        # create a group with testgroupvalues
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        if not group:
-            offset = self.offset('/rest/group')
-            postparams = '%s=1' % self.dumps(self.testgroupvalues)
-            res = self.app.post(offset, params=postparams, status=[201],
-                    extra_environ=self.extra_environ)
-            model.Session.remove()
-            group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert len(group.packages) == 2, group.packages
-        user = model.User.by_name(self.user_name)
-        model.setup_default_user_roles(group, [user])
-
-        # edit it
-        group_vals = {'name':u'somethingnew', 'title':u'newtesttitle',
-                      'packages':[u'annakarenina']}
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        postparams = '%s=1' % self.dumps(group_vals)
-        res = self.app.post(offset, params=postparams, status=[200],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        group = model.Session.query(model.Group).filter_by(name=group_vals['name']).one()
-        assert group.name == group_vals['name']
-        assert group.title == group_vals['title']
-        assert len(group.packages) == 1, group.packages
-        assert group.packages[0].name == group_vals['packages'][0]
-
-    def test_10_edit_group_name_duplicate(self):
-        # create a group with testgroupvalues
-        if not model.Group.by_name(self.testgroupvalues['name']):
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = self.testgroupvalues['name']
-            model.Session.commit()
-
-            group = model.Group.by_name(self.testgroupvalues['name'])
-            model.setup_default_user_roles(group, [self.user])
-            rev = model.repo.new_revision()
-            model.repo.commit_and_remove()
-        assert model.Group.by_name(self.testgroupvalues['name'])
-        
-        # create a group with name 'dupname'
-        dupname = u'dupname'
-        if not model.Group.by_name(dupname):
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = dupname
-            model.Session.commit()
-        assert model.Group.by_name(dupname)
-
-        # edit first group to have dupname
-        group_vals = {'name':dupname}
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        postparams = '%s=1' % self.dumps(group_vals)
-        res = self.app.post(offset, params=postparams, status=[409],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        
-    def test_11_delete_group(self):
-        # Test Groups Entity Delete 200.
-
-        # create a group with testgroupvalues
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        if not group:
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = self.testgroupvalues['name']
-            model.repo.commit_and_remove()
-
-            rev = model.repo.new_revision()
-            group = model.Group.by_name(self.testgroupvalues['name'])
-            model.setup_default_user_roles(group, [self.user])
-            model.repo.commit_and_remove()
-        assert group
-        user = model.User.by_name(self.user_name)
-        model.setup_default_user_roles(group, [user])
-
-        # delete it
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.delete(offset, status=[200],
-                extra_environ=self.extra_environ)
-
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert group.state == 'deleted', group.state
-
-        res = self.app.get(offset, status=[403])
-        res = self.app.get(offset, status=[200],
-                           extra_environ=self.extra_environ)
-
-        model.Session.remove()
-
-    def test_12_get_group_404(self):
-        # Test Package Entity Get 404.
-        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_13_delete_group_404(self):
-        # Test Packages Entity Delete 404.
-        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.delete(offset, status=[404],
-                              extra_environ=self.extra_environ)
-
-    def test_14_list_revisions(self):
-        # Check mock register behaviour.
-        offset = self.offset('/rest/revision')
-        res = self.app.get(offset, status=200)
-        revs = model.Session.query(model.Revision).all()
-        assert revs, "There are no revisions in the model."
-        res_dict = self.data_from_res(res)
-        for rev in revs:
-            assert rev.id in res_dict, (rev.id, res_dict)
-
-    def test_14_get_revision(self):
-        rev = model.repo.history().all()[-2] # 2nd revision is the creation of pkgs
-        assert rev.id
-        assert rev.timestamp.isoformat()
-        offset = self.offset('/rest/revision/%s' % rev.id)
-        response = self.app.get(offset, status=[200])
-        response_data = self.data_from_res(response)
-        assert rev.id == response_data['id']
-        assert rev.timestamp.isoformat() == response_data['timestamp'], (rev.timestamp.isoformat(), response_data['timestamp'])
-        assert 'packages' in response_data
-        packages = response_data['packages']
-        assert isinstance(packages, list)
-        #assert len(packages) != 0, "Revision packages is empty: %s" % packages
-        assert self.ref_package(self.anna) in packages, packages
-        assert self.ref_package(self.war) in packages, packages
-
-    def test_14_get_revision_404(self):
-        revision_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
-        offset = self.offset('/rest/revision/%s' % revision_id)
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_16_list_licenses(self):
-        from ckan.model.license import LicenseRegister
-        register = LicenseRegister()
-        assert len(register), "No changesets found in model."
-        offset = self.offset('/rest/licenses')
-        res = self.app.get(offset, status=[200])
-        licenses_data = self.data_from_res(res)
-        assert len(licenses_data) == len(register), (len(licenses_data), len(register))
-        for license_data in licenses_data:
-            id = license_data['id']
-            license = register[id]
-            assert license['title'] == license.title
-            assert license['url'] == license.url
-
-
-class RelationshipsApiTestCase(ApiTestCase, ControllerTestCase):
-
-    @classmethod
-    def setup_class(self):
-        CreateTestData.create()
-        self.user = self.create_user(name=u'barry')
-        self.testsysadmin = model.User.by_name(u'testsysadmin')
-        self.extra_environ={ 'Authorization' : str(self.user.apikey) }
-        self.comment = u'Comment umlaut: \xfc.'
-
-    @classmethod
-    def teardown_class(self):
-        model.Session.remove()
-        model.repo.rebuild_db()
-        model.Session.remove()
-
-    def teardown(self):
-        for relationship in self.anna.get_relationships():
-            relationship.purge()
-        model.Session.commit()
-        relationships = self.anna.get_relationships()
-        assert relationships == [], "There are still some relationships: %s" % relationships
-
-    def test_01_create_and_read_relationship(self):
-        # check anna has no existing relationships
-        assert not self.anna.get_relationships()
-        assert self.get_relationships(package1_name='annakarenina') == [], self.get_relationships(package1_name='annakarenina')
-        assert self.get_relationships(package1_name='annakarenina',
-                                       package2_name='warandpeace') == []
-        assert self.get_relationships(package1_name='annakarenina',
-                                       type='child_of',
-                                       package2_name='warandpeace') == 404
-        assert self.get_relationships_via_package('annakarenina') == []
-
-        # Create a relationship.
-        self.create_annakarenina_parent_of_war_and_peace()
-
-        # Check package relationship register.
-        rels = self.get_relationships(package1_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # Todo: Name this?
-        # Check '/api/VER/rest/package/annakarenina/relationships/warandpeace'
-        rels = self.get_relationships(package1_name='annakarenina',
-                                       package2_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # Todo: Name this?
-        # check '/api/VER/rest/package/annakarenina/parent_of/warandpeace'
-        rels = self.get_relationships(package1_name='annakarenina',
-                                       type='parent_of',
-                                       package2_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # same checks in reverse direction
-        rels = self.get_relationships(package1_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        rels = self.get_relationships(package1_name='warandpeace',
-                                       package2_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        rels = self.get_relationships(package1_name='warandpeace',
-                                       type='child_of',
-                                      package2_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        # Check package entity relationships.
-        rels = self.get_relationships_via_package('annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-        
-    def test_03_update_relationship(self):
-        # Create a relationship.
-        self.create_annakarenina_parent_of_war_and_peace()
-
-        # Check the relationship before update.
-        self.check_relationships_rest('warandpeace', 'annakarenina',
-                                      [{'type': 'child_of',
-                                        'comment': self.comment}])
-
-        # Update the relationship.
-        new_comment = u'New comment.'
-        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
-
-        # Check the relationship after update.
-        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
-
-        # Repeat update with same values, to check it remains the same?
-
-        # Update the relationship.
-        new_comment = u'New comment.'
-        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
-
-        # Check the relationship after update.
-        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
-
-    def test_05_delete_relationship(self):
-        self.create_annakarenina_parent_of_war_and_peace()
-        self.update_annakarenina_parent_of_war_and_peace()
-        expected = [ {'type': 'child_of', 'comment': u'New comment.'} ]
-        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
-
-        self.delete_annakarenina_parent_of_war_and_peace()
-
-        expected = []
-        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
-
-    def create_annakarenina_parent_of_war_and_peace(self):
-        # Create package relationship.
-        # Todo: Redesign this in a RESTful style, so that a relationship is 
-        # created by posting a relationship to a relationship **register**.
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        postparams = '%s=1' % self.dumps({'comment':self.comment})
-        res = self.app.post(offset, params=postparams, status=[201],
-                            extra_environ=self.extra_environ)
-        # Check the model, directly.
-        rels = self.anna.get_relationships()
-        assert len(rels) == 1, rels
-        assert rels[0].type == 'child_of'
-        assert rels[0].subject.name == 'warandpeace'
-        assert rels[0].object.name == 'annakarenina'
-
-    def update_annakarenina_parent_of_war_and_peace(self, comment=u'New comment.'):
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        postparams = '%s=1' % self.dumps({'comment':comment})
-        res = self.app.post(offset, params=postparams, status=[201], extra_environ=self.extra_environ)
-        return res
-
-    def delete_annakarenina_parent_of_war_and_peace(self):
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        res = self.app.delete(offset, status=[200], extra_environ=self.extra_environ)
-        return res
-
-    def get_relationships(self, package1_name=u'annakarenina', type='relationships', package2_name=None):
-        package1_ref = self.package_ref_from_name(package1_name)
-        if not package2_name:
-            offset = self.offset('/rest/package/%s/%s' % (package1_ref, type))
-        else:
-            package2_ref = self.package_ref_from_name(package2_name)
-            offset = self.offset('/rest/package/%s/%s/%s' % (
-                str(package1_ref), type, str(package2_ref)))
-        allowable_statuses = [200]
-        if type:
-            allowable_statuses.append(404)
-        res = self.app.get(offset, status=allowable_statuses)
-        if res.status == 200:
-            res_dict = self.data_from_res(res) if res.body else []
-            return res_dict
-        else:
-            return 404
-
-    def get_relationships_via_package(self, package1_name):
-        offset = self.offset('/rest/package/%s' % (str(package1_name)))
-        res = self.app.get(offset, status=200)
-        res_dict = self.data_from_res(res)
-        return res_dict['relationships']
-
-    def assert_len_relationships(self, relationships, expected_relationships):
-        len_relationships = len(relationships)
-        len_expected_relationships = len(expected_relationships)
-        if len_relationships != len_expected_relationships:
-            msg = 'Found %i relationships, ' % len_relationships
-            msg += 'but expected %i.' % len_expected_relationships
-            if len_relationships:
-                msg += ' Found: '
-                for r in relationships:
-                    msg += '%s %s %s; ' % (r['subject'], r['type'], r['object'])
-                msg += '.'
-            raise Exception, msg
-
-    def check_relationships_rest(self, pkg1_name, pkg2_name=None,
-                                 expected_relationships=[]):
-        rels = self.get_relationships(package1_name=pkg1_name,
-                                      package2_name=pkg2_name)
-        self.assert_len_relationships(rels, expected_relationships) 
-        for rel in rels:
-            the_expected_rel = None
-            for expected_rel in expected_relationships:
-                if expected_rel['type'] == rel['type'] and \
-                   (pkg2_name or expected_rel['object'] == pkg2_name):
-                    the_expected_rel = expected_rel
-                    break
-            if not the_expected_rel:
-                raise Exception('Unexpected relationship: %s %s %s' %
-                                (rel['subject'], rel['type'], rel['object']))
-            for field in ('subject', 'object', 'type', 'comment'):
-                if the_expected_rel.has_key(field):
-                    value = rel[field]
-                    expected = the_expected_rel[field]
-                    assert value == expected, (value, expected, field, rel)
-
-    def check_relationship_dict(self, rel_dict, subject_name, type, object_name, comment):
-        subject_ref = self.package_ref_from_name(subject_name)
-        object_ref = self.package_ref_from_name(object_name)
-
-        assert rel_dict['subject'] == subject_ref, (rel_dict, subject_ref)
-        assert rel_dict['object'] == object_ref, (rel_dict, object_ref)
-        assert rel_dict['type'] == type, (rel_dict, type)
-        assert rel_dict['comment'] == comment, (rel_dict, comment)
- 
-
-# Tests for Version 1 of the API.
-class TestModelApi1(Api1TestCase, ModelApiTestCase):
-
-    def test_06_create_pkg_using_download_url(self):
-        test_params = {
-            'name':u'testpkg06',
-            'download_url':u'ftp://ftp.monash.edu.au/pub/nihongo/JMdict.gz',
-            }
-        offset = self.package_offset()
-        postparams = '%s=1' % self.dumps(test_params)
-        res = self.app.post(offset, params=postparams, 
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(test_params['name'])
-        assert pkg
-        assert pkg.name == test_params['name'], pkg
-        assert len(pkg.resources) == 1, pkg.resources
-        assert pkg.resources[0].url == test_params['download_url'], pkg.resources[0]
-
-    def test_10_edit_pkg_with_download_url(self):
-        test_params = {
-            'name':u'testpkg10',
-            'download_url':u'testurl',
-            }
-        rev = model.repo.new_revision()
-        pkg = model.Package()
-        model.Session.add(pkg)
-        pkg.name = test_params['name']
-        pkg.download_url = test_params['download_url']
-        model.Session.commit()
-
-        pkg = self.get_package_by_name(test_params['name'])
-        model.setup_default_user_roles(pkg, [self.user])
-        rev = model.repo.new_revision()
-        model.repo.commit_and_remove()
-        assert self.get_package_by_name(test_params['name'])
-
-        # edit it
-        pkg_vals = {'download_url':u'newurl'}
-        offset = self.package_offset(test_params['name'])
-        postparams = '%s=1' % self.dumps(pkg_vals)
-        res = self.app.post(offset, params=postparams, status=[200],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = model.Session.query(model.Package).filter_by(name=test_params['name']).one()
-        assert len(pkg.resources) == 1, pkg.resources
-        assert pkg.resources[0].url == pkg_vals['download_url']
-
-
-class TestRelationshipsApi1(Api1TestCase, RelationshipsApiTestCase): pass
-
-# Tests for Version 2 of the API.
-class TestModelApi2(Api2TestCase, ModelApiTestCase): pass
-class TestRelationshipsApi2(Api2TestCase, RelationshipsApiTestCase): pass
-
-# Tests for unversioned API.
-class TestModelApiUnversioned(ApiUnversionedTestCase, ModelApiTestCase): pass
-class TestRelationshipsApiUnversioned(RelationshipsApiTestCase, ApiUnversionedTestCase): pass
-


--- a/ckan/tests/functional/api/test_package_search.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/tests/functional/api/test_package_search.py	Sun Jul 10 22:58:18 2011 +0100
@@ -179,6 +179,7 @@
     def test_08_uri_qjson_malformed(self):
         offset = self.base_url + '?qjson="q":""' # user forgot the curly braces
         res = self.app.get(offset, status=400)
+        self.assert_json_response(res, 'Bad request - Could not read parameters')
         
     def test_08_all_fields(self):
         rating = model.Rating(user_ip_address=u'123.1.2.3',
@@ -211,6 +212,7 @@
         res = self.app.get(offset, status=400)
         assert('boolean' in res.body)
         assert('all_fields' in res.body)
+        self.assert_json_response(res, 'boolean')
 
     def test_09_just_tags(self):
         offset = self.base_url + '?tags=russian&all_fields=1'
@@ -257,6 +259,7 @@
         res = self.app.get(offset, status=400)
         assert('integer' in res.body)
         assert('offset' in res.body)
+        self.assert_json_response(res, 'integer')
 
     def test_12_all_packages_qjson(self):
         query = {'q': ''}


--- a/ckan/tests/functional/api/test_resource_search.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/tests/functional/api/test_resource_search.py	Sun Jul 10 22:58:18 2011 +0100
@@ -70,6 +70,7 @@
     def test_04_bad_option(self):
         offset = self.base_url + '?random=option'
         result = self.app.get(offset, status=400)
+        self.assert_json_response(result, 'Bad request - Bad search option')
 
     def test_05_options(self):
         offset = self.base_url + '?url=site&all_fields=1&callback=mycallback'


--- a/ckan/tests/functional/api/test_revision_search.py	Sun Jul 10 22:57:07 2011 +0100
+++ b/ckan/tests/functional/api/test_revision_search.py	Sun Jul 10 22:58:18 2011 +0100
@@ -11,13 +11,19 @@
     def teardown_class(self):
         model.repo.rebuild_db()
 
-    def test_12_search_revision_basic(self):
+    def test_12_search_revision_bad_requests(self):
         offset = self.offset('/search/revision')
         # Check bad request.
-        self.app.get(offset, status=400)
-        self.app.get(offset+'?since_rev=2010-01-01T00:00:00', status=400)
-        self.app.get(offset+'?since_revision=2010-01-01T00:00:00', status=400)
-        self.app.get(offset+'?since_id=', status=400)
+        res = self.app.get(offset, status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_rev=2010-01-01T00:00:00', status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_revision=2010-01-01T00:00:00', status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_id=', status=400)
+        self.assert_json_response(res, 'Bad request - No revision specified')
+        res = self.app.get(offset+'?since_id=1234', status=404)
+        self.assert_json_response(res, 'Not found - There is no revision')
 
     def test_12_search_revision_since_rev(self):
         offset = self.offset('/search/revision')
@@ -54,8 +60,9 @@
         assert res_list == [], res_list
         # Check bad format.
         params = "?since_time=2010-04-31T23:45"
-        self.app.get(offset+params, status=400)
+        res = self.app.get(offset+params, status=400)
+        self.assert_json_response(res, 'Bad request - ValueError: day is out of range for month')
 
 
-class TestPackageSearchApi1(Api1TestCase, RevisionSearchApiTestCase): pass
-class TestPackageSearchApi2(Api2TestCase, RevisionSearchApiTestCase): pass
+class TestRevisionSearchApi1(Api1TestCase, RevisionSearchApiTestCase): pass
+class TestRevisionSearchApi2(Api2TestCase, RevisionSearchApiTestCase): pass


--- a/doc/api/model_formats.rst.inc	Sun Jul 10 22:57:07 2011 +0100
+++ b/doc/api/model_formats.rst.inc	Sun Jul 10 22:58:18 2011 +0100
@@ -26,6 +26,6 @@
 
  * When you update an object, fields that you don't supply will remain as they were before.
 
- * To delete an 'extra' key-value pair, supply the key with a None value.
+ * To delete an 'extra' key-value pair, supply the key with JSON value: ``null``
 
  * When you read a package then some additional information is supplied that cannot current be adjusted throught the CKAN API. This includes info on Package Relationship ('relationships'), Group membership ('groups'), ratings ('ratings_average' and 'ratings_count'), full URL of the package in CKAN ('ckan_url') and Package ID ('id'). This is purely a convenience for clients, and only forms part of the Package on GET.


--- a/doc/api/model_resources_table.rst.inc	Sun Jul 10 22:57:07 2011 +0100
+++ b/doc/api/model_resources_table.rst.inc	Sun Jul 10 22:58:18 2011 +0100
@@ -8,7 +8,7 @@
 +--------------------------------+-------------------------------------------------------------------+
 | Group Register                 | ``/rest/group``                                                   |
 +--------------------------------+-------------------------------------------------------------------+
-| Group Entity                   | ``/rest/group/GROUP-NAME``                                        |
+| Group Entity                   | ``/rest/group/GROUP-REF``                                         |
 +--------------------------------+-------------------------------------------------------------------+
 | Tag Register                   | ``/rest/tag``                                                     |
 +--------------------------------+-------------------------------------------------------------------+
@@ -16,9 +16,9 @@
 +--------------------------------+-------------------------------------------------------------------+
 | Rating Register                | ``/rest/rating``                                                  |
 +--------------------------------+-------------------------------------------------------------------+
-| Rating Entity                  | ``/rest/rating/PACKAGE-REF``                                      |
+| Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships``                       |
 +--------------------------------+-------------------------------------------------------------------+
-| Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships``                       |
+| Package Relationships Register | ``/rest/package/PACKAGE-REF/RELATIONSHIP-TYPE``                   |
 +--------------------------------+-------------------------------------------------------------------+
 | Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships/PACKAGE-REF``           |
 +--------------------------------+-------------------------------------------------------------------+


--- a/doc/buildbot.rst	Sun Jul 10 22:57:07 2011 +0100
+++ b/doc/buildbot.rst	Sun Jul 10 22:58:18 2011 +0100
@@ -144,6 +144,20 @@
        ProxyPreserveHost On
   </VirtualHost>
 
+or the old one had::
+
+  <VirtualHost *:80>
+      ServerAdmin sysadmin at okfn.org
+      ServerName buildbot.okfn.org
+      DocumentRoot /var/www/
+      <Location />
+          Order allow,deny
+          allow from all
+      </Location>
+      RewriteEngine On   
+      RewriteRule /(.*) http://localhost:8010/$1 [P,L]
+  </VirtualHost>
+
 Then::
 
   sudo apt-get install libapache2-mod-proxy-html


--- a/requires/lucid_present.txt	Sun Jul 10 22:57:07 2011 +0100
+++ b/requires/lucid_present.txt	Sun Jul 10 22:58:18 2011 +0100
@@ -10,6 +10,9 @@
 psycopg2==2.0.13
 lxml==2.2.4
 sphinx==0.6.4
+# Specifying not to use later webob because of incompatibility
+# with pylons 0.9.7 (change to imports of Multidict)
+webob<=1.0.8
 Pylons==0.9.7
 repoze.who==1.0.18
 tempita==0.4

Repository URL: https://bitbucket.org/okfn/ckan/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.




More information about the ckan-changes mailing list