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

Bitbucket commits-noreply at bitbucket.org
Wed Jun 8 11:38:36 UTC 2011


6 new changesets in ckan:

http://bitbucket.org/okfn/ckan/changeset/cba5287e2ce4/
changeset:   cba5287e2ce4
branch:      feature-1147-add-expired_id-to-revision-tables
user:        kindly
date:        2011-06-07 00:34:28
summary:     [moderated edits] active current is made instead of null enddate
affected #:  4 files (2.2 KB)

--- a/ckan/logic/action/update.py	Mon Jun 06 15:50:46 2011 +0100
+++ b/ckan/logic/action/update.py	Mon Jun 06 23:34:28 2011 +0100
@@ -68,28 +68,29 @@
     for group in groups:
         check_access(group, model.Action.EDIT, context)
 
-def _make_latest_rev_active(context, obj_list):
+def _make_latest_rev_active(context, q):
 
-    latest_rev = sorted(obj_list, key=lambda x: x.revision_timestamp)[-1]
-    if latest_rev.state == 'pending-deleted':
+    session = context['model'].Session
+
+    old_current = q.filter_by(state='active-current').first()
+    if old_current:
+        old_current.state = 'active'
+        session.add(old_current)
+
+    latest_rev = q.filter_by(expired_timestamp='9999-12-31').one()
+    if latest_rev.state in ('pending-deleted', 'deleted'):
         latest_rev.state = 'deleted'
     else:
-        latest_rev.state = 'active'
-    for obj_rev in obj_list:
-        if obj_rev == latest_rev:
-            continue
-
-        obj_rev.expired_id = latest_rev.revision_id
-        obj_rev.expired_timestamp = latest_rev.revision_timestamp
-        context['model'].Session.add(obj_rev)
+        latest_rev.state = 'active-current'
+    session.add(latest_rev)
         
-        ##this is just a way to get the latest revision that changed
-        ##in order to timestamp
-        old_latest = context.get('latest_revision_date')
-        if old_latest:
-            if latest_rev.revision_timestamp > old_latest:
-                context['latest_revision_date'] = latest_rev.revision_timestamp
-                context['latest_revision'] = latest_rev.revision_id
+    ##this is just a way to get the latest revision that changed
+    ##in order to timestamp
+    old_latest = context.get('latest_revision_date')
+    if old_latest:
+        if latest_rev.revision_timestamp > old_latest:
+            context['latest_revision_date'] = latest_rev.revision_timestamp
+            context['latest_revision'] = latest_rev.revision_id
 
 def make_latest_pending_package_active(context):
 
@@ -101,33 +102,29 @@
     check_access(pkg, model.Action.EDIT, context)
 
     #packages
-    q = session.query(model.PackageRevision)
-    pkgrevs = q.filter_by(id=id, expired_timestamp='9999-12-31').all()
-    _make_latest_rev_active(context, pkgrevs)
+    q = session.query(model.PackageRevision).filter_by(id=id)
+    _make_latest_rev_active(context, q)
 
     #resources
     for resource in pkg.resource_groups_all[0].resources_all:
-        res_revs = session.query(model.ResourceRevision).filter_by(
-            id=resource.id, expired_timestamp='9999-12-31').all()
-        _make_latest_rev_active(context, res_revs)
+        q = session.query(model.ResourceRevision).filter_by(id=resource.id)
+        _make_latest_rev_active(context, q)
 
     #tags
     for tag in pkg.package_tag_all:
-        tags_revs = session.query(model.PackageTagRevision).filter_by(
-            id=tag.id, expired_timestamp='9999-12-31').all()
-        _make_latest_rev_active(context, tags_revs)
+        q = session.query(model.PackageTagRevision).filter_by(id=tag.id)
+        _make_latest_rev_active(context, q)
 
     #extras
     for extra in pkg.extras_list:
-        extras_revs = session.query(model.PackageExtraRevision).filter_by(
-            id=extra.id, expired_timestamp='9999-12-31').all()
-        _make_latest_rev_active(context, extras_revs)
+        q = session.query(model.PackageExtraRevision).filter_by(id=extra.id)
+        _make_latest_rev_active(context, q)
 
     latest_revision = context.get('latest_revision')
     if not latest_revision:
         return
 
-    q = session.query(model.ResourceRevision).filter_by(id=latest_revision)
+    q = session.query(model.Revision).filter_by(id=latest_revision)
     revision = q.first()
     revision.approved_timestamp = datetime.datetime.now()
     session.add(revision)


--- a/ckan/migration/versions/039_add_expired_id_and_dates.py	Mon Jun 06 15:50:46 2011 +0100
+++ b/ckan/migration/versions/039_add_expired_id_and_dates.py	Mon Jun 06 23:34:28 2011 +0100
@@ -90,6 +90,7 @@
 update package_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_package_period on package_revision(revision_timestamp, expired_timestamp, id);
 create index idx_package_expired on package_revision(expired_timestamp);
@@ -100,6 +101,7 @@
 update package_extra_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_extra_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_package_extra_period on package_extra_revision(revision_timestamp, expired_timestamp, id);
 create index idx_package_extra_period_package on package_extra_revision(revision_timestamp, expired_timestamp, package_id);
@@ -111,6 +113,7 @@
 update package_group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_group_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_package_group_period_package_group on package_group_revision(revision_timestamp, expired_timestamp, package_id, group_id);
 create index idx_package_group_expired on package_group_revision(expired_timestamp);
@@ -122,6 +125,7 @@
 update package_tag_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_tag_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_period_package_tag on package_tag_revision(revision_timestamp, expired_timestamp, package_id, tag_id);
 create index idx_package_tag_expired on package_tag_revision(expired_timestamp);
@@ -132,6 +136,7 @@
 update package_relationship_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_relationship_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_period_package_relationship on package_relationship_revision(revision_timestamp, expired_timestamp, object_package_id, subject_package_id);
 create index idx_package_relationship_expired on package_relationship_revision(expired_timestamp);
@@ -142,6 +147,7 @@
 update resource_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update resource_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_resource_period on resource_revision(revision_timestamp, expired_timestamp, id);
 create index idx_resource_period_resource_group on resource_revision(revision_timestamp, expired_timestamp, resource_group_id);
@@ -153,6 +159,7 @@
 update resource_group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update resource_group_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_resource_group_period on resource_group_revision(revision_timestamp, expired_timestamp, id);
 create index idx_resource_group_period_package on resource_group_revision(revision_timestamp, expired_timestamp, package_id);
@@ -164,6 +171,7 @@
 update group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update group_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_group_period on group_revision(revision_timestamp, expired_timestamp, id);
 create index idx_group_expired on group_revision(expired_timestamp);
@@ -174,6 +182,7 @@
 update group_extra_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update group_extra_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
 
 create index idx_group_extra_period on group_extra_revision(revision_timestamp, expired_timestamp, id);
 create index idx_group_extra_period_group on group_extra_revision(revision_timestamp, expired_timestamp, group_id);
@@ -193,7 +202,9 @@
         count = migrate_engine.execute('''select count(*) from "%s"''' % table).first()[0]
         revision_expired_id_count = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.expired_id is null''' % (table, table)).first()[0]
         revision_expired_data_count = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.expired_timestamp = '9999-12-31' ''' % (table, table)).first()[0]
+        revision_current = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.state = 'active-current' ''' % (table, table)).first()[0]
         assert count == revision_expired_id_count
         assert count == revision_expired_data_count
+        assert count == revision_current
 
     


--- a/ckan/model/meta.py	Mon Jun 06 15:50:46 2011 +0100
+++ b/ckan/model/meta.py	Mon Jun 06 23:34:28 2011 +0100
@@ -46,21 +46,28 @@
             if not hasattr(obj, '__revision_class__'):
                 continue
 
+            revision_cls = obj.__revision_class__
+
+            ## when a normal active transaction happens
             if 'pending' not in obj.state:
                 revision.approved_timestamp = datetime.now()
+                old = session.query(revision_cls).filter_by(
+                    state='active-current',
+                    id = obj.id
+                ).first()
+                if old:
+                    old.state = 'active'
+                    session.add(old)
 
-            revision_cls = obj.__revision_class__
             q = session.query(revision_cls)
             q = q.filter_by(expired_timestamp='9999-12-31', id=obj.id)
-            if 'pending' in obj.state:
-                q = q.filter(revision_cls.state.in_([
-                    'pending', 'pending-deleted']))
-                revision.state = 'pending'
             results = q.all()
 
             for rev_obj in results:
                 if rev_obj.revision_id == revision.id:
                     rev_obj.revision_timestamp = revision.timestamp
+                    if 'pending' not in obj.state:
+                        rev_obj.state = 'active-current'
                 else:
                     rev_obj.expired_id = revision.id
                     rev_obj.expired_timestamp = revision.timestamp


--- a/ckan/tests/lib/test_dictization.py	Mon Jun 06 15:50:46 2011 +0100
+++ b/ckan/tests/lib/test_dictization.py	Mon Jun 06 23:34:28 2011 +0100
@@ -347,11 +347,11 @@
 
         assert len(sorted_packages) == 3
         assert sorted_packages[0].state == 'pending'
-        assert sorted_packages[1].state == 'active'
+        assert sorted_packages[1].state == 'active-current'
         assert sorted_packages[2].state == 'active'
 
         assert str(sorted_packages[0].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_packages[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_packages[1].expired_timestamp) != '9999-12-31 00:00:00'
         assert str(sorted_packages[2].expired_timestamp) != '9999-12-31 00:00:00'
 
         resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
@@ -360,12 +360,12 @@
 
         assert len(sorted_resources) == 4
         assert sorted_resources[0].state == 'pending'
-        assert sorted_resources[1].state == 'active'
-        assert sorted_resources[2].state == 'active'
+        assert sorted_resources[1].state == 'active-current'
+        assert sorted_resources[2].state == 'active-current'
         assert sorted_resources[3].state == 'active'
 
         assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_resources[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[1].expired_timestamp) != '9999-12-31 00:00:00'
         assert str(sorted_resources[2].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_resources[3].expired_timestamp) != '9999-12-31 00:00:00'
 
@@ -378,13 +378,13 @@
         assert len(sorted_tags) == 4, len(sorted_tags)
         assert sorted_tags[0].state == 'pending-deleted'
         assert sorted_tags[1].state == 'pending'
-        assert sorted_tags[2].state == 'active'
-        assert sorted_tags[3].state == 'active'
+        assert sorted_tags[2].state == 'active-current'
+        assert sorted_tags[3].state == 'active-current'
 
         assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[2].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_tags[3].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[3].expired_timestamp) != '9999-12-31 00:00:00'
 
         extras_revisions = model.Session.query(model.PackageExtraRevision).filter_by(package_id=anna1.id).all()
 
@@ -392,12 +392,12 @@
                                key=lambda x: (x.revision_timestamp, x.key))[::-1]
 
         assert sorted_extras[0].state == 'pending'
-        assert sorted_extras[1].state == 'active'
-        assert sorted_extras[2].state == 'active'
+        assert sorted_extras[1].state == 'active-current'
+        assert sorted_extras[2].state == 'active-current'
 
         assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_extras[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[2].expired_timestamp) != '9999-12-31 00:00:00'
 
 
     def test_11_add_pending(self):
@@ -430,13 +430,13 @@
         assert len(sorted_resources) == 5
         assert sorted_resources[0].state == 'pending'
         assert sorted_resources[1].state == 'pending'
-        assert sorted_resources[2].state == 'active'
-        assert sorted_resources[3].state == 'active'
+        assert sorted_resources[2].state == 'active-current'
+        assert sorted_resources[3].state == 'active-current'
         assert sorted_resources[4].state == 'active'
 
         assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_resources[1].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_resources[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[2].expired_timestamp) != '9999-12-31 00:00:00'
         assert str(sorted_resources[3].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_resources[4].expired_timestamp) != '9999-12-31 00:00:00'
 
@@ -451,8 +451,8 @@
         assert sorted_tags[1].state == 'pending-deleted'
         assert sorted_tags[2].state == 'pending-deleted'
         assert sorted_tags[3].state == 'pending'
-        assert sorted_tags[4].state == 'active'
-        assert sorted_tags[5].state == 'active'
+        assert sorted_tags[4].state == 'active-current'
+        assert sorted_tags[5].state == 'active-current'
 
         assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
@@ -470,14 +470,14 @@
         assert sorted_extras[0].state == 'pending-deleted'
         assert sorted_extras[1].state == 'pending'
         assert sorted_extras[2].state == 'pending'
-        assert sorted_extras[3].state == 'active'
-        assert sorted_extras[4].state == 'active'
+        assert sorted_extras[3].state == 'active-current'
+        assert sorted_extras[4].state == 'active-current'
 
         assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_extras[2].expired_timestamp) != '9999-12-31 00:00:00'
         assert str(sorted_extras[3].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_extras[4].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[4].expired_timestamp) != '9999-12-31 00:00:00'
 
     def test_12_make_active(self):
 
@@ -488,16 +488,24 @@
                    "id": anna1.id}
         make_latest_pending_package_active(context)
 
+        pkgrevisions = model.Session.query(model.PackageRevision).filter_by(id=anna1.id).all()
+        sorted_packages = sorted(pkgrevisions, key=lambda x:x.revision_timestamp)[::-1]
+
+        assert len(sorted_packages) == 3
+        assert sorted_packages[0].state == 'active-current' #was pending
+        assert sorted_packages[1].state == 'active' #was active-current
+        assert sorted_packages[2].state == 'active'
+
         resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
         sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
 
         assert len(sorted_resources) == 5
         for res in sorted_resources:
             print res.id, res.revision_timestamp, res.expired_timestamp, res.state
-        assert sorted_resources[0].state == 'active'
-        assert sorted_resources[1].state == 'active'
+        assert sorted_resources[0].state == 'active-current'
+        assert sorted_resources[1].state == 'active-current'
         assert sorted_resources[2].state == 'active'
-        assert sorted_resources[3].state == 'active'
+        assert sorted_resources[3].state == 'active-current'
         assert sorted_resources[4].state == 'active'
 
         assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
@@ -513,11 +521,11 @@
         print [(tag.state, tag.tag.name) for tag in sorted_tags]
 
         assert len(sorted_tags) == 6, len(sorted_tags)
-        assert sorted_tags[0].state == 'active'
+        assert sorted_tags[0].state == 'active-current'
         assert sorted_tags[1].state == 'deleted'
         assert sorted_tags[2].state == 'deleted'
         assert sorted_tags[3].state == 'pending'
-        assert sorted_tags[4].state == 'active'
+        assert sorted_tags[4].state == 'active-current'
         assert sorted_tags[5].state == 'active'
 
         assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
@@ -534,9 +542,9 @@
         print [(extra.state, extra.key, extra.value) for extra in sorted_extras]
 
         assert sorted_extras[0].state == 'deleted'
-        assert sorted_extras[1].state == 'active'
+        assert sorted_extras[1].state == 'active-current'
         assert sorted_extras[2].state == 'pending'
-        assert sorted_extras[3].state == 'active'
+        assert sorted_extras[3].state == 'active-current'
         assert sorted_extras[4].state == 'active'
 
         assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'


http://bitbucket.org/okfn/ckan/changeset/d6fab55ec9ae/
changeset:   d6fab55ec9ae
branch:      feature-1147-add-expired_id-to-revision-tables
user:        kindly
date:        2011-06-08 00:19:48
summary:     [moderated edits] all queries not using orm and current added
affected #:  17 files (5.2 KB)

--- a/ckan/lib/create_test_data.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/lib/create_test_data.py	Tue Jun 07 23:19:48 2011 +0100
@@ -370,10 +370,12 @@
                              description=u'Roger likes these books.')
         for obj in [david, roger]:
             model.Session.add(obj)
+        
         cls.group_names.add(u'david')
         cls.group_names.add(u'roger')
-        david.packages = [pkg1, pkg2]
-        roger.packages = [pkg1]
+        model.Session.add(model.PackageGroup(package=pkg1, group=david))
+        model.Session.add(model.PackageGroup(package=pkg2, group=david))
+        model.Session.add(model.PackageGroup(package=pkg1, group=roger))
         # authz
         model.Session.add_all([
             model.User(name=u'tester', apikey=u'tester', password=u'tester'),


--- a/ckan/lib/dictization/__init__.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/lib/dictization/__init__.py	Tue Jun 07 23:19:48 2011 +0100
@@ -17,13 +17,19 @@
     model = context["model"]
     session = context["session"]
 
-    ModelClass = obj.__class__
-    table = class_mapper(ModelClass).mapped_table
-
-    fields = [field.name for field in table.c]
+    if isinstance(obj, sqlalchemy.engine.base.RowProxy):
+        fields = obj.keys()
+    else:
+        ModelClass = obj.__class__
+        table = class_mapper(ModelClass).mapped_table
+        fields = [field.name for field in table.c]
 
     for field in fields:
         name = field
+        if name in ('current', 'expired_timestamp', 'expired_id'):
+            continue
+        if name == 'continuity_id':
+            continue
         value = getattr(obj, name)
         if value is None:
             result_dict[name] = value


--- a/ckan/lib/dictization/model_dictize.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/lib/dictization/model_dictize.py	Tue Jun 07 23:19:48 2011 +0100
@@ -1,4 +1,6 @@
 from pylons import config
+from sqlalchemy.sql import select, and_
+import datetime
 
 from ckan.lib.dictization import (obj_list_dictize,
                                   obj_dict_dictize,
@@ -6,6 +8,14 @@
 import ckan.misc
 import json
 
+END_DATE = datetime.datetime(9999,12,31)
+
+class FakeSqlAlchemyObject(object):
+
+    def __init__(self, **kw):
+        for key, value in kw.iteritems():
+            self.key = value
+
 ## package save
 
 def group_list_dictize(obj_list, context, sort_key=lambda x:x):
@@ -41,6 +51,17 @@
 
     return sorted(result_list, key=lambda x: x["key"])
 
+def extras_list_dictize(extras_list, context):
+    result_list = []
+    for extra in extras_list:
+        dictized = table_dictize(extra, context)
+        value = dictized["value"]
+        if not(context.get("extras_as_string") and isinstance(value, basestring)):
+            dictized["value"] = json.dumps(value)
+        result_list.append(dictized)
+
+    return sorted(result_list, key=lambda x: x["key"])
+
 def resource_dictize(res, context):
     resource = table_dictize(res, context)
     extras = resource.pop("extras", None)
@@ -48,23 +69,72 @@
         resource.update(extras)
     return resource
 
+def _execute_with_revision(q, rev_table, context):
+
+    model = context['model']
+    meta = model.meta
+    session = model.Session
+    revision_id = context.get('revision_date')
+    revision_date = context.get('revision_date')
+    pending = context.get('pending')
+
+    if revision_id:
+        model = session.query(context['model'].Revision).filter_by()
+    
+    if revision_date:
+        q = q.where(rev_table.c.revision_timestamp >= revision_date)
+        q = q.where(rev_table.c.expired_timestamp < revision_date)
+    elif pending:
+        q = q.where(rev_table.c.expired_timestamp == '9999-12-31')
+    else:
+        q = q.where(rev_table.c.current == '1')
+    return session.execute(q)
+
+
 def package_dictize(pkg, context):
-
-    result_dict = table_dictize(pkg, context)
-
-    result_dict["resources"] = resource_list_dictize(pkg.resource_groups[0].resources_all, context)
-
-    result_dict["tags"] = obj_list_dictize(
-        pkg.tags, context, lambda x: x["name"])
-    result_dict["extras"] = extras_dict_dictize(
-        pkg._extras, context)
-    result_dict["groups"] = group_list_dictize(
-        pkg.groups, context, lambda x: x["name"])
-    result_dict["relationships_as_subject"] = obj_list_dictize(
-        pkg.relationships_as_subject, context)
-    result_dict["relationships_as_object"] = obj_list_dictize(
-        pkg.relationships_as_object, context)
-
+    model = context['model']
+    #package
+    package_rev = model.package_revision_table
+    q = select([package_rev]).where(package_rev.c.id == pkg.id)
+    result = _execute_with_revision(q, package_rev, context).first()
+    result_dict = table_dictize(result, context)
+    #resources
+    res_rev = model.resource_revision_table
+    resource_group = model.resource_group_table
+    q = select([res_rev], from_obj = res_rev.join(resource_group, 
+               resource_group.c.id == res_rev.c.resource_group_id))
+    q = q.where(resource_group.c.package_id == pkg.id)
+    result = _execute_with_revision(q, res_rev, context)
+    result_dict["resources"] = resource_list_dictize(result, context)
+    #tags
+    tag_rev = model.package_tag_revision_table
+    tag = model.tag_table
+    q = select([tag], 
+        from_obj=tag_rev.join(tag, tag.c.id == tag_rev.c.tag_id)
+        ).where(tag_rev.c.package_id == pkg.id)
+    result = _execute_with_revision(q, tag_rev, context)
+    result_dict["tags"] = obj_list_dictize(result, context, lambda x: x["name"])
+    #extras
+    extra_rev = model.extra_revision_table
+    q = select([extra_rev]).where(extra_rev.c.package_id == pkg.id)
+    result = _execute_with_revision(q, extra_rev, context)
+    result_dict["extras"] = extras_list_dictize(result, context)
+    #groups
+    group_rev = model.package_group_revision_table
+    group = model.group_table
+    q = select([group],
+               from_obj=group_rev.join(group, group.c.id == group_rev.c.group_id)
+               ).where(group_rev.c.package_id == pkg.id)
+    result = _execute_with_revision(q, group_rev, context)
+    result_dict["groups"] = obj_list_dictize(result, context)
+    #relations
+    rel_rev = model.package_relationship_revision_table
+    q = select([rel_rev]).where(rel_rev.c.subject_package_id == pkg.id)
+    result = _execute_with_revision(q, rel_rev, context)
+    result_dict["relationships_as_subject"] = obj_list_dictize(result, context)
+    q = select([rel_rev]).where(rel_rev.c.object_package_id == pkg.id)
+    result = _execute_with_revision(q, rel_rev, context)
+    result_dict["relationships_as_object"] = obj_list_dictize(result, context)
     return result_dict
 
 def group_dictize(group, context):
@@ -102,12 +172,16 @@
 def resource_dict_to_api(res_dict, package_id, context):
     res_dict.pop("revision_id")
     res_dict.pop("state")
+    res_dict.pop("revision_timestamp")
     res_dict["package_id"] = package_id
 
 
 def package_to_api1(pkg, context):
 
     dictized = package_dictize(pkg, context)
+
+    dictized.pop("revision_timestamp")
+
     dictized["groups"] = [group["name"] for group in dictized["groups"]]
     dictized["tags"] = [tag["name"] for tag in dictized["tags"]]
     dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) 
@@ -160,7 +234,10 @@
 def package_to_api2(pkg, context):
 
     dictized = package_dictize(pkg, context)
+
     dictized["groups"] = [group["id"] for group in dictized["groups"]]
+    dictized.pop("revision_timestamp")
+    
     dictized["tags"] = [tag["name"] for tag in dictized["tags"]]
     dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) 
                               for extra in dictized["extras"])


--- a/ckan/lib/dictization/model_save.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/lib/dictization/model_save.py	Tue Jun 07 23:19:48 2011 +0100
@@ -21,11 +21,11 @@
 
     table = class_mapper(model.Resource).mapped_table
     fields = [field.name for field in table.c]
-
+    
     for key, value in res_dict.iteritems():
         if isinstance(value, list):
             continue
-        if key == 'extras':
+        if key in ('extras', 'revision_timestamp'):
             continue
         if key in fields:
             setattr(obj, key, value)
@@ -201,7 +201,6 @@
         else:
             package_group.state = 'deleted'
 
-
     package.package_group_all[:] = group_package_group.values()
 
     


--- a/ckan/lib/package_saver.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/lib/package_saver.py	Tue Jun 07 23:19:48 2011 +0100
@@ -45,7 +45,7 @@
         c.pkg_maintainer_link = cls._person_email_link(c.pkg.maintainer, c.pkg.maintainer_email, "Maintainer")
         c.package_relationships = pkg.get_relationships_printable()
         c.pkg_extras = []
-        for extra in sorted(pkg.extras_list):
+        for extra in sorted(pkg.extras_list, key=lambda x:x.key):
             if extra.state == 'deleted':
                 continue
             k, v = extra.key, extra.value


--- a/ckan/logic/action/create.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/logic/action/create.py	Tue Jun 07 23:19:48 2011 +0100
@@ -71,7 +71,8 @@
     ## this is added so that the rest controller can make a new location 
     context["id"] = pkg.id
     log.debug('Created object %s' % str(pkg.name))
-    return package_dictize(pkg, context) 
+    if not preview:
+        return package_dictize(pkg, context) 
 
 def resource_create(data_dict, context):
     model = context['model']


--- a/ckan/logic/action/update.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/logic/action/update.py	Tue Jun 07 23:19:48 2011 +0100
@@ -72,16 +72,18 @@
 
     session = context['model'].Session
 
-    old_current = q.filter_by(state='active-current').first()
+    old_current = q.filter_by(current=True).first()
     if old_current:
-        old_current.state = 'active'
+        old_current.current = '0'
         session.add(old_current)
 
     latest_rev = q.filter_by(expired_timestamp='9999-12-31').one()
+    latest_rev.current = True
     if latest_rev.state in ('pending-deleted', 'deleted'):
         latest_rev.state = 'deleted'
     else:
-        latest_rev.state = 'active-current'
+        latest_rev.state = 'active'
+
     session.add(latest_rev)
         
     ##this is just a way to get the latest revision that changed


--- a/ckan/logic/schema.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/logic/schema.py	Tue Jun 07 23:19:48 2011 +0100
@@ -38,6 +38,7 @@
         'hash': [ignore_missing, unicode],
         'state': [ignore],
         'position': [ignore],
+        'revision_timestamp': [ignore],
         '__extras': [ignore_missing, extras_unicode_convert, keep_extras],
     }
 
@@ -166,6 +167,7 @@
         'value': [not_missing, unicode],
         'state': [ignore],
         'deleted': [ignore_missing],
+        'revision_timestamp': [ignore],
     }
     return schema
 


--- a/ckan/migration/versions/039_add_expired_id_and_dates.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/migration/versions/039_add_expired_id_and_dates.py	Tue Jun 07 23:19:48 2011 +0100
@@ -35,48 +35,57 @@
 ALTER TABLE package_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE package_extra_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE group_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE group_extra_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 
 ALTER TABLE package_group_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE package_tag_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE resource_group_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE resource_revision
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE package_relationship_revision 
 	ADD COLUMN expired_id text,
 	ADD COLUMN revision_timestamp timestamp without time zone,
-	ADD COLUMN expired_timestamp timestamp without time zone;
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
 
 ALTER TABLE revision
 	ADD COLUMN approved_timestamp timestamp without time zone;
@@ -91,6 +100,7 @@
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
 update package_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update package_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_package_period on package_revision(revision_timestamp, expired_timestamp, id);
 create index idx_package_expired on package_revision(expired_timestamp);
@@ -101,7 +111,7 @@
 update package_extra_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update package_extra_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update package_extra_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_package_extra_period on package_extra_revision(revision_timestamp, expired_timestamp, id);
 create index idx_package_extra_period_package on package_extra_revision(revision_timestamp, expired_timestamp, package_id);
@@ -113,7 +123,7 @@
 update package_group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update package_group_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update package_group_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_package_group_period_package_group on package_group_revision(revision_timestamp, expired_timestamp, package_id, group_id);
 create index idx_package_group_expired on package_group_revision(expired_timestamp);
@@ -125,7 +135,7 @@
 update package_tag_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update package_tag_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update package_tag_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_period_package_tag on package_tag_revision(revision_timestamp, expired_timestamp, package_id, tag_id);
 create index idx_package_tag_expired on package_tag_revision(expired_timestamp);
@@ -136,7 +146,7 @@
 update package_relationship_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update package_relationship_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update package_relationship_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_period_package_relationship on package_relationship_revision(revision_timestamp, expired_timestamp, object_package_id, subject_package_id);
 create index idx_package_relationship_expired on package_relationship_revision(expired_timestamp);
@@ -147,7 +157,7 @@
 update resource_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update resource_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update resource_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_resource_period on resource_revision(revision_timestamp, expired_timestamp, id);
 create index idx_resource_period_resource_group on resource_revision(revision_timestamp, expired_timestamp, resource_group_id);
@@ -159,7 +169,7 @@
 update resource_group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update resource_group_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update resource_group_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_resource_group_period on resource_group_revision(revision_timestamp, expired_timestamp, id);
 create index idx_resource_group_period_package on resource_group_revision(revision_timestamp, expired_timestamp, package_id);
@@ -171,7 +181,7 @@
 update group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update group_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update group_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_group_period on group_revision(revision_timestamp, expired_timestamp, id);
 create index idx_group_expired on group_revision(expired_timestamp);
@@ -182,7 +192,7 @@
 update group_extra_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
                                expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
-update group_extra_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update group_extra_revision set current = '1' where expired_timestamp = '9999-12-31';
 
 create index idx_group_extra_period on group_extra_revision(revision_timestamp, expired_timestamp, id);
 create index idx_group_extra_period_group on group_extra_revision(revision_timestamp, expired_timestamp, group_id);
@@ -202,7 +212,7 @@
         count = migrate_engine.execute('''select count(*) from "%s"''' % table).first()[0]
         revision_expired_id_count = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.expired_id is null''' % (table, table)).first()[0]
         revision_expired_data_count = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.expired_timestamp = '9999-12-31' ''' % (table, table)).first()[0]
-        revision_current = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.state = 'active-current' ''' % (table, table)).first()[0]
+        revision_current = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.current = '1' ''' % (table, table)).first()[0]
         assert count == revision_expired_id_count
         assert count == revision_expired_data_count
         assert count == revision_current


--- a/ckan/model/changeset.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/model/changeset.py	Tue Jun 07 23:19:48 2011 +0100
@@ -981,7 +981,7 @@
 
     def get_columns(self):
         """Returns the model of the entity attributes."""
-        from ckan.model.core import orm
+        from sqlalchemy import orm
         table = orm.class_mapper(self.object_type).mapped_table
         return table.c
 


--- a/ckan/model/core.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/model/core.py	Tue Jun 07 23:19:48 2011 +0100
@@ -1,5 +1,5 @@
 from meta import metadata, mapper
-from sqlalchemy import Column, ForeignKey, DateTime, Text, orm
+from sqlalchemy import Column, DateTime, Text, Boolean
 import vdm.sqlalchemy
 
 from domain_object import DomainObject
@@ -35,5 +35,6 @@
     revision_table.append_column(Column('revision_timestamp', DateTime))
     revision_table.append_column(Column('expired_timestamp', DateTime, 
                                  default='9999-12-31'))
+    revision_table.append_column(Column('current', Boolean))
     return revision_table
 


--- a/ckan/model/group.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/model/group.py	Tue Jun 07 23:19:48 2011 +0100
@@ -10,7 +10,8 @@
 from ckan.model import extension
 
 __all__ = ['group_table', 'Group', 'package_revision_table',
-           'PackageGroup', 'GroupRevision', 'PackageGroupRevision']
+           'PackageGroup', 'GroupRevision', 'PackageGroupRevision',
+           'package_group_revision_table']
 
 package_group_table = Table('package_group', metadata,
     Column('id', UnicodeText, primary_key=True, default=make_uuid),


--- a/ckan/model/meta.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/model/meta.py	Tue Jun 07 23:19:48 2011 +0100
@@ -43,6 +43,7 @@
         deleted = obj_cache['deleted']
 
         for obj in new | changed | deleted:
+            
             if not hasattr(obj, '__revision_class__'):
                 continue
 
@@ -52,11 +53,11 @@
             if 'pending' not in obj.state:
                 revision.approved_timestamp = datetime.now()
                 old = session.query(revision_cls).filter_by(
-                    state='active-current',
+                    current='1',
                     id = obj.id
                 ).first()
                 if old:
-                    old.state = 'active'
+                    old.current = '0'
                     session.add(old)
 
             q = session.query(revision_cls)
@@ -67,7 +68,7 @@
                 if rev_obj.revision_id == revision.id:
                     rev_obj.revision_timestamp = revision.timestamp
                     if 'pending' not in obj.state:
-                        rev_obj.state = 'active-current'
+                        rev_obj.current = '1'
                 else:
                     rev_obj.expired_id = revision.id
                     rev_obj.expired_timestamp = revision.timestamp


--- a/ckan/model/package_extra.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/model/package_extra.py	Tue Jun 07 23:19:48 2011 +0100
@@ -7,7 +7,8 @@
 from types import JsonType
 from ckan.model import extension
 
-__all__ = ['PackageExtra', 'package_extra_table', 'PackageExtraRevision']
+__all__ = ['PackageExtra', 'package_extra_table', 'PackageExtraRevision',
+           'extra_revision_table']
 
 package_extra_table = Table('package_extra', metadata,
     Column('id', UnicodeText, primary_key=True, default=make_uuid),


--- a/ckan/model/tag.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/model/tag.py	Tue Jun 07 23:19:48 2011 +0100
@@ -8,7 +8,7 @@
 from core import *
 
 __all__ = ['tag_table', 'package_tag_table', 'Tag', 'PackageTag',
-           'PackageTagRevision']
+           'PackageTagRevision', 'package_tag_revision_table']
 
 tag_table = Table('tag', metadata,
         Column('id', types.UnicodeText, primary_key=True, default=make_uuid),


--- a/ckan/tests/functional/test_package.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/tests/functional/test_package.py	Tue Jun 07 23:19:48 2011 +0100
@@ -883,7 +883,7 @@
             assert len(pkg.groups) == 0
             grp = model.Group.by_name(u'david')
             model.repo.new_revision()
-            pkg.groups.append(grp)
+            model.Session.add(model.PackageGroup(package=pkg, group=grp))
             model.repo.commit_and_remove()
             pkg = model.Package.by_name(u'editpkgtest')
             assert len(pkg.groups) == 1
@@ -892,6 +892,7 @@
             prefix = ''
             field_name = prefix + "groups__0__id"
             fv = res.forms['package-edit']
+            print field_name
             fv[field_name] = False
             res = fv.submit('save', extra_environ={'REMOTE_USER':'russianfan'})
             model.repo.commit_and_remove()


--- a/ckan/tests/lib/test_dictization.py	Mon Jun 06 23:34:28 2011 +0100
+++ b/ckan/tests/lib/test_dictization.py	Tue Jun 07 23:19:48 2011 +0100
@@ -25,6 +25,7 @@
     @classmethod
     def setup_class(cls):
         CreateTestData.create()
+        
 
     @classmethod
     def teardown_class(cls):
@@ -42,6 +43,8 @@
                 dict.pop(key)
             if key == 'created':
                 dict.pop(key)
+            if 'timestamp' in key:
+                dict.pop(key)
             if isinstance(value, list):
                 for new_dict in value:
                     self.remove_changable_columns(new_dict)
@@ -49,7 +52,8 @@
 
     def remove_revision_id(self, dict):
         for key, value in dict.items():
-            if key == 'revision_id':
+            if key in ('revision_id', 'revision_timestamp',
+                       'expired_timestamp', 'expired_id'):
                 dict.pop(key)
             if isinstance(value, list):
                 for new_dict in value:
@@ -123,8 +127,7 @@
         self.remove_changable_columns(result)
 
         
-        assert result ==\
-            {'author': None,
+        expected = {'author': None,
              'author_email': None,
              'extras': [
                 {'key': u'genre',
@@ -166,7 +169,13 @@
              'tags': [{'name': u'russian'}, {'name': u'tolstoy'}],
              'title': u'A Novel By Tolstoy',
              'url': u'http://www.annakarenina.com',
-             'version': u'0.7a'}, pprint(result)
+             'version': u'0.7a'}
+
+        pprint(result)
+        pprint(expected)
+
+        assert sorted(result.values()) == sorted(expected.values())
+        assert result == expected
 
 
 
@@ -310,13 +319,19 @@
 
         package_dictized = package_dictize(pkg, context)
 
+        resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
+
+        sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
+        for res in sorted_resources:
+            print res.id, res.revision_timestamp, res.expired_timestamp, res.state, res.current
+        assert len(sorted_resources) == 3
+
         anna_original = pformat(anna_dictized)
         anna_after_save = pformat(package_dictized)
 
         print anna_original
         print anna_after_save
 
-
         assert self.remove_revision_id(anna_dictized) == self.remove_revision_id(package_dictized),\
                 "\n".join(unified_diff(anna_original.split("\n"), anna_after_save.split("\n")))
 
@@ -347,7 +362,8 @@
 
         assert len(sorted_packages) == 3
         assert sorted_packages[0].state == 'pending'
-        assert sorted_packages[1].state == 'active-current'
+        assert sorted_packages[1].state == 'active'
+        assert sorted_packages[1].current
         assert sorted_packages[2].state == 'active'
 
         assert str(sorted_packages[0].expired_timestamp) == '9999-12-31 00:00:00'
@@ -355,13 +371,17 @@
         assert str(sorted_packages[2].expired_timestamp) != '9999-12-31 00:00:00'
 
         resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
+        sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
 
-        sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
+        for pkg in sorted_resources:
+            print pkg.url, pkg.id, pkg.revision_timestamp, pkg.expired_timestamp, pkg.state, pkg.current
 
         assert len(sorted_resources) == 4
         assert sorted_resources[0].state == 'pending'
-        assert sorted_resources[1].state == 'active-current'
-        assert sorted_resources[2].state == 'active-current'
+        assert sorted_resources[1].state == 'active'
+        assert sorted_resources[1].current
+        assert sorted_resources[2].state == 'active'
+        assert sorted_resources[2].current
         assert sorted_resources[3].state == 'active'
 
         assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
@@ -378,8 +398,9 @@
         assert len(sorted_tags) == 4, len(sorted_tags)
         assert sorted_tags[0].state == 'pending-deleted'
         assert sorted_tags[1].state == 'pending'
-        assert sorted_tags[2].state == 'active-current'
-        assert sorted_tags[3].state == 'active-current'
+        assert sorted_tags[2].state == 'active'
+        assert sorted_resources[2].current
+        assert sorted_tags[3].state == 'active'
 
         assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
@@ -392,8 +413,10 @@
                                key=lambda x: (x.revision_timestamp, x.key))[::-1]
 
         assert sorted_extras[0].state == 'pending'
-        assert sorted_extras[1].state == 'active-current'
-        assert sorted_extras[2].state == 'active-current'
+        assert sorted_resources[1].current
+        assert sorted_extras[1].state == 'active'
+        assert sorted_resources[1].current
+        assert sorted_extras[2].state == 'active'
 
         assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
@@ -426,12 +449,19 @@
         resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
 
         sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
+        pprint(anna_dictized['resources'])
 
-        assert len(sorted_resources) == 5
+        for pkg in sorted_resources:
+            print pkg.url, pkg.id, pkg.revision_timestamp, pkg.expired_timestamp, pkg.state, pkg.current
+
+
+        assert len(sorted_resources) == 5, len(sorted_resources)
         assert sorted_resources[0].state == 'pending'
         assert sorted_resources[1].state == 'pending'
-        assert sorted_resources[2].state == 'active-current'
-        assert sorted_resources[3].state == 'active-current'
+        assert sorted_resources[2].current
+        assert sorted_resources[2].state == 'active'
+        assert sorted_resources[3].current
+        assert sorted_resources[3].state == 'active'
         assert sorted_resources[4].state == 'active'
 
         assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
@@ -446,19 +476,20 @@
 
         print [(tag.state, tag.tag.name) for tag in sorted_tags]
 
-        assert len(sorted_tags) == 6, len(sorted_tags)
+        assert len(sorted_tags) == 5, len(sorted_tags)
         assert sorted_tags[0].state == 'pending'
         assert sorted_tags[1].state == 'pending-deleted'
-        assert sorted_tags[2].state == 'pending-deleted'
-        assert sorted_tags[3].state == 'pending'
-        assert sorted_tags[4].state == 'active-current'
-        assert sorted_tags[5].state == 'active-current'
+        assert sorted_tags[2].state == 'pending'
+        assert sorted_tags[3].state == 'active'
+        assert sorted_tags[3].current
+        assert sorted_tags[4].state == 'active'
+        assert sorted_tags[4].current
 
         assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[2].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_tags[3].expired_timestamp) != '9999-12-31 00:00:00'
-        assert str(sorted_tags[4].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[3].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[4].expired_timestamp) != '9999-12-31 00:00:00'
 
         extras_revisions = model.Session.query(model.PackageExtraRevision).filter_by(package_id=anna1.id).all()
 
@@ -467,17 +498,15 @@
 
         print [(extra.state, extra.key, extra.value) for extra in sorted_extras]
 
-        assert sorted_extras[0].state == 'pending-deleted'
+        assert sorted_extras[0].state == 'pending'
         assert sorted_extras[1].state == 'pending'
-        assert sorted_extras[2].state == 'pending'
-        assert sorted_extras[3].state == 'active-current'
-        assert sorted_extras[4].state == 'active-current'
+        assert sorted_extras[2].state == 'active'
+        assert sorted_extras[3].state == 'active'
 
         assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_extras[2].expired_timestamp) != '9999-12-31 00:00:00'
-        assert str(sorted_extras[3].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_extras[4].expired_timestamp) != '9999-12-31 00:00:00'
+        assert str(sorted_extras[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[3].expired_timestamp) != '9999-12-31 00:00:00'
 
     def test_12_make_active(self):
 
@@ -492,7 +521,8 @@
         sorted_packages = sorted(pkgrevisions, key=lambda x:x.revision_timestamp)[::-1]
 
         assert len(sorted_packages) == 3
-        assert sorted_packages[0].state == 'active-current' #was pending
+        assert sorted_packages[0].state == 'active', sorted_packages[0].state #was pending
+        assert sorted_packages[0].current == True #was pending
         assert sorted_packages[1].state == 'active' #was active-current
         assert sorted_packages[2].state == 'active'
 
@@ -502,10 +532,13 @@
         assert len(sorted_resources) == 5
         for res in sorted_resources:
             print res.id, res.revision_timestamp, res.expired_timestamp, res.state
-        assert sorted_resources[0].state == 'active-current'
-        assert sorted_resources[1].state == 'active-current'
+        assert sorted_resources[0].state == 'active'
+        assert sorted_resources[0].current == True
+        assert sorted_resources[1].state == 'active'
+        assert sorted_resources[1].current == True
         assert sorted_resources[2].state == 'active'
-        assert sorted_resources[3].state == 'active-current'
+        assert sorted_resources[3].state == 'active'
+        assert sorted_resources[3].current == True
         assert sorted_resources[4].state == 'active'
 
         assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
@@ -520,19 +553,21 @@
 
         print [(tag.state, tag.tag.name) for tag in sorted_tags]
 
-        assert len(sorted_tags) == 6, len(sorted_tags)
-        assert sorted_tags[0].state == 'active-current'
+        assert len(sorted_tags) == 5, len(sorted_tags)
+        assert sorted_tags[0].state == 'active'
+        assert sorted_tags[0].current
         assert sorted_tags[1].state == 'deleted'
-        assert sorted_tags[2].state == 'deleted'
-        assert sorted_tags[3].state == 'pending'
-        assert sorted_tags[4].state == 'active-current'
-        assert sorted_tags[5].state == 'active'
+        assert sorted_tags[1].current
+        assert sorted_tags[2].state == 'active'
+        assert sorted_tags[2].current
+        assert sorted_tags[3].state == 'active'
+        assert sorted_tags[4].state == 'active'
 
         assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_tags[2].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_tags[3].expired_timestamp) != '9999-12-31 00:00:00'
-        assert str(sorted_tags[4].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[3].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[4].expired_timestamp) != '9999-12-31 00:00:00'
 
         extras_revisions = model.Session.query(model.PackageExtraRevision).filter_by(package_id=anna1.id).all()
 
@@ -541,18 +576,15 @@
 
         print [(extra.state, extra.key, extra.value) for extra in sorted_extras]
 
-        assert sorted_extras[0].state == 'deleted'
-        assert sorted_extras[1].state == 'active-current'
-        assert sorted_extras[2].state == 'pending'
-        assert sorted_extras[3].state == 'active-current'
-        assert sorted_extras[4].state == 'active'
+        assert sorted_extras[0].state == 'active'
+        assert sorted_extras[1].state == 'active'
+        assert sorted_extras[2].state == 'active'
+        assert sorted_extras[3].state == 'active'
 
         assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_extras[2].expired_timestamp) != '9999-12-31 00:00:00'
-        assert str(sorted_extras[3].expired_timestamp) == '9999-12-31 00:00:00'
-        assert str(sorted_extras[4].expired_timestamp) != '9999-12-31 00:00:00'
-
+        assert str(sorted_extras[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[3].expired_timestamp) != '9999-12-31 00:00:00'
 
     def test_12_resource_no_id(self):
 


http://bitbucket.org/okfn/ckan/changeset/63ad891e0a23/
changeset:   63ad891e0a23
branch:      feature-1147-add-expired_id-to-revision-tables
user:        kindly
date:        2011-06-08 00:20:13
summary:     [merge default] all queries not using orm and current added
affected #:  11 files (39.7 KB)

--- a/ckan/controllers/authorization_group.py	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/controllers/authorization_group.py	Tue Jun 07 23:20:13 2011 +0100
@@ -142,85 +142,214 @@
             h.redirect_to(action='read', id=c.authorization_group_name)
 
     def authz(self, id):
-        c.authorization_group = model.AuthorizationGroup.by_name(id)
-        if c.authorization_group is None:
+        authorization_group = model.AuthorizationGroup.by_name(id)
+        if authorization_group is None:
             abort(404, _('Group not found'))
-        c.authorization_group_name = c.authorization_group.name
-        
+
+        c.authorization_group_name = authorization_group.name
+
         c.authz_editable = self.authorizer.am_authorized(c, model.Action.EDIT_PERMISSIONS, 
-                                                         c.authorization_group)
+                                                         authorization_group)
+
         if not c.authz_editable:
-            abort(401, _('Not authorized to edit authorization for group'))
+            abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id))
 
-        if 'save' in request.params: # form posted
-            # needed because request is nested
-            # multidict which is read only
-            params = dict(request.params)
-            c.fs = ckan.forms.get_authz_fieldset('authorization_group_authz_fs').bind(
-                                                 c.authorization_group.roles, 
-                                                 data=params or None)
-            try:
-                self._update_authz(c.fs)
-            except ValidationException, error:
-                # TODO: sort this out 
-                # fs = error.args[0]
-                # return render('group/authz.html')
-                raise
-            # now do new roles
-            newrole_user_id = request.params.get('AuthorizationGroupRole--user_id')
-            newrole_authzgroup_id = request.params.get('AuthorizationGroupRole--authorized_group_id')
-            if newrole_user_id != '__null_value__' and newrole_authzgroup_id != '__null_value__':
-                c.message = _(u'Please select either a user or an authorization group, not both.')
-            elif newrole_user_id != '__null_value__':
-                user = model.Session.query(model.User).get(newrole_user_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('AuthorizationGroupRole--role')
-                newauthzgrouprole = model.AuthorizationGroupRole(user=user, 
-                        authorization_group=c.authorization_group, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for user \'%s\'') % (
-                    newauthzgrouprole.role,
-                    newauthzgrouprole.user.name)      
-            elif newrole_authzgroup_id != '__null_value__':
-                authzgroup = model.Session.query(model.AuthorizationGroup).get(newrole_authzgroup_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('AuthorizationGroupRole--role')
-                newauthzgrouprole = model.AuthorizationGroupRole(authorized_group=authzgroup, 
-                        authorization_group=c.authorization_group, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for authorization group \'%s\'') % (
-                    newauthzgrouprole.role,
-                    newauthzgrouprole.authorized_group.name)
-        elif 'role_to_delete' in request.params:
-            authzgrouprole_id = request.params['role_to_delete']
-            authzgrouprole = model.Session.query(model.AuthorizationGroupRole).get(authzgrouprole_id)
-            if authzgrouprole is None:
-                c.error = _(u'Error: No role found with that id')
+        #see package.py for comments
+        def get_userobjectroles():
+            authorization_group = model.AuthorizationGroup.by_name(id)
+            uors = model.Session.query(model.AuthorizationGroupRole).join('authorization_group').filter_by(name=authorization_group.name).all()
+            return uors
+
+        def action_save_form(users_or_authz_groups):
+            # The permissions grid has been saved
+            # which is a grid of checkboxes named user$role
+            rpi = request.params.items()
+
+            # The grid passes us a list of the users/roles that were displayed
+            submitted = [ a for (a,b) in rpi if (b == u'submitted')]
+            # and also those which were checked
+            checked = [ a for (a,b) in rpi if (b == u'on')]
+
+            # from which we can deduce true/false for each user/role combination
+            # that was displayed in the form
+            table_dict={}
+            for a in submitted:
+                table_dict[a]=False
+            for a in checked:
+                table_dict[a]=True
+
+            # now we'll split up the user$role strings to make a dictionary from 
+            # (user,role) to True/False, which tells us what we need to do.
+            new_user_role_dict={}
+            for (ur,val) in table_dict.items():
+                u,r = ur.split('$')
+                new_user_role_dict[(u,r)] = val
+               
+            # we get the current user/role assignments 
+            # and make a dictionary of them
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_users_roles = [( uor.user.name, uor.role) for uor in current_uors if uor.user]
+            elif users_or_authz_groups=='authz_groups':
+                current_users_roles = [( uor.authorized_group.name, uor.role) for uor in current_uors if uor.authorized_group]        
             else:
-                authzgrouprole.purge()
-                if authzgrouprole.user:
-                    c.message = _(u'Deleted role \'%s\' for user \'%s\'') % \
-                                (authzgrouprole.role, authzgrouprole.user.name)
-                elif authzgrouprole.authorized_group:
-                    c.message = _(u'Deleted role \'%s\' for authorization group \'%s\'') % \
-                                (authzgrouprole.role, authzgrouprole.authorized_group.name)
-                model.Session.commit()
+                assert False, "shouldn't be here"
 
-        # retrieve group again ...
-        c.authorization_group = model.AuthorizationGroup.by_name(id)
-        fs = ckan.forms.get_authz_fieldset('authorization_group_authz_fs')\
-                .bind(c.authorization_group.roles)
-        c.form = fs.render()
-        c.new_roles_form = \
-            ckan.forms.get_authz_fieldset('new_authorization_group_roles_fs').render()
+            current_user_role_dict={}
+            for (u,r) in current_users_roles:
+                current_user_role_dict[(u,r)]=True
+
+            # and now we can loop through our dictionary of desired states
+            # checking whether a change needs to be made, and if so making it
+
+            # Here we check whether someone is already assigned a role, in order
+            # to avoid assigning it twice, or attempting to delete it when it
+            # doesn't exist. Otherwise problems can occur.
+            if users_or_authz_groups=='users':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_user_to_role(model.User.by_name(u),r,authorization_group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_user_from_role(model.User.by_name(u),r,authorization_group)
+            elif users_or_authz_groups=='authz_groups':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_authorization_group_to_role(model.AuthorizationGroup.by_name(u),r,authorization_group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_authorization_group_from_role(model.AuthorizationGroup.by_name(u),r,authorization_group)
+            else:
+                assert False, "shouldn't be here"
+
+            # finally commit the change to the database
+            model.repo.commit_and_remove()
+            h.flash_success("Changes Saved")
+
+
+
+        def action_add_form(users_or_authz_groups):
+            # The user is attempting to set new roles for a named user
+            new_user = request.params.get('new_user_name')
+            # this is the list of roles whose boxes were ticked
+            checked_roles = [ a for (a,b) in request.params.items() if (b == u'on')]
+            # this is the list of all the roles that were in the submitted form
+            submitted_roles = [ a for (a,b) in request.params.items() if (b == u'submitted')]
+
+            # from this we can make a dictionary of the desired states
+            # i.e. true for the ticked boxes, false for the unticked
+            desired_roles = {}
+            for r in submitted_roles:
+                desired_roles[r]=False
+            for r in checked_roles:
+                desired_roles[r]=True
+
+            # again, in order to avoid either creating a role twice or deleting one which is
+            # non-existent, we need to get the users' current roles (if any)
+  
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_roles = [uor.role for uor in current_uors if ( uor.user and uor.user.name == new_user )]
+                user_object = model.User.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown user:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_user_to_role(user_object, r, authorization_group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_user_from_role(user_object, r, authorization_group)
+                    h.flash_success("User Added")
+
+            elif users_or_authz_groups=='authz_groups':
+                current_roles = [uor.role for uor in current_uors if ( uor.authorized_group and uor.authorized_group.name == new_user )]
+                user_object = model.AuthorizationGroup.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown authorization group:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_authorization_group_to_role(user_object, r, authorization_group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_authorization_group_from_role(user_object, r, authorization_group)
+                    h.flash_success("Authorization Group Added")
+
+            else:
+                assert False, "shouldn't be here"
+
+            # and finally commit all these changes to the database
+            model.repo.commit_and_remove()
+
+
+        # In the event of a post request, work out which of the four possible actions
+        # is to be done, and do it before displaying the page
+        if 'add' in request.POST:
+            action_add_form('users')
+
+        if 'authz_add' in request.POST:
+            action_add_form('authz_groups')
+
+        if 'save' in request.POST:
+            action_save_form('users')
+
+        if 'authz_save' in request.POST:
+            action_save_form('authz_groups')
+
+        # =================
+        # Display the page
+
+        # Find out all the possible roles. At the moment, any role can be
+        # associated with any object, so that's easy:
+        possible_roles = model.Role.get_all()
+
+        # get the list of users who have roles on this object, with their roles
+        uors = get_userobjectroles()
+
+        # uniquify and sort
+        users = sorted(list(set([uor.user.name for uor in uors if uor.user])))
+        authz_groups = sorted(list(set([uor.authorized_group.name for uor in uors if uor.authorized_group])))
+
+        # make a dictionary from (user, role) to True, False
+        users_roles = [( uor.user.name, uor.role) for uor in uors if uor.user]
+        user_role_dict={}
+        for u in users:
+            for r in possible_roles:
+                if (u,r) in users_roles:
+                    user_role_dict[(u,r)]=True
+                else:
+                    user_role_dict[(u,r)]=False
+
+        # and similarly make a dictionary from (authz_group, role) to True, False
+        authz_groups_roles = [( uor.authorized_group.name, uor.role) for uor in uors if uor.authorized_group]
+        authz_groups_role_dict={}
+        for u in authz_groups:
+            for r in possible_roles:
+                if (u,r) in authz_groups_roles:
+                    authz_groups_role_dict[(u,r)]=True
+                else:
+                    authz_groups_role_dict[(u,r)]=False
+
+        # pass these variables to the template for rendering
+        c.roles = possible_roles
+
+        c.users = users
+        c.user_role_dict = user_role_dict
+
+        c.authz_groups = authz_groups
+        c.authz_groups_role_dict = authz_groups_role_dict
+
         return render('authorization_group/authz.html')
 
     def _render_edit_form(self, fs):


--- a/ckan/controllers/group.py	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/controllers/group.py	Tue Jun 07 23:20:13 2011 +0100
@@ -2,7 +2,7 @@
 
 from sqlalchemy.orm import eagerload_all
 from ckan.lib.base import BaseController, c, model, request, render, h
-from ckan.lib.base import ValidationException, abort
+from ckan.lib.base import ValidationException, abort, gettext
 from pylons.i18n import get_lang, _
 import ckan.authz as authz
 from ckan.authz import Authorizer
@@ -180,91 +180,222 @@
             return self.edit(id, data_dict, errors, error_summary)
 
     def authz(self, id):
-        c.group = model.Group.get(id)
-        if c.group is None:
+        group = model.Group.get(id)
+        if group is None:
             abort(404, _('Group not found'))
-        
-        c.groupname = c.group.name
-        c.grouptitle = c.group.display_name
+        c.groupname = group.name
+        c.grouptitle = group.display_name
 
-        c.authz_editable = self.authorizer.am_authorized(c, model.Action.EDIT_PERMISSIONS, c.group)
+        c.authz_editable = self.authorizer.am_authorized(c, model.Action.EDIT_PERMISSIONS, group)
         if not c.authz_editable:
-            abort(401, _('Not authorized to edit authorization for group'))
+            abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id))
+ 
 
-        if 'save' in request.params: # form posted
-            # needed because request is nested
-            # multidict which is read only
-            params = dict(request.params)
-            c.fs = ckan.forms.get_authz_fieldset('group_authz_fs').bind(c.group.roles, data=params or None)
-            try:
-                self._update_authz(c.fs)
-            except ValidationException, error:
-                # TODO: sort this out 
-                # fs = error.args[0]
-                # return render('group/authz.html')
-                raise
-            # now do new roles
-            newrole_user_id = request.params.get('GroupRole--user_id')
-            newrole_authzgroup_id = request.params.get('GroupRole--authorized_group_id')
-            if newrole_user_id != '__null_value__' and newrole_authzgroup_id != '__null_value__':
-                c.message = _(u'Please select either a user or an authorization group, not both.')
-            elif newrole_user_id != '__null_value__':
-                user = model.Session.query(model.User).get(newrole_user_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('GroupRole--role')
-                newgrouprole = model.GroupRole(user=user, group=c.group,
-                        role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                for extension in self.extensions:
-                    extension.authz_add_role(newgrouprole)
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for user \'%s\'') % (
-                    newgrouprole.role,
-                    newgrouprole.user.display_name)
-            elif newrole_authzgroup_id != '__null_value__':
-                authzgroup = model.Session.query(model.AuthorizationGroup).get(newrole_authzgroup_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('GroupRole--role')
-                newgrouprole = model.GroupRole(authorized_group=authzgroup, 
-                        group=c.group, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                for extension in self.extensions:
-                    extension.authz_add_role(newgrouprole)
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for authorization group \'%s\'') % (
-                    newgrouprole.role,
-                    newgrouprole.authorized_group.name)
-        elif 'role_to_delete' in request.params:
-            grouprole_id = request.params['role_to_delete']
-            grouprole = model.Session.query(model.GroupRole).get(grouprole_id)
-            if grouprole is None:
-                c.error = _(u'Error: No role found with that id')
+        #see package.py for comments
+        def get_userobjectroles():
+            group = model.Group.get(id)
+            uors = model.Session.query(model.GroupRole).join('group').filter_by(name=group.name).all()
+            return uors
+
+        def action_save_form(users_or_authz_groups):
+            # The permissions grid has been saved
+            # which is a grid of checkboxes named user$role
+            rpi = request.params.items()
+
+            # The grid passes us a list of the users/roles that were displayed
+            submitted = [ a for (a,b) in rpi if (b == u'submitted')]
+            # and also those which were checked
+            checked = [ a for (a,b) in rpi if (b == u'on')]
+
+            # from which we can deduce true/false for each user/role combination
+            # that was displayed in the form
+            table_dict={}
+            for a in submitted:
+                table_dict[a]=False
+            for a in checked:
+                table_dict[a]=True
+
+            # now we'll split up the user$role strings to make a dictionary from 
+            # (user,role) to True/False, which tells us what we need to do.
+            new_user_role_dict={}
+            for (ur,val) in table_dict.items():
+                u,r = ur.split('$')
+                new_user_role_dict[(u,r)] = val
+               
+            # we get the current user/role assignments 
+            # and make a dictionary of them
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_users_roles = [( uor.user.name, uor.role) for uor in current_uors if uor.user]
+            elif users_or_authz_groups=='authz_groups':
+                current_users_roles = [( uor.authorized_group.name, uor.role) for uor in current_uors if uor.authorized_group]        
             else:
-                for extension in self.extensions:
-                    extension.authz_remove_role(grouprole)
-                grouprole.purge()
-                if grouprole.user:
-                    c.message = _(u'Deleted role \'%s\' for user \'%s\'') % \
-                                (grouprole.role, grouprole.user.display_name)
-                elif grouprole.authorized_group:
-                    c.message = _(u'Deleted role \'%s\' for authorization group \'%s\'') % \
-                                (grouprole.role, grouprole.authorized_group.name)
-                model.Session.commit()
+                assert False, "shouldn't be here"
 
-        # retrieve group again ...
-        c.group = model.Group.get(id)
-        fs = ckan.forms.get_authz_fieldset('group_authz_fs').bind(c.group.roles)
-        c.form = fs.render()
-        c.new_roles_form = \
-            ckan.forms.get_authz_fieldset('new_group_roles_fs').render()
+            current_user_role_dict={}
+            for (u,r) in current_users_roles:
+                current_user_role_dict[(u,r)]=True
+
+            # and now we can loop through our dictionary of desired states
+            # checking whether a change needs to be made, and if so making it
+
+            # Here we check whether someone is already assigned a role, in order
+            # to avoid assigning it twice, or attempting to delete it when it
+            # doesn't exist. Otherwise problems can occur.
+            if users_or_authz_groups=='users':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_user_to_role(model.User.by_name(u),r,group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_user_from_role(model.User.by_name(u),r,group)
+            elif users_or_authz_groups=='authz_groups':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_authorization_group_to_role(model.AuthorizationGroup.by_name(u),r,group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_authorization_group_from_role(model.AuthorizationGroup.by_name(u),r,group)
+            else:
+                assert False, "shouldn't be here"
+
+
+            # finally commit the change to the database
+            model.repo.commit_and_remove()
+            h.flash_success("Changes Saved")
+
+
+
+        def action_add_form(users_or_authz_groups):
+            # The user is attempting to set new roles for a named user
+            new_user = request.params.get('new_user_name')
+            # this is the list of roles whose boxes were ticked
+            checked_roles = [ a for (a,b) in request.params.items() if (b == u'on')]
+            # this is the list of all the roles that were in the submitted form
+            submitted_roles = [ a for (a,b) in request.params.items() if (b == u'submitted')]
+
+            # from this we can make a dictionary of the desired states
+            # i.e. true for the ticked boxes, false for the unticked
+            desired_roles = {}
+            for r in submitted_roles:
+                desired_roles[r]=False
+            for r in checked_roles:
+                desired_roles[r]=True
+
+            # again, in order to avoid either creating a role twice or deleting one which is
+            # non-existent, we need to get the users' current roles (if any)
+  
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_roles = [uor.role for uor in current_uors if ( uor.user and uor.user.name == new_user )]
+                user_object = model.User.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown user:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_user_to_role(user_object, r, group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_user_from_role(user_object, r, group)
+                    h.flash_success("User Added")
+
+            elif users_or_authz_groups=='authz_groups':
+                current_roles = [uor.role for uor in current_uors if ( uor.authorized_group and uor.authorized_group.name == new_user )]
+                user_object = model.AuthorizationGroup.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown authorization group:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_authorization_group_to_role(user_object, r, group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_authorization_group_from_role(user_object, r, group)
+                    h.flash_success("Authorization Group Added")
+
+            else:
+                assert False, "shouldn't be here"
+
+            # and finally commit all these changes to the database
+            model.repo.commit_and_remove()
+
+
+        # In the event of a post request, work out which of the four possible actions
+        # is to be done, and do it before displaying the page
+        if 'add' in request.POST:
+            action_add_form('users')
+
+        if 'authz_add' in request.POST:
+            action_add_form('authz_groups')
+
+        if 'save' in request.POST:
+            action_save_form('users')
+
+        if 'authz_save' in request.POST:
+            action_save_form('authz_groups')
+
+        # =================
+        # Display the page
+
+        # Find out all the possible roles. At the moment, any role can be
+        # associated with any object, so that's easy:
+        possible_roles = model.Role.get_all()
+
+        # get the list of users who have roles on this object, with their roles
+        uors = get_userobjectroles()
+
+        # uniquify and sort
+        users = sorted(list(set([uor.user.name for uor in uors if uor.user])))
+        authz_groups = sorted(list(set([uor.authorized_group.name for uor in uors if uor.authorized_group])))
+
+        # make a dictionary from (user, role) to True, False
+        users_roles = [( uor.user.name, uor.role) for uor in uors if uor.user]
+        user_role_dict={}
+        for u in users:
+            for r in possible_roles:
+                if (u,r) in users_roles:
+                    user_role_dict[(u,r)]=True
+                else:
+                    user_role_dict[(u,r)]=False
+
+        # and similarly make a dictionary from (authz_group, role) to True, False
+        authz_groups_roles = [( uor.authorized_group.name, uor.role) for uor in uors if uor.authorized_group]
+        authz_groups_role_dict={}
+        for u in authz_groups:
+            for r in possible_roles:
+                if (u,r) in authz_groups_roles:
+                    authz_groups_role_dict[(u,r)]=True
+                else:
+                    authz_groups_role_dict[(u,r)]=False
+
+        # pass these variables to the template for rendering
+        c.roles = possible_roles
+
+        c.users = users
+        c.user_role_dict = user_role_dict
+
+        c.authz_groups = authz_groups
+        c.authz_groups_role_dict = authz_groups_role_dict
+
         return render('group/authz.html')
-        
+
+
+
+
+
+
+       
     def history(self, id):
         if 'diff' in request.params or 'selected1' in request.params:
             try:


--- a/ckan/controllers/package.py	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/controllers/package.py	Tue Jun 07 23:20:13 2011 +0100
@@ -437,79 +437,221 @@
         if not c.authz_editable:
             abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id))
 
-        if 'save' in request.params: # form posted
-            # A dict needed for the params because request.params is a nested
-            # multidict, which is read only.
-            params = dict(request.params)
-            c.fs = ckan.forms.get_authz_fieldset('package_authz_fs').bind(pkg.roles, data=params or None)
-            try:
-                self._update_authz(c.fs)
-            except ValidationException, error:
-                # TODO: sort this out 
-                # fs = error.args
-                # return render('package/authz.html')
-                raise
-            # now do new roles
-            newrole_user_id = request.params.get('PackageRole--user_id')
-            newrole_authzgroup_id = request.params.get('PackageRole--authorized_group_id')
-            if newrole_user_id != '__null_value__' and newrole_authzgroup_id != '__null_value__':
-                c.message = _(u'Please select either a user or an authorization group, not both.')
-            elif newrole_user_id != '__null_value__':
-                user = model.Session.query(model.User).get(newrole_user_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('PackageRole--role')
-                newpkgrole = model.PackageRole(user=user, package=pkg,
-                        role=role)
-                # With FA no way to get new PackageRole back to set package attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.PackageRole, data=params or None)
-                # new_roles.sync()
-                for item in self.extensions:
-                    item.authz_add_role(newpkgrole)
-                model.repo.commit_and_remove()
-                c.message = _(u'Added role \'%s\' for user \'%s\'') % (
-                    newpkgrole.role,
-                    newpkgrole.user.display_name)
-            elif newrole_authzgroup_id != '__null_value__':
-                authzgroup = model.Session.query(model.AuthorizationGroup).get(newrole_authzgroup_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('PackageRole--role')
-                newpkgrole = model.PackageRole(authorized_group=authzgroup, 
-                        package=pkg, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                for item in self.extensions:
-                    item.authz_add_role(newpkgrole)
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for authorization group \'%s\'') % (
-                    newpkgrole.role,
-                    newpkgrole.authorized_group.name)
-        elif 'role_to_delete' in request.params:
-            pkgrole_id = request.params['role_to_delete']
-            pkgrole = model.Session.query(model.PackageRole).get(pkgrole_id)
-            if pkgrole is None:
-                c.error = _(u'Error: No role found with that id')
+        # Three different ways of getting the list of userobjectroles for this package.
+        # They all take a frighteningly long time to retrieve
+        # the data, but I can't tell how they'll scale. On a large dataset it might
+        # be worth working out which is quickest, so I've made a function for
+        # ease of changing the query.
+        def get_userobjectroles():
+            # we already have a pkg variable in scope, but I found while testing
+            # that it occasionally mysteriously loses its value!  Redefine it
+            # here. 
+            pkg = model.Package.get(id)
+
+            # dread's suggestion for 'get all userobjectroles for this package':
+            uors = model.Session.query(model.PackageRole).join('package').filter_by(name=pkg.name).all()
+            # rgrp's version:
+            # uors = model.Session.query(model.PackageRole).filter_by(package=pkg)
+            # get them all and filter in python:
+            # uors = [uor for uor in model.Session.query(model.PackageRole).all() if uor.package==pkg]
+            return uors
+
+        def action_save_form(users_or_authz_groups):
+            # The permissions grid has been saved
+            # which is a grid of checkboxes named user$role
+            rpi = request.params.items()
+
+            # The grid passes us a list of the users/roles that were displayed
+            submitted = [ a for (a,b) in rpi if (b == u'submitted')]
+            # and also those which were checked
+            checked = [ a for (a,b) in rpi if (b == u'on')]
+
+            # from which we can deduce true/false for each user/role combination
+            # that was displayed in the form
+            table_dict={}
+            for a in submitted:
+                table_dict[a]=False
+            for a in checked:
+                table_dict[a]=True
+
+            # now we'll split up the user$role strings to make a dictionary from 
+            # (user,role) to True/False, which tells us what we need to do.
+            new_user_role_dict={}
+            for (ur,val) in table_dict.items():
+                u,r = ur.split('$')
+                new_user_role_dict[(u,r)] = val
+               
+            # we get the current user/role assignments 
+            # and make a dictionary of them
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_users_roles = [( uor.user.name, uor.role) for uor in current_uors if uor.user]
+            elif users_or_authz_groups=='authz_groups':
+                current_users_roles = [( uor.authorized_group.name, uor.role) for uor in current_uors if uor.authorized_group]        
             else:
-                for item in self.extensions:
-                    item.authz_remove_role(pkgrole)
-                if pkgrole.user:
-                    c.message = _(u'Deleted role \'%s\' for user \'%s\'') % \
-                                (pkgrole.role, pkgrole.user.display_name)
-                elif pkgrole.authorized_group:
-                    c.message = _(u'Deleted role \'%s\' for authorization group \'%s\'') % \
-                                (pkgrole.role, pkgrole.authorized_group.name)
-                pkgrole.purge()
-                model.repo.commit_and_remove()
+                assert False, "shouldn't be here"
 
-        # retrieve pkg again ...
-        c.pkg = model.Package.get(id)
-        fs = ckan.forms.get_authz_fieldset('package_authz_fs').bind(c.pkg.roles)
-        c.form = fs.render()
-        c.new_roles_form = \
-            ckan.forms.get_authz_fieldset('new_package_roles_fs').render()
+            current_user_role_dict={}
+            for (u,r) in current_users_roles:
+                current_user_role_dict[(u,r)]=True
+
+            # and now we can loop through our dictionary of desired states
+            # checking whether a change needs to be made, and if so making it
+
+            # Here we check whether someone is already assigned a role, in order
+            # to avoid assigning it twice, or attempting to delete it when it
+            # doesn't exist. Otherwise problems can occur.
+            if users_or_authz_groups=='users':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_user_to_role(model.User.by_name(u),r,pkg)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_user_from_role(model.User.by_name(u),r,pkg)
+            elif users_or_authz_groups=='authz_groups':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_authorization_group_to_role(model.AuthorizationGroup.by_name(u),r,pkg)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_authorization_group_from_role(model.AuthorizationGroup.by_name(u),r,pkg)
+            else:
+                assert False, "shouldn't be here"
+
+
+            # finally commit the change to the database
+            model.repo.commit_and_remove()
+            h.flash_success("Changes Saved")
+
+
+
+        def action_add_form(users_or_authz_groups):
+            # The user is attempting to set new roles for a named user
+            new_user = request.params.get('new_user_name')
+            # this is the list of roles whose boxes were ticked
+            checked_roles = [ a for (a,b) in request.params.items() if (b == u'on')]
+            # this is the list of all the roles that were in the submitted form
+            submitted_roles = [ a for (a,b) in request.params.items() if (b == u'submitted')]
+
+            # from this we can make a dictionary of the desired states
+            # i.e. true for the ticked boxes, false for the unticked
+            desired_roles = {}
+            for r in submitted_roles:
+                desired_roles[r]=False
+            for r in checked_roles:
+                desired_roles[r]=True
+
+            # again, in order to avoid either creating a role twice or deleting one which is
+            # non-existent, we need to get the users' current roles (if any)
+  
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_roles = [uor.role for uor in current_uors if ( uor.user and uor.user.name == new_user )]
+                user_object = model.User.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown user:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_user_to_role(user_object, r, pkg)
+                        else:
+                            if (r in current_roles):
+                                model.remove_user_from_role(user_object, r, pkg)
+                    h.flash_success("User Added")
+
+            elif users_or_authz_groups=='authz_groups':
+                current_roles = [uor.role for uor in current_uors if ( uor.authorized_group and uor.authorized_group.name == new_user )]
+                user_object = model.AuthorizationGroup.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown authorization group:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_authorization_group_to_role(user_object, r, pkg)
+                        else:
+                            if (r in current_roles):
+                                model.remove_authorization_group_from_role(user_object, r, pkg)
+                    h.flash_success("Authorization Group Added")
+
+            else:
+                assert False, "shouldn't be here"
+
+            # and finally commit all these changes to the database
+            model.repo.commit_and_remove()
+
+
+        # In the event of a post request, work out which of the four possible actions
+        # is to be done, and do it before displaying the page
+        if 'add' in request.POST:
+            action_add_form('users')
+
+        if 'authz_add' in request.POST:
+            action_add_form('authz_groups')
+
+        if 'save' in request.POST:
+            action_save_form('users')
+
+        if 'authz_save' in request.POST:
+            action_save_form('authz_groups')
+
+        # =================
+        # Display the page
+
+        # Find out all the possible roles. At the moment, any role can be
+        # associated with any object, so that's easy:
+        possible_roles = model.Role.get_all()
+
+        # get the list of users who have roles on this object, with their roles
+        uors = get_userobjectroles()
+
+        # uniquify and sort
+        users = sorted(list(set([uor.user.name for uor in uors if uor.user])))
+        authz_groups = sorted(list(set([uor.authorized_group.name for uor in uors if uor.authorized_group])))
+
+        # make a dictionary from (user, role) to True, False
+        users_roles = [( uor.user.name, uor.role) for uor in uors if uor.user]
+        user_role_dict={}
+        for u in users:
+            for r in possible_roles:
+                if (u,r) in users_roles:
+                    user_role_dict[(u,r)]=True
+                else:
+                    user_role_dict[(u,r)]=False
+
+        # and similarly make a dictionary from (authz_group, role) to True, False
+        authz_groups_roles = [( uor.authorized_group.name, uor.role) for uor in uors if uor.authorized_group]
+        authz_groups_role_dict={}
+        for u in authz_groups:
+            for r in possible_roles:
+                if (u,r) in authz_groups_roles:
+                    authz_groups_role_dict[(u,r)]=True
+                else:
+                    authz_groups_role_dict[(u,r)]=False
+
+        # pass these variables to the template for rendering
+        c.roles = possible_roles
+
+        c.users = users
+        c.user_role_dict = user_role_dict
+
+        c.authz_groups = authz_groups
+        c.authz_groups_role_dict = authz_groups_role_dict
+
         return render('package/authz.html')
 
+
+
+
     def rate(self, id):
         package_name = id
         package = model.Package.get(package_name)


--- a/ckan/model/modification.py	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/model/modification.py	Tue Jun 07 23:20:13 2011 +0100
@@ -49,10 +49,14 @@
         for obj in new | changed | deleted:
             if not isinstance(obj, Package):
                 try:
-                    changed_pkgs.update(obj.related_packages())
+                    related_packages = obj.related_packages()
                 except AttributeError:
                     continue
-
+                # this is needed to sort out vdm bug where pkg.as_dict does not
+                # work when the package is deleted.
+                for package in related_packages:
+                    if package not in deleted | new:
+                        changed_pkgs.add(package)
         for obj in changed_pkgs:
             self.notify(obj, DomainObjectOperation.changed)
 


--- a/ckan/templates/authorization_group/authz.html	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/templates/authorization_group/authz.html	Tue Jun 07 23:20:13 2011 +0100
@@ -9,21 +9,41 @@
       Authorization for authorization group: ${c.authorization_group_name}
     </h2>
 
-    <p py:if="c.message">${c.message}</p>
 
-    <form id="group-authz" action="" method="post">
-      <h3>Update Existing Roles</h3>
-      <table>
-      ${h.literal(c.form)}
-      </table>
+    <h2>Update Existing Roles</h2>
 
-      <h3>Create New User or Authorization Group Roles</h3>
-      ${h.literal(c.new_roles_form)}
-      
-      <br/>
+    <form id="theform" method="POST">
+      ${authz_form_table('theform', c.roles, c.users, c.user_role_dict)}
+      <button type="submit" name="save">
+        Save
+      </button>
+    </form>
 
-      ${h.submit('save', _('Save'))}
+    <h2>Add Roles for Any User</h2>
+
+    <form id="addform" method="POST">
+      ${authz_add_table(c.roles)}
+      <button type="submit" name="add"> Add </button></form>
+
+    <hr/>
+
+    <h2>Existing Roles for Authorization Groups</h2>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save">
+        Save
+      </button>
+    </form>
+
+    <h2>Add Roles for Any Authorization Group</h2>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add"> Add </button>
+    </form>
+
   </div><xi:include href="layout.html" />


--- a/ckan/templates/group/authz.html	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/templates/group/authz.html	Tue Jun 07 23:20:13 2011 +0100
@@ -9,21 +9,40 @@
       Authorization for group: ${c.grouptitle or c.groupname}
     </h2>
 
-    <p py:if="c.message">${c.message}</p>
+    <h2>Update Existing Roles</h2>
 
-    <form id="group-authz" action="" method="post">
-      <h3>Update Existing Roles</h3>
-      <table>
-      ${h.literal(c.form)}
-      </table>
+    <form id="theform" method="POST">
+      ${authz_form_table('theform', c.roles, c.users, c.user_role_dict)}
+      <button type="submit" name="save">
+        Save
+      </button>
+    </form>
 
-      <h3>Create New User Roles</h3>
-      ${h.literal(c.new_roles_form)}
-        
-      <br/>
+    <h2>Add Roles for Any User</h2>
 
-      ${h.submit('save', _('Save'))}
+    <form id="addform" method="POST">
+      ${authz_add_table(c.roles)}
+      <button type="submit" name="add"> Add </button></form>
+
+    <hr/>
+
+    <h2>Existing Roles for Authorization Groups</h2>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save">
+        Save
+      </button>
+    </form>
+
+    <h2>Add Roles for Any Authorization Group</h2>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add"> Add </button>
+    </form>
+
   </div><xi:include href="layout.html" />


--- a/ckan/templates/package/authz.html	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/templates/package/authz.html	Tue Jun 07 23:20:13 2011 +0100
@@ -9,21 +9,40 @@
       Authorization for Data Package: ${c.pkgname}
     </h2>
 
-    <p py:if="c.message">${c.message}</p>
+    <h2>Update Existing Roles</h2>
 
-    <form id="package-authz" action="" method="post">
-      <h3>Update Existing Roles</h3>
-      <table>
-      ${h.literal(c.form)}
-      </table>
+    <form id="theform" method="POST">
+      ${authz_form_table('theform', c.roles, c.users, c.user_role_dict)}
+      <button type="submit" name="save">
+        Save
+      </button>
+    </form>
 
-      <h3>Create New User Roles</h3>
-      ${h.literal(c.new_roles_form)}
-        
-      <br/>
+    <h2>Add Roles for Any User</h2>
 
-      ${h.submit('save', _('Save'))}
+    <form id="addform" method="POST">
+      ${authz_add_table(c.roles)}
+      <button type="submit" name="add"> Add </button></form>
+
+    <hr/>
+
+    <h2>Existing Roles for Authorization Groups</h2>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save">
+        Save
+      </button>
+    </form>
+
+    <h2>Add Roles for Any Authorization Group</h2>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add"> Add </button>
+    </form>
+
   </div><xi:include href="layout.html" />


--- a/ckan/tests/functional/test_authorization_group.py	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/tests/functional/test_authorization_group.py	Tue Jun 07 23:20:13 2011 +0100
@@ -143,251 +143,255 @@
         model.repo.rebuild_db()
         model.Session.remove()
 
-    def test_authzgroups_walkthrough(self):
-        # very long test sequence repeating the series of things I did to
-        # convince myself that the authzgroups system worked as expected,
-        # starting off with the default test data
+
+    ## THIS WALKTHROUGH IS NOW COMPLETELY BROKEN BY THE CHANGES I MADE TO THE AUTHZ PAGE
+
+
+    # def test_authzgroups_walkthrough(self):
+    #     # very long test sequence repeating the series of things I did to
+    #     # convince myself that the authzgroups system worked as expected,
+    #     # starting off with the default test data
         
-        # The first thing to notice is that the authzgroup page:
-        auth_group_index_url = url_for(controller='/authorization_group', action='index')
-        # displays differently for different users.
+    #     # The first thing to notice is that the authzgroup page:
+    #     auth_group_index_url = url_for(controller='/authorization_group', action='index')
+    #     # displays differently for different users.
 
-        def get_page(url, expect_status, username, assert_text=None, error_text=None):
-            res= self.app.get(url, 
-                              status=expect_status, 
-                              extra_environ={'REMOTE_USER': username})
-            if assert_text and assert_text not in res:
-                errorstring = error_text + ' ( "' + assert_text + \
-                              '" not found in result of getting "' + \
-                              url + '" as user "' + username + '" )'
-                assert False, errorstring
-            return res
+    #     def get_page(url, expect_status, username, assert_text=None, error_text=None):
+    #         res= self.app.get(url, 
+    #                           status=expect_status, 
+    #                           extra_environ={'REMOTE_USER': username})
+    #         if assert_text and assert_text not in res:
+    #             errorstring = error_text + ' ( "' + assert_text + \
+    #                           '" not found in result of getting "' + \
+    #                           url + '" as user "' + username + '" )'
+    #             assert False, errorstring
+    #         return res
 
-        # testsysadmin sees the true picture, where the test data contains two groups
-        get_page(auth_group_index_url, 200, 'testsysadmin',
-                'There are <strong>2</strong> authorization groups',
-                'Should be accurate for testsysadmin')
+    #     # testsysadmin sees the true picture, where the test data contains two groups
+    #     get_page(auth_group_index_url, 200, 'testsysadmin',
+    #             'There are <strong>2</strong> authorization groups',
+    #             'Should be accurate for testsysadmin')
 
-        # But if we look at the same page as annafan, who does not have read 
-        # permissions on these groups, we should see neither
-        get_page(auth_group_index_url, 200, 'annafan',
-                'There are <strong>0</strong> authorization groups',
-                'Should lie to annafan about number of groups')
+    #     # But if we look at the same page as annafan, who does not have read 
+    #     # permissions on these groups, we should see neither
+    #     get_page(auth_group_index_url, 200, 'annafan',
+    #             'There are <strong>0</strong> authorization groups',
+    #             'Should lie to annafan about number of groups')
 
-        # There is a page for each group
-        anauthzgroup_url = url_for(controller='/authorization_group', 
-                                   action='read', 
-                                   id='anauthzgroup')
-        # And an edit page
-        anauthzgroup_edit_url = url_for(controller='/authorization_group',
-                                        action='edit', 
-                                        id='anauthzgroup')
+    #     # There is a page for each group
+    #     anauthzgroup_url = url_for(controller='/authorization_group', 
+    #                                action='read', 
+    #                                id='anauthzgroup')
+    #     # And an edit page
+    #     anauthzgroup_edit_url = url_for(controller='/authorization_group',
+    #                                     action='edit', 
+    #                                     id='anauthzgroup')
 
-        # testsysadmin should be able to see this, and check that there are no members
-        get_page(anauthzgroup_url, 200, 'testsysadmin',
-                 'There are 0 users in this',
-                 'should be no users in anauthzgroup')
+    #     # testsysadmin should be able to see this, and check that there are no members
+    #     get_page(anauthzgroup_url, 200, 'testsysadmin',
+    #              'There are 0 users in this',
+    #              'should be no users in anauthzgroup')
 
-        # now testsysadmin adds annafan to anauthzgroup via the edit page
-        res = get_page(anauthzgroup_edit_url, 200, 'testsysadmin')
-        group_edit_form = res.forms['group-edit']
-        group_edit_form['AuthorizationGroupUser--user_name'] = u'annafan'
-        submit_res = group_edit_form.submit('save',
-                                      extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     # now testsysadmin adds annafan to anauthzgroup via the edit page
+    #     res = get_page(anauthzgroup_edit_url, 200, 'testsysadmin')
+    #     group_edit_form = res.forms['group-edit']
+    #     group_edit_form['AuthorizationGroupUser--user_name'] = u'annafan'
+    #     submit_res = group_edit_form.submit('save',
+    #                                   extra_environ={'REMOTE_USER': 'testsysadmin'})
 
-        # adding a user to a group should both make her a member, and give her
-        # read permission on the group. We'll check those things have actually
-        # happened by looking directly in the model.
-        anauthzgroup = model.AuthorizationGroup.by_name('anauthzgroup')
-        anauthzgroup_users = [x.name for x in anauthzgroup.users]
-        anauthzgroup_user_roles = [(x.user.name, x.role) for x in anauthzgroup.roles if x.user]
-        assert anauthzgroup_users == [u'annafan'], \
-                                         'anauthzgroup should contain annafan (only)'
-        assert anauthzgroup_user_roles == [(u'annafan', u'reader')],\
-                                         'annafan should be a reader'
+    #     # adding a user to a group should both make her a member, and give her
+    #     # read permission on the group. We'll check those things have actually
+    #     # happened by looking directly in the model.
+    #     anauthzgroup = model.AuthorizationGroup.by_name('anauthzgroup')
+    #     anauthzgroup_users = [x.name for x in anauthzgroup.users]
+    #     anauthzgroup_user_roles = [(x.user.name, x.role) for x in anauthzgroup.roles if x.user]
+    #     assert anauthzgroup_users == [u'annafan'], \
+    #                                      'anauthzgroup should contain annafan (only)'
+    #     assert anauthzgroup_user_roles == [(u'annafan', u'reader')],\
+    #                                      'annafan should be a reader'
 
-        # Since annafan has been added to anauthzgroup, which is an admin on
-        # anotherauthzgroup, she should now be able to see both the groups.
-        get_page(auth_group_index_url, 200, 'annafan',
-                 'There are <strong>2</strong> auth',
-                 "annafan should now be able to see both groups")
+    #     # Since annafan has been added to anauthzgroup, which is an admin on
+    #     # anotherauthzgroup, she should now be able to see both the groups.
+    #     get_page(auth_group_index_url, 200, 'annafan',
+    #              'There are <strong>2</strong> auth',
+    #              "annafan should now be able to see both groups")
 
-        # When annafan looks at the page for anauthzgroup now
-        # She should see that there's one user:
-        get_page(anauthzgroup_url, 200,'annafan',
-                       'There are 1 users in this', 
-                       'annafan should be able to see the list of members')
+    #     # When annafan looks at the page for anauthzgroup now
+    #     # She should see that there's one user:
+    #     get_page(anauthzgroup_url, 200,'annafan',
+    #                    'There are 1 users in this', 
+    #                    'annafan should be able to see the list of members')
 
-        # Which is her, so her name should be in there somewhere:
-        get_page(anauthzgroup_url, 200,'annafan',
-                       'annafan', 
-                       'annafan should be listed as a member')
+    #     # Which is her, so her name should be in there somewhere:
+    #     get_page(anauthzgroup_url, 200,'annafan',
+    #                    'annafan', 
+    #                    'annafan should be listed as a member')
 
-        # But she shouldn't be able to see the edit page for that group.  
+    #     # But she shouldn't be able to see the edit page for that group.  
 
-        # The behaviour of the test setup here is a bit weird, since in the
-        # browser she gets redirected to the login page, but from these tests,
-        # she just gets a 401, with no apparent redirect.  Sources inform me
-        # that this is normal, and to do with repoze being in the application
-        # stack but not in the test stack.
-        get_page(anauthzgroup_edit_url, 401, 'annafan',
-                 'not authorized to edit', 
-                 'annafan should not be able to edit the list of members')
-        # this behaviour also means that we get a flash message left over, which appears on 
-        # whatever the next page is.
+    #     # The behaviour of the test setup here is a bit weird, since in the
+    #     # browser she gets redirected to the login page, but from these tests,
+    #     # she just gets a 401, with no apparent redirect.  Sources inform me
+    #     # that this is normal, and to do with repoze being in the application
+    #     # stack but not in the test stack.
+    #     get_page(anauthzgroup_edit_url, 401, 'annafan',
+    #              'not authorized to edit', 
+    #              'annafan should not be able to edit the list of members')
+    #     # this behaviour also means that we get a flash message left over, which appears on 
+    #     # whatever the next page is.
   
-        # I'm going to assert that behaviour here, just to note it. It's most
-        # definitely not required functionality!  We'll do a dummy fetch of the
-        # main page for anauthzgroup, which will have the errant flash message
-        get_page(anauthzgroup_url, 200, 'annafan',
-                 'not authorized to edit', 
-                 'flash message should carry over to next fetch')
+    #     # I'm going to assert that behaviour here, just to note it. It's most
+    #     # definitely not required functionality!  We'll do a dummy fetch of the
+    #     # main page for anauthzgroup, which will have the errant flash message
+    #     get_page(anauthzgroup_url, 200, 'annafan',
+    #              'not authorized to edit', 
+    #              'flash message should carry over to next fetch')
 
-        # But if we do the dummy fetch twice, the flash message should have gone
-        res = get_page(anauthzgroup_url, 200, 'annafan')
-        assert 'not authorized to edit' not in res, 'flash message should have gone'
+    #     # But if we do the dummy fetch twice, the flash message should have gone
+    #     res = get_page(anauthzgroup_url, 200, 'annafan')
+    #     assert 'not authorized to edit' not in res, 'flash message should have gone'
 
-        # Since annafan is now a member of anauthzgroup, she should have admin privileges
-        # on anotherauthzgroup
-        anotherauthzgroup_edit_url = url_for(controller='/authorization_group', 
-                                             action='edit', 
-                                             id='anotherauthzgroup')
+    #     # Since annafan is now a member of anauthzgroup, she should have admin privileges
+    #     # on anotherauthzgroup
+    #     anotherauthzgroup_edit_url = url_for(controller='/authorization_group', 
+    #                                          action='edit', 
+    #                                          id='anotherauthzgroup')
 
-        # Which means that she can go to the edit page:
-        res = get_page(anotherauthzgroup_edit_url, 200, 'annafan',
-                 'There are no users',
-                 "There shouldn't be any users in anotherauthzgroup")
+    #     # Which means that she can go to the edit page:
+    #     res = get_page(anotherauthzgroup_edit_url, 200, 'annafan',
+    #              'There are no users',
+    #              "There shouldn't be any users in anotherauthzgroup")
 
-        # And change the name of the group
-        # The group name editing box has a name dependent on the id of the group,
-        # so we find it by regex in the page.
-        import re
-        p = re.compile('AuthorizationGroup-.*-name')
-        groupnamebox = [ v for k,v in res.forms['group-edit'].fields.items() if p.match(k)][0][0]
-        groupnamebox.value = 'annasauthzgroup'
-        res = res.forms['group-edit'].submit('save', extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow()
+    #     # And change the name of the group
+    #     # The group name editing box has a name dependent on the id of the group,
+    #     # so we find it by regex in the page.
+    #     import re
+    #     p = re.compile('AuthorizationGroup-.*-name')
+    #     groupnamebox = [ v for k,v in res.forms['group-edit'].fields.items() if p.match(k)][0][0]
+    #     groupnamebox.value = 'annasauthzgroup'
+    #     res = res.forms['group-edit'].submit('save', extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow()
         
-        ## POTENTIAL BUG:
-        # note that she could change the name of the group to anauthzgroup,
-        # which causes problems due to the name collision. This should be
-        # guarded against.
+    #     ## POTENTIAL BUG:
+    #     # note that she could change the name of the group to anauthzgroup,
+    #     # which causes problems due to the name collision. This should be
+    #     # guarded against.
 
 
-        # annafan should still be able to see the admin and edit pages of the
-        # newly renamed group by virtue of being a member of anauthzgroup
-        annasauthzgroup_authz_url = url_for(controller='/authorization_group', 
-                                            action='authz', 
-                                            id='annasauthzgroup')
+    #     # annafan should still be able to see the admin and edit pages of the
+    #     # newly renamed group by virtue of being a member of anauthzgroup
+    #     annasauthzgroup_authz_url = url_for(controller='/authorization_group', 
+    #                                         action='authz', 
+    #                                         id='annasauthzgroup')
 
-        annasauthzgroup_edit_url = url_for(controller='/authorization_group', 
-                                            action='edit', 
-                                            id='annasauthzgroup')
+    #     annasauthzgroup_edit_url = url_for(controller='/authorization_group', 
+    #                                         action='edit', 
+    #                                         id='annasauthzgroup')
 
 
-        res = get_page(annasauthzgroup_authz_url, 200, 'annafan',
-                       'Authorization for authorization group: annasauthzgroup',
-                       'should be authz page')
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'annafan',
+    #                    'Authorization for authorization group: annasauthzgroup',
+    #                    'should be authz page')
 
-        # annafan has the power to remove anauthzgroup's admin role on her group
-        # The button to remove that role is a link, rather than a submit. I
-        # assume there is a better way to do this than searching by regex, but I
-        # can't find it.
-        import re
-        delete_links = re.compile('<a href="(.*)" title="delete">').findall(res.body)
-        assert len(delete_links) == 1, "There should only be one delete link here"
-        delete_link = delete_links[0]
+    #     # annafan has the power to remove anauthzgroup's admin role on her group
+    #     # The button to remove that role is a link, rather than a submit. I
+    #     # assume there is a better way to do this than searching by regex, but I
+    #     # can't find it.
+    #     import re
+    #     delete_links = re.compile('<a href="(.*)" title="delete">').findall(res.body)
+    #     assert len(delete_links) == 1, "There should only be one delete link here"
+    #     delete_link = delete_links[0]
 
-        # Paranoid check, try to follow link without credentials. Should be redirected.
-        res = self.app.get(delete_link, status=302)
-        res = res.follow()
-        assert 'Not authorized to edit authorization for group' in res,\
-                "following link without credentials should result in redirection to login page"
+    #     # Paranoid check, try to follow link without credentials. Should be redirected.
+    #     res = self.app.get(delete_link, status=302)
+    #     res = res.follow()
+    #     assert 'Not authorized to edit authorization for group' in res,\
+    #             "following link without credentials should result in redirection to login page"
 
-        # Now follow it as annafan, which should work.
-        get_page(delete_link, 200,'annafan',
-                 "Deleted role 'admin' for authorization group 'anauthzgroup'",
-                 "Page should mention the deleted role")
+    #     # Now follow it as annafan, which should work.
+    #     get_page(delete_link, 200,'annafan',
+    #              "Deleted role 'admin' for authorization group 'anauthzgroup'",
+    #              "Page should mention the deleted role")
         
-        # Trying it a second time should fail since she's now not an admin.
-        get_page(delete_link, 401,'annafan')
+    #     # Trying it a second time should fail since she's now not an admin.
+    #     get_page(delete_link, 401,'annafan')
  
-        # No one should now have any rights on annasauthzgroup, including
-        # annafan herself.  So this should fail too. Again, get a 401 error
-        # here, but in the browser we get redirected if we try.
-        get_page(annasauthzgroup_authz_url, 401,'annafan')
+    #     # No one should now have any rights on annasauthzgroup, including
+    #     # annafan herself.  So this should fail too. Again, get a 401 error
+    #     # here, but in the browser we get redirected if we try.
+    #     get_page(annasauthzgroup_authz_url, 401,'annafan')
 
-        # testsysadmin can put her back. 
-        # It appears that the select boxes on this form need to be set by id
-        anauthzgroupid = model.AuthorizationGroup.by_name(u'anauthzgroup').id
-        annafanid = model.User.by_name(u'annafan').id
+    #     # testsysadmin can put her back. 
+    #     # It appears that the select boxes on this form need to be set by id
+    #     anauthzgroupid = model.AuthorizationGroup.by_name(u'anauthzgroup').id
+    #     annafanid = model.User.by_name(u'annafan').id
 
-        # first try to make both anauthzgroup and annafan editors. This should fail.
-        res = get_page(annasauthzgroup_authz_url,200, 'testsysadmin')
-        gaf= res.forms['group-authz']
-        gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
-        gaf['AuthorizationGroupRole--role'] = 'editor'
-        gaf['AuthorizationGroupRole--user_id'] = annafanid
-        res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
-        assert 'Please select either a user or an authorization group, not both.' in res,\
-             'request should fail if you change both user and authz group'
+    #     # first try to make both anauthzgroup and annafan editors. This should fail.
+    #     res = get_page(annasauthzgroup_authz_url,200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
+    #     gaf['AuthorizationGroupRole--role'] = 'editor'
+    #     gaf['AuthorizationGroupRole--user_id'] = annafanid
+    #     res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert 'Please select either a user or an authorization group, not both.' in res,\
+    #          'request should fail if you change both user and authz group'
 
-        # settle for just doing one at a time. make anauthzgroup an editor
-        res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
-        gaf= res.forms['group-authz']
-        gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
-        gaf['AuthorizationGroupRole--role'] = 'editor'
-        res = gaf.submit('save',status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
-        assert "Added role 'editor' for authorization group 'anauthzgroup'" in res, \
-                                                            "no flash message"
+    #     # settle for just doing one at a time. make anauthzgroup an editor
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
+    #     gaf['AuthorizationGroupRole--role'] = 'editor'
+    #     res = gaf.submit('save',status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert "Added role 'editor' for authorization group 'anauthzgroup'" in res, \
+    #                                                         "no flash message"
 
-        # and make annafan a reader 
-        res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
-        gaf= res.forms['group-authz']
-        gaf['AuthorizationGroupRole--user_id'] = annafanid
-        gaf['AuthorizationGroupRole--role'] = 'reader'
-        res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
-        assert "Added role 'reader' for user 'annafan'" in res, "no flash message"
+    #     # and make annafan a reader 
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--user_id'] = annafanid
+    #     gaf['AuthorizationGroupRole--role'] = 'reader'
+    #     res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert "Added role 'reader' for user 'annafan'" in res, "no flash message"
 
-        # annafan should now be able to add her friends to annasauthzgroup
-        res = get_page(annasauthzgroup_edit_url, 200, 'annafan')
-        res.forms['group-edit']['AuthorizationGroupUser--user_name']='tester'
-        # this follows the post/redirect/get pattern
-        res = res.forms['group-edit'].submit('save', status=302,
-                                             extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
-        # and she gets redirected to the group view page
-        assert 'tester' in res, 'tester not added?'
+    #     # annafan should now be able to add her friends to annasauthzgroup
+    #     res = get_page(annasauthzgroup_edit_url, 200, 'annafan')
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='tester'
+    #     # this follows the post/redirect/get pattern
+    #     res = res.forms['group-edit'].submit('save', status=302,
+    #                                          extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     # and she gets redirected to the group view page
+    #     assert 'tester' in res, 'tester not added?'
  
-        # she needs to do them one by one
-        res = get_page(annasauthzgroup_edit_url, 200, 'annafan',
-                       'tester', 
-                       'tester not in edit form')
-        res.forms['group-edit']['AuthorizationGroupUser--user_name']='russianfan'        
-        res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     # she needs to do them one by one
+    #     res = get_page(annasauthzgroup_edit_url, 200, 'annafan',
+    #                    'tester', 
+    #                    'tester not in edit form')
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='russianfan'        
+    #     res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
         
-        # and finally adds herself
-        res = self.app.get(annasauthzgroup_edit_url, status=200, extra_environ={'REMOTE_USER': 'annafan'})
-        assert 'russianfan' in res, 'russianfan not added?'
-        res.forms['group-edit']['AuthorizationGroupUser--user_name']='annafan'        
-        res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
-        assert 'annafan' in res, 'annafan not added?'
+    #     # and finally adds herself
+    #     res = self.app.get(annasauthzgroup_edit_url, status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     assert 'russianfan' in res, 'russianfan not added?'
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='annafan'        
+    #     res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     assert 'annafan' in res, 'annafan not added?'
 
-        # finally let's check that annafan can create a completely new authzgroup
-        new_authzgroup_url = url_for(controller='/authorization_group', action='new')
-        res = get_page(new_authzgroup_url, 200,'annafan',
-                       'New Authorization Group', 
-                       "wrong page?")
-        gef = res.forms['group-edit']
-        gef['AuthorizationGroup--name']="newgroup"
-        gef['AuthorizationGroupUser--user_name'] = "russianfan"
-        res = gef.submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
-        #post/redirect/get
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     # finally let's check that annafan can create a completely new authzgroup
+    #     new_authzgroup_url = url_for(controller='/authorization_group', action='new')
+    #     res = get_page(new_authzgroup_url, 200,'annafan',
+    #                    'New Authorization Group', 
+    #                    "wrong page?")
+    #     gef = res.forms['group-edit']
+    #     gef['AuthorizationGroup--name']="newgroup"
+    #     gef['AuthorizationGroupUser--user_name'] = "russianfan"
+    #     res = gef.submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     #post/redirect/get
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
         
-        assert 'newgroup'   in res, "should have redirected to the newgroup page"
-        assert 'russianfan' in res, "no russianfan"
-        assert 'There are 1 users in this authorization group' in res, "missing text"
+    #     assert 'newgroup'   in res, "should have redirected to the newgroup page"
+    #     assert 'russianfan' in res, "no russianfan"
+    #     assert 'There are 1 users in this authorization group' in res, "missing text"
 


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/test_edit_authz.py	Tue Jun 07 23:20:13 2011 +0100
@@ -0,0 +1,420 @@
+import ckan.model as model
+from ckan.tests import *
+from ckan.lib.base import *
+import ckan.authz as authz
+
+
+def check_and_set_checkbox(theform, user, role, should_be, set_to):
+   '''Given an authz form, find the checkbox associated with the strings user and role,
+   assert that it's in the state 'should_be', and set it to 'set_to' '''
+   user_role_string = '%s$%s' % (user, role)
+   checkboxes = [x for x in theform.fields[user_role_string] \
+                                   if x.__class__.__name__ == 'Checkbox']
+
+   assert(len(checkboxes)==1), \
+        "there should only be one checkbox for %s/%s" % (user, role)
+   checkbox = checkboxes[0]
+
+   #checkbox should be unticked
+   assert checkbox.checked==should_be, \
+                 "%s/%s checkbox in unexpected state" % (user, role)
+
+   #tick or untick the box and return the form
+   checkbox.checked=set_to
+   return theform
+
+
+class TestEditAuthz(TestController):
+    @classmethod
+    def setup_class(self):
+        # for the authorization editing tests we set up test data so:
+        # three users, sysadmin , administrator, and another
+        # one authzgroup, one group, one package
+        # and administrator is admin on all three
+        # one extra authzgroup, authzgroup2, with no permissions to start with
+        model.repo.init_db()
+        model.repo.new_revision()
+        
+        self.sysadmin = 'sysadmin'
+        sysadmin_user = model.User(name=unicode(self.sysadmin))
+        self.admin = 'administrator'
+        admin_user = model.User(name=unicode(self.admin))
+        self.another = 'another'
+        another_user = model.User(name=unicode(self.another))
+        self.authzgroup = 'authzgroup'
+        authzgroup = model.AuthorizationGroup(name=unicode(self.authzgroup))
+        self.group = 'group'
+        group = model.Group(name=unicode(self.group))
+        self.authzgroup2 = 'authzgroup2'
+        authzgroup2 = model.AuthorizationGroup(name=unicode(self.authzgroup2))
+
+
+        for obj in sysadmin_user, admin_user, another_user, authzgroup, group, authzgroup2:
+            model.Session.add(obj)
+
+        model.add_user_to_role(sysadmin_user, model.Role.ADMIN, model.System())
+        model.repo.commit_and_remove()
+
+        model.repo.new_revision()
+
+        self.pkg = u'package'
+        pkg = model.Package(name=self.pkg)
+        model.Session.add(pkg)
+
+        admin_user = model.User.by_name(unicode(self.admin))
+        assert admin_user
+
+        # setup all three authorization objects to have logged in and visitor as editors, and the admin as admin
+        model.setup_user_roles(pkg, ['editor'], ['editor'], [admin_user])
+        model.setup_user_roles(authzgroup, ['editor'], ['editor'], [admin_user])
+        model.setup_user_roles(group, ['editor'], ['editor'], [admin_user])
+
+        model.repo.commit_and_remove()
+
+    @classmethod
+    def teardown_class(self):
+        model.repo.rebuild_db()
+
+    def test_access_to_authz(self):
+        #for each of the three authz pages, check that the access permissions work correctly
+        for (c,i) in [('package', self.pkg),('group', self.group),('authorization_group', self.authzgroup)]:
+            offset = url_for(controller=c, action='authz', id=i)
+
+            # attempt to access the authz pages without credentials should result in getting redirected to the login page
+            res = self.app.get(offset, status=[302])
+            res = res.follow()
+            assert res.request.url.startswith('/user/login')
+
+            # for an ordinary user, it should result in access denied
+            # which is weird, because in the app proper he'd get redirected too.
+            # it behaves differently in the test setup, but this is a known strangeness.
+            res = self.app.get(offset, status=[401], extra_environ={'REMOTE_USER':self.another})
+
+            # going there as the package administrator or system administrator should be fine
+            for u in [self.admin,self.sysadmin]:
+                res = self.app.get(offset, status=[200], extra_environ={'REMOTE_USER':u})
+                # the name of the object should appear in the page
+                assert i in res
+                assert "Authorization for" in res
+
+
+    def roles_list(self, authzobj):
+        # get a list of username/roles for a given authorizable object
+        list = [ (r.user.name, r.role) for r in authzobj.roles if r.user]
+        list.extend([(r.authorized_group.name, r.role) for r in authzobj.roles if r.authorized_group])
+        return list
+
+    # get the users/roles for the specific objects created in our test data
+    def package_roles(self):
+        return self.roles_list(model.Package.by_name(self.pkg))
+
+    def group_roles(self):
+        return self.roles_list(model.Group.by_name(self.group))
+
+    def authzgroup_roles(self):
+        return self.roles_list(model.AuthorizationGroup.by_name(self.authzgroup))
+
+    # check that the authz page for each object contains certain key strings
+    def test_2_read_ok(self):
+        for (c,i,m) in [('package', self.pkg, self.package_roles),\
+                        ('group', self.group, self.group_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles)]:
+            offset = url_for(controller=c, action='authz', id=i)
+            res = self.app.get(offset, extra_environ={'REMOTE_USER': self.admin})
+            assert i in res
+            assert "Authorization for" in res
+
+            # all the package's users and roles should appear in tables
+            assert '<tr' in res
+            for (user,role) in m():
+                assert user in res
+                assert role in res
+
+
+    def assert_roles_to_be(self, actual_roles_list, expected_roles_list):
+        # given an actual and an expected list of user/roles, assert that they're as expected, 
+        # modulo ordering.
+        ok = ( len(actual_roles_list) == len(expected_roles_list) )
+        for r in actual_roles_list:
+           if not r in expected_roles_list:
+               ok = False
+        if not ok:
+           print "expected roles: ", expected_roles_list
+           print "actual roles: ", actual_roles_list
+           assert False, "roles not as expected"
+
+
+    # check that when we change one role and add another, that both the checkbox states and the database
+    # change as we expect them to, and that the roles on the other objects don't get changed by accident.
+    # this should guard against certain errors which might be introduced by copy and pasting the controller code.
+    def change_roles(self, user):
+
+        normal_roles=[('administrator', 'admin'),
+                      ('visitor', 'editor'),
+                      ('logged_in', 'editor')]
+
+        changed_roles=[('administrator', 'admin'),
+                       ('visitor', 'editor'),
+                       ('visitor', 'reader'),
+                       ('logged_in', 'admin')]
+
+        # loop variables here are the controller string, the name of the object we're changing, and three functions, 
+        # the first fn gets the roles which we'd like to change, and the other two get the roles which we'd like to stay the same.
+        for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
+
+            # load authz page
+            offset = url_for(controller=c, action='authz', id=i)
+            res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+            assert i in res
+
+            self.assert_roles_to_be(var(), normal_roles)
+            self.assert_roles_to_be(const1(), normal_roles)
+            self.assert_roles_to_be(const2(), normal_roles)
+
+            #admin makes visitor a reader and logged in an admin
+            form = res.forms['theform']
+            check_and_set_checkbox(form, u'visitor', u'reader', False, True)
+            check_and_set_checkbox(form, u'logged_in', u'admin', False, True)
+            check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+            check_and_set_checkbox(form, u'logged_in', u'editor', True, False)
+
+            res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+            # ensure db was changed
+            self.assert_roles_to_be(var(), changed_roles)
+            self.assert_roles_to_be(const1(), normal_roles)
+            self.assert_roles_to_be(const2(), normal_roles)
+
+            # ensure rerender of form is changed
+            offset = url_for(controller=c, action='authz', id=i)
+            res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+            assert i in res
+
+            # check that the checkbox states are what we think they should be
+            # and put things back how they were.
+            form = res.forms['theform']
+            check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+            check_and_set_checkbox(form, u'logged_in', u'admin', True, False)
+            check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+            check_and_set_checkbox(form, u'logged_in', u'editor', False, True)
+            res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+            # ensure db was changed
+            self.assert_roles_to_be(var(), normal_roles)
+            self.assert_roles_to_be(const1(), normal_roles)
+            self.assert_roles_to_be(const2(), normal_roles)
+
+
+    # do the change roles both as package/group/authzgroup admin, and also as sysadmin.
+    def test_3_admin_changes_role(self):
+        self.change_roles(self.admin)
+
+    def test_3_sysadmin_changes_role(self):
+        self.change_roles(self.sysadmin)
+
+    def delete_role_as(self,user):
+
+        normal_roles=[('administrator', 'admin'),
+                      ('visitor', 'editor'),
+                      ('logged_in', 'editor')]
+
+        changed_roles=[('administrator', 'admin'),
+                       ('logged_in', 'editor')]
+
+        changed_roles2=[('administrator', 'admin'),
+                        ('visitor', 'reader'),
+                        ('logged_in', 'editor')]
+
+
+        # loop variables here are the controller string, the name of the object we're changing, and three functions, 
+        # the first fn gets the roles which we'd like to change, and the other two get the roles which we'd like to stay the same.
+        for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
+
+           # get the authz page, check that visitor's in there
+           # remove visitor's role on the package
+           # re-get the page and make sure that visitor's not in there at all
+           offset = url_for(controller=c, action='authz', id=i)
+           res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+           assert self.pkg in res
+
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           #admin removes visitor's only role
+           form = res.forms['theform']
+           check_and_set_checkbox(form, u'visitor', u'editor', True, False)
+           res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+           # ensure db was changed
+           self.assert_roles_to_be(var(), changed_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # ensure rerender of form is changed
+           offset = url_for(controller=c, action='authz', id=i)
+           res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+           assert self.pkg in res
+
+           assert 'visitor' not in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           # check that the checkbox states are what we think they should be
+           form = res.forms['theform']
+           check_and_set_checkbox(form, u'logged_in', u'editor', True, True)
+           check_and_set_checkbox(form, u'administrator', u'admin', True, True)
+
+           # now we should add visitor back in, let's make him a reader
+           form = res.forms['addform']
+           form.fields['new_user_name'][0].value='visitor'
+           checkbox = [x for x in form.fields['reader'] \
+                         if x.__class__.__name__ == 'Checkbox'][0]
+           # check it's currently unticked
+           assert checkbox.checked == False
+           # tick it and submit
+           checkbox.checked=True
+           res = form.submit('add', extra_environ={'REMOTE_USER':user})
+           assert "User Added" in res, "don't see flash message"
+
+           # check that the page contains strings for everyone
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           # check that the roles in the db are back to normal
+           self.assert_roles_to_be(var(), changed_roles2)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # now change him back to being an editor
+           form = res.forms['theform']
+           check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+           check_and_set_checkbox(form, u'visitor', u'editor', False, True)
+           res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+           # check that the page contains strings for everyone
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           # check that the roles in the db are back to normal
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+
+
+    def test_4_admin_deletes_role(self):
+        self.delete_role_as(self.admin)
+
+    def test_4_sysadmin_deletes_role(self):
+        self.delete_role_as(self.sysadmin)
+
+
+    # now a version of the above tests dealing with permissions assigned to authzgroups 
+    # (as opposed to on authzgroups)
+    def add_change_delete_authzgroup_as(self, user):
+
+        normal_roles=[('administrator', 'admin'),
+                      ('visitor', 'editor'),
+                      ('logged_in', 'editor')]
+
+        changed_roles=[('authzgroup2', 'admin'),
+                       ('administrator', 'admin'),
+                       ('visitor', 'editor'),
+                       ('logged_in', 'editor')]
+
+        changed_roles_2=[('authzgroup2', 'editor'),
+                         ('administrator', 'admin'),
+                         ('visitor', 'editor'),
+                         ('logged_in', 'editor')]
+
+        for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
+
+           # get the authz page, check that it contains the object name
+           offset = url_for(controller=c, action='authz', id=i)
+           res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+           assert i in res
+
+           # check the state of the database
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # and that corresponding user strings are in the authz page
+           # particularly that authzgroup2 isn't there (yet)
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+           assert 'authzgroup2' not in res
+ 
+           # add authzgroup2 as an admin
+           form = res.forms['authzgroup_addform']
+           form.fields['new_user_name'][0].value='authzgroup2'
+           checkbox = [x for x in form.fields['admin'] \
+                         if x.__class__.__name__ == 'Checkbox'][0]
+           # check the checkbox is currently unticked
+           assert checkbox.checked == False
+           # tick it and submit
+           checkbox.checked=True
+           res = form.submit('authz_add', extra_environ={'REMOTE_USER':user})
+           assert "Authorization Group Added" in res, "don't see flash message"
+
+           # examine the new page for user names/authzgroup names
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+           assert 'authzgroup2' in res
+
+           # and ensure that the database has changed as expected
+           self.assert_roles_to_be(var(), changed_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+ 
+           # check that the checkbox states are what we think they should be
+           # and change authzgroup2 from admin to editor
+           form = res.forms['authzgroup_form']
+           check_and_set_checkbox(form, u'authzgroup2', u'editor', False, True)
+           check_and_set_checkbox(form, u'authzgroup2', u'admin', True, False)
+           res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+           #check database has changed.
+           self.assert_roles_to_be(var(), changed_roles_2)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+
+           # now remove authzgroup2 entirely
+           form = res.forms['authzgroup_form']
+           check_and_set_checkbox(form, u'authzgroup2', u'editor', True, False)
+           check_and_set_checkbox(form, u'authzgroup2', u'admin', False, False)
+           res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+           #check database is back to normal
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # and that page contains only the expected strings
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+           assert 'authzgroup2' not in res
+
+
+    def test_5_admin_changes_adds_deletes_authzgroup(self):
+        self.add_change_delete_authzgroup_as(self.admin)
+
+    def test_5_sysadmin_changes_adds_deletes_authzgroup(self):
+        self.add_change_delete_authzgroup_as(self.sysadmin)


--- a/ckan/tests/functional/test_group_edit_authz.py	Tue Jun 07 23:19:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-import ckan.model as model
-from ckan.tests import *
-from ckan.lib.base import *
-import ckan.authz as authz
-
-class TestGroupEditAuthz(TestController):
-    @classmethod
-    def setup_class(self):
-        model.repo.init_db()
-        model.repo.new_revision()
-        self.admin = 'madeup-administrator'
-        user = model.User(name=unicode(self.admin))
-        model.Session.add(user)
-        self.another = u'madeup-another'
-        model.Session.add(model.User(name=unicode(self.another)))
-        self.groupname = u'test6'
-        group = model.Group(name=self.groupname)
-        model.setup_default_user_roles(group, admins=[user])
-        model.repo.commit_and_remove()
-
-    @classmethod
-    def teardown_class(self):
-        model.repo.rebuild_db()
-
-    def test_0_nonadmin_cannot_edit_authz(self):
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, status=[302, 401])
-        res = res.follow()
-        assert res.request.url.startswith('/user/login')
-        # Alternative if we allowed read-only access
-        # res = self.app.get(offset)
-        # assert not '<form' in res, res
-    
-    def test_1_admin_has_access(self):
-        offset_authz = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset_authz, extra_environ={'REMOTE_USER':
-            self.admin}, status=200)
-
-        # check link is there too
-        offset_read = url_for(controller='group', action='read', id=self.groupname)
-        res = self.app.get(offset_read, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert offset_authz in res
-        
-
-    def test_2_read_ok(self):
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert self.groupname in res
-        assert '<tr' in res
-        assert self.admin in res
-        assert 'Role' in res
-        for uname in [ model.PSEUDO_USER__VISITOR, self.admin ]:
-            assert '%s' % uname in res
-        # crude but roughly correct
-        group = model.Group.by_name(self.groupname)
-        for r in group.roles:
-            assert '<select id="GroupRole-%s-role' % r.id in res
-
-        # now test delete links
-        pr = group.roles[0]
-        href = '%s' % pr.id
-        assert href in res, res
-
-    def _prs(self, groupname):
-        group = model.Group.by_name(groupname)
-        return dict([ (getattr(r.user, 'name', 'USER NAME IS NONE'), r) for r in group.roles ])
-
-    def test_3_admin_changes_role(self):
-        # create a role to be deleted
-        group = model.Group.by_name(self.groupname)
-        model.add_user_to_role(model.User.by_name(u'visitor'), model.Role.READER, group)
-        model.repo.commit_and_remove()
-
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert self.groupname in res
-
-        group = model.Group.by_name(self.groupname)
-        assert len(group.roles) == 3, [(grouprole.user.name, grouprole.role) for grouprole in group.roles]
-
-        def _r(r):
-            return 'GroupRole-%s-role' % r.id
-        def _u(r):
-            return 'GroupRole-%s-user_id' % r.id
-
-        prs = self._prs(self.groupname)
-        assert prs.has_key('visitor')
-        assert prs.has_key('logged_in')
-        assert prs.has_key(self.admin), prs
-        form = res.forms['group-authz']
-        
-        # change role assignments
-        form.select(_r(prs['visitor']), model.Role.EDITOR)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-
-        model.Session.remove()
-        prs = self._prs(self.groupname)
-        assert len(prs) == 3, prs
-        assert prs['visitor'].role == model.Role.EDITOR
-    
-    def test_4_admin_deletes_role(self):
-        group = model.Group.by_name(self.groupname)
-        
-        # create a role to be deleted
-        model.add_user_to_role(model.User.by_name(u'logged_in'), model.Role.READER, group)
-        model.repo.commit_and_remove()
-        
-        group = model.Group.by_name(self.groupname)
-        num_roles_start = len(group.roles)
-
-        # make sure not admin
-        pr_id = [ r for r in group.roles if r.user.name != self.admin ][0].id
-        offset = url_for(controller='group', action='authz', id=self.groupname,
-                role_to_delete=pr_id)
-        # need this here as o/w conflicts over session binding
-        model.Session.remove()
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert 'Deleted role' in res, res
-        assert 'error' not in res, res
-        group = model.Group.by_name(self.groupname)
-        assert len(group.roles) == num_roles_start - 1
-        assert model.Session.query(model.GroupRole).filter_by(id=pr_id).count() == 0
-
-    def test_5_admin_adds_role(self):
-        model.repo.commit_and_remove()
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert self.groupname in res
-        prs = self._prs(self.groupname) 
-        startlen = len(prs)
-        # could be 2 or 3 depending on whether we ran this test alone or not
-        # assert len(prs) == 2, prs
-
-        assert 'Create New User Roles' in res
-        assert '<select id="GroupRole--user_id"' in res, res
-        assert '<td>madeup-administrator</td>' not in res, res
-        form = res.forms['group-authz']
-        another = model.User.by_name(self.another)
-        form.select('GroupRole--user_id', another.id)
-        form.select('GroupRole--role', model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-        model.Session.remove()
-
-        prs = self._prs(self.groupname)
-        assert len(prs) == startlen+1, prs
-        assert prs[self.another].role == model.Role.ADMIN
-


--- a/ckan/tests/functional/test_package_edit_authz.py	Tue Jun 07 23:19:48 2011 +0100
+++ b/ckan/tests/functional/test_package_edit_authz.py	Tue Jun 07 23:20:13 2011 +0100
@@ -3,9 +3,34 @@
 from ckan.lib.base import *
 import ckan.authz as authz
 
+
+def check_and_set_checkbox(theform, user, role, should_be, set_to):
+   '''Given an authz form, find the checkbox associated with the strings user and role,
+   assert that it's in the state 'should_be', and set it to 'set_to' '''
+   user_role_string = '%s$%s' % (user, role)
+   checkboxes = [x for x in theform.fields[user_role_string] \
+                                   if x.__class__.__name__ == 'Checkbox']
+
+   assert(len(checkboxes)==1), \
+        "there should only be one checkbox for %s/%s" % (user, role)
+   checkbox = checkboxes[0]
+
+   #checkbox should be unticked
+   assert checkbox.checked==should_be, \
+                 "%s/%s checkbox in unexpected state" % (user, role)
+
+   #tick or untick the box and return the form
+   checkbox.checked=set_to
+   return theform
+
+
 class TestPackageEditAuthz(TestController):
     @classmethod
     def setup_class(self):
+        # for the authorization editing tests we set up test data so:
+        # three users, madeup-sysadmin , madeup-administrator, and madeup-another
+        # one authzgroup
+        # two packages test6 and test6a, m-a is admin on both
         model.repo.init_db()
         model.repo.new_revision()
         
@@ -15,7 +40,9 @@
         admin_user = model.User(name=unicode(self.admin))
         self.another = u'madeup-another'
         another_user = model.User(name=unicode(self.another))
-        for obj in sysadmin_user, admin_user, another_user:
+        self.authzgroup = u'madeup-authzgroup'
+        authzgroup = model.AuthorizationGroup(name=unicode(self.authzgroup))
+        for obj in sysadmin_user, admin_user, another_user, authzgroup:
             model.Session.add(obj)
 
         model.add_user_to_role(sysadmin_user, model.Role.ADMIN, model.System())
@@ -43,10 +70,7 @@
         res = self.app.get(offset, status=[302, 401])
         res = res.follow()
         assert res.request.url.startswith('/user/login')
-        # Alternative if we allowed read-only access
-        # res = self.app.get(offset)
-        # assert not '<form' in res, res
-    
+     
     def test_1_admin_has_access(self):
         offset = url_for(controller='package', action='authz', id=self.pkgname)
         res = self.app.get(offset, extra_environ={'REMOTE_USER':
@@ -62,182 +86,247 @@
         res = self.app.get(offset, extra_environ={'REMOTE_USER':
             self.admin})
         assert self.pkgname in res
+
+        # all the package's users and roles should appear in tables
         assert '<tr' in res
-        assert self.admin in res
-        assert 'Role' in res
-        for uname in [ model.PSEUDO_USER__VISITOR, self.admin ]:
-            assert '%s' % uname in res
-        # crude but roughly correct
+        for (user,role) in self.package_roles():
+            assert user in res
+            assert role in res
+
+
+    def package_roles(self):
         pkg = model.Package.by_name(self.pkgname)
-        for r in pkg.roles:
-            assert '<select id="PackageRole-%s-role' % r.id in res
+        list = [ (r.user.name, r.role) for r in pkg.roles if r.user]
+        list.extend([(r.authorized_group.name, r.role) for r in pkg.roles if r.authorized_group])
+        return list
 
-        # now test delete links
-        pr = pkg.roles[0]
-        href = '%s' % pr.id
-        assert href in res, res
+    def assert_package_roles_to_be(self, roles_list):
+        prs=self.package_roles()
+        ok = ( len(prs) == len(roles_list) )
+        for r in roles_list:
+           if not r in prs:
+               ok = False
+        if not ok:
+           print "expected roles: ", roles_list
+           print "actual roles: ", prs
+           assert False, "roles not as expected"
 
-    def _prs(self, pkgname):
-        pkg = model.Package.by_name(pkgname)
-        return dict([ (getattr(r.user, 'name', 'USER NAME IS NONE'), r) for r in pkg.roles ])
-
-    def test_3_admin_changes_role(self):
+    def change_roles(self, user):
         # load authz page
         offset = url_for(controller='package', action='authz', id=self.pkgname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
         assert self.pkgname in res
 
-        def _r(r):
-            return 'PackageRole-%s-role' % r.id
-        def _u(r):
-            return 'PackageRole-%s-user_id' % r.id
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
 
-        prs = self._prs(self.pkgname)
-        assert prs['visitor'].role == model.Role.EDITOR
-        assert prs['logged_in'].role == model.Role.EDITOR
-        form = res.forms['package-authz']
-        
-        # change role assignments
-        form.select(_r(prs['visitor']), model.Role.READER)
-        form.select(_r(prs['logged_in']), model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-        model.repo.commit_and_remove()
+        #admin makes visitor a reader and logged in an admin
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'reader', False, True)
+        check_and_set_checkbox(form, u'logged_in', u'admin', False, True)
+        check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+        check_and_set_checkbox(form, u'logged_in', u'editor', True, False)
+
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
 
         # ensure db was changed
-        prs = self._prs(self.pkgname)
-        assert len(prs) == 3, prs
-        assert prs['visitor'].role == model.Role.READER
-        assert prs['logged_in'].role == model.Role.ADMIN
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('visitor', 'reader'),
+           ('logged_in', 'admin')])
 
         # ensure rerender of form is changed
         offset = url_for(controller='package', action='authz', id=self.pkgname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
         assert self.pkgname in res
-        fv = res.forms['package-authz']
-        visitor_options = fv[_r(prs['visitor'])].options
-        assert ('reader', True) in visitor_options, visitor_options
-        logged_in_options = fv[_r(prs['logged_in'])].options
-        assert ('admin', True) in logged_in_options, logged_in_options
+
+        # check that the checkbox states are what we think they should be
+        # and put things back how they were.
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+        check_and_set_checkbox(form, u'logged_in', u'admin', True, False)
+        check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+        check_and_set_checkbox(form, u'logged_in', u'editor', False, True)
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+        # ensure db was changed
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+
+    def test_3_admin_changes_role(self):
+        self.change_roles(self.admin)
 
     def test_3_sysadmin_changes_role(self):
-        # load authz page
-        offset = url_for(controller='package', action='authz', id=self.pkgname2)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert self.pkgname2 in res
+        self.change_roles(self.sysadmin)
 
-        def _r(r):
-            return 'PackageRole-%s-role' % r.id
-        def _u(r):
-            return 'PackageRole-%s-user_id' % r.id
+    def delete_role_as(self,user):
+        # get the authz page, check that visitor's in there
+        # remove visitor's role on the package
+        # re-get the page and make sure that visitor's not in there at all
+        offset = url_for(controller='package', action='authz', id=self.pkgname)
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+        assert self.pkgname in res
 
-        prs = self._prs(self.pkgname2)
-        assert prs['visitor'].role == model.Role.EDITOR
-        assert prs['logged_in'].role == model.Role.EDITOR
-        form = res.forms['package-authz']
-        
-        # change role assignments
-        form.select(_r(prs['visitor']), model.Role.READER)
-        form.select(_r(prs['logged_in']), model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.sysadmin})
-        model.repo.commit_and_remove()
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        #admin removes visitor's only role
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'editor', True, False)
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
 
         # ensure db was changed
-        prs = self._prs(self.pkgname2)
-        assert len(prs) == 3, prs
-        assert prs['visitor'].role == model.Role.READER
-        assert prs['logged_in'].role == model.Role.ADMIN
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('logged_in', 'editor')])
 
         # ensure rerender of form is changed
-        offset = url_for(controller='package', action='authz', id=self.pkgname2)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert self.pkgname2 in res
-        fv = res.forms['package-authz']
-        visitor_options = fv[_r(prs['visitor'])].options
-        assert ('reader', True) in visitor_options, visitor_options
-        logged_in_options = fv[_r(prs['logged_in'])].options
-        assert ('admin', True) in logged_in_options, logged_in_options
-    
+        offset = url_for(controller='package', action='authz', id=self.pkgname)
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+        assert self.pkgname in res
+
+        assert 'visitor' not in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        # check that the checkbox states are what we think they should be
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'logged_in', u'editor', True, True)
+        check_and_set_checkbox(form, u'madeup-administrator', u'admin', True, True)
+
+        # now we should add visitor back in, let's make him a reader
+        form = res.forms['addform']
+        form.fields['new_user_name'][0].value='visitor'
+        checkbox = [x for x in form.fields['reader'] \
+                      if x.__class__.__name__ == 'Checkbox'][0]
+        # check it's currently unticked
+        assert checkbox.checked == False
+        # tick it and submit
+        checkbox.checked=True
+        res = form.submit('add', extra_environ={'REMOTE_USER':user})
+        assert "User Added" in res, "don't see flash message"
+
+       # check that the page contains strings for everyone
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        # check that the roles in the db are back to normal
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'reader'),
+           ('logged_in', 'editor')])
+
+        # now change him back to being an editor
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+        check_and_set_checkbox(form, u'visitor', u'editor', False, True)
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
+ 
+        # check that the page contains strings for everyone
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        # check that the roles in the db are back to normal
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+
     def test_4_admin_deletes_role(self):
-        pkg = model.Package.by_name(self.pkgname)
-        assert len(pkg.roles) == 3
-        # make sure not admin
-        pr_id = [ r for r in pkg.roles if r.user.name != self.admin ][0].id
-        offset = url_for(controller='package', action='authz', id=self.pkgname,
-                role_to_delete=pr_id)
-        # need this here as o/w conflicts over session binding
-        model.Session.remove()
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert 'Deleted role' in res, res
-        assert 'error' not in res, res
-        pkg = model.Package.by_name(self.pkgname)
-        assert len(pkg.roles) == 2
-        assert model.Session.query(model.PackageRole).filter_by(id=pr_id).count() == 0
+        self.delete_role_as(self.admin)
 
     def test_4_sysadmin_deletes_role(self):
-        pkg = model.Package.by_name(self.pkgname2)
-        assert len(pkg.roles) == 3
-        # make sure not admin
-        pr_id = [ r for r in pkg.roles if r.user.name != self.admin ][0].id
-        offset = url_for(controller='package', action='authz', id=self.pkgname2,
-                role_to_delete=pr_id)
-        # need this here as o/w conflicts over session binding
-        model.Session.remove()
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert 'Deleted role' in res, res
-        assert 'error' not in res, res
-        pkg = model.Package.by_name(self.pkgname2)
-        assert len(pkg.roles) == 2
-        assert model.Session.query(model.PackageRole).filter_by(id=pr_id).count() == 0
+        self.delete_role_as(self.sysadmin)
 
-    def test_5_admin_adds_role(self):
+
+    def test_5_add_change_delete_authzgroup(self):
+        user=self.admin
+
+        # get the authz page, check that authzgroup isn't in there
         offset = url_for(controller='package', action='authz', id=self.pkgname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
         assert self.pkgname in res
-        prs = self._prs(self.pkgname) 
-        startlen = len(prs)
-        # could be 2 or 3 depending on whether we ran this test alone or not
-        # assert len(prs) == 2, prs
 
-        assert 'Create New User Roles' in res
-        assert '<select id=' in res, res
-        form = res.forms['package-authz']
-        another = model.User.by_name(self.another)
-        form.select('PackageRole--user_id', another.id)
-        form.select('PackageRole--role', model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-        model.Session.remove()
+        # check the state of the database
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
 
-        prs = self._prs(self.pkgname)
-        assert len(prs) == startlen+1, prs
-        assert prs[self.another].role == model.Role.ADMIN
+        # and that corresponding user strings are in the authz page
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' not in res
 
-    def test_5_sysadmin_adds_role(self):
-        offset = url_for(controller='package', action='authz', id=self.pkgname2)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert self.pkgname2 in res
-        prs = self._prs(self.pkgname2) 
-        startlen = len(prs)
-        # could be 2 or 3 depending on whether we ran this test alone or not
-        # assert len(prs) == 2, prs
+        # add madeup-authzgroup as an admin
+        form = res.forms['authzgroup_addform']
+        form.fields['new_user_name'][0].value='madeup-authzgroup'
+        checkbox = [x for x in form.fields['admin'] \
+                      if x.__class__.__name__ == 'Checkbox'][0]
+        # check the checkbox is currently unticked
+        assert checkbox.checked == False
+        # tick it and submit
+        checkbox.checked=True
+        res = form.submit('authz_add', extra_environ={'REMOTE_USER':user})
+        assert "Authorization Group Added" in res, "don't see flash message"
 
-        assert 'Create New User Roles' in res
-        assert '<select id=' in res, res
-        form = res.forms['package-authz']
-        another = model.User.by_name(self.another)
-        form.select('PackageRole--user_id', another.id)
-        form.select('PackageRole--role', model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.sysadmin})
-        model.Session.remove()
+        # examine the new page for user names/authzgroup names
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' in res
 
-        prs = self._prs(self.pkgname2)
-        assert len(prs) == startlen+1, prs
-        assert prs[self.another].role == model.Role.ADMIN
+        # and ensure that the database has changed as expected
+        self.assert_package_roles_to_be([
+           ('madeup-authzgroup', 'admin'),
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
 
+        # check that the checkbox states are what we think they should be
+        # and change madeup-authzgroup from admin to editor
+        form = res.forms['authzgroup_form']
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'editor', False, True)
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'admin', True, False)
+        res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+        #check database has changed.
+        self.assert_package_roles_to_be([
+           ('madeup-authzgroup', 'editor'),
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # now remove madeup-authzgroup entirely
+        form = res.forms['authzgroup_form']
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'editor', True, False)
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'admin', False, False)
+        res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+        #check database is back to normal
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # and that page contains only the expected strings
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' not in res


http://bitbucket.org/okfn/ckan/changeset/5004c8cba2bd/
changeset:   5004c8cba2bd
branch:      feature-1141-moderated-edits-ajax
user:        kindly
date:        2011-06-08 00:22:22
summary:     [merge] add expired id to revision
affected #:  36 files (83.5 KB)

--- a/ckan/controllers/authorization_group.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/controllers/authorization_group.py	Tue Jun 07 23:22:22 2011 +0100
@@ -142,85 +142,214 @@
             h.redirect_to(action='read', id=c.authorization_group_name)
 
     def authz(self, id):
-        c.authorization_group = model.AuthorizationGroup.by_name(id)
-        if c.authorization_group is None:
+        authorization_group = model.AuthorizationGroup.by_name(id)
+        if authorization_group is None:
             abort(404, _('Group not found'))
-        c.authorization_group_name = c.authorization_group.name
-        
+
+        c.authorization_group_name = authorization_group.name
+
         c.authz_editable = self.authorizer.am_authorized(c, model.Action.EDIT_PERMISSIONS, 
-                                                         c.authorization_group)
+                                                         authorization_group)
+
         if not c.authz_editable:
-            abort(401, _('Not authorized to edit authorization for group'))
+            abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id))
 
-        if 'save' in request.params: # form posted
-            # needed because request is nested
-            # multidict which is read only
-            params = dict(request.params)
-            c.fs = ckan.forms.get_authz_fieldset('authorization_group_authz_fs').bind(
-                                                 c.authorization_group.roles, 
-                                                 data=params or None)
-            try:
-                self._update_authz(c.fs)
-            except ValidationException, error:
-                # TODO: sort this out 
-                # fs = error.args[0]
-                # return render('group/authz.html')
-                raise
-            # now do new roles
-            newrole_user_id = request.params.get('AuthorizationGroupRole--user_id')
-            newrole_authzgroup_id = request.params.get('AuthorizationGroupRole--authorized_group_id')
-            if newrole_user_id != '__null_value__' and newrole_authzgroup_id != '__null_value__':
-                c.message = _(u'Please select either a user or an authorization group, not both.')
-            elif newrole_user_id != '__null_value__':
-                user = model.Session.query(model.User).get(newrole_user_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('AuthorizationGroupRole--role')
-                newauthzgrouprole = model.AuthorizationGroupRole(user=user, 
-                        authorization_group=c.authorization_group, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for user \'%s\'') % (
-                    newauthzgrouprole.role,
-                    newauthzgrouprole.user.name)      
-            elif newrole_authzgroup_id != '__null_value__':
-                authzgroup = model.Session.query(model.AuthorizationGroup).get(newrole_authzgroup_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('AuthorizationGroupRole--role')
-                newauthzgrouprole = model.AuthorizationGroupRole(authorized_group=authzgroup, 
-                        authorization_group=c.authorization_group, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for authorization group \'%s\'') % (
-                    newauthzgrouprole.role,
-                    newauthzgrouprole.authorized_group.name)
-        elif 'role_to_delete' in request.params:
-            authzgrouprole_id = request.params['role_to_delete']
-            authzgrouprole = model.Session.query(model.AuthorizationGroupRole).get(authzgrouprole_id)
-            if authzgrouprole is None:
-                c.error = _(u'Error: No role found with that id')
+        #see package.py for comments
+        def get_userobjectroles():
+            authorization_group = model.AuthorizationGroup.by_name(id)
+            uors = model.Session.query(model.AuthorizationGroupRole).join('authorization_group').filter_by(name=authorization_group.name).all()
+            return uors
+
+        def action_save_form(users_or_authz_groups):
+            # The permissions grid has been saved
+            # which is a grid of checkboxes named user$role
+            rpi = request.params.items()
+
+            # The grid passes us a list of the users/roles that were displayed
+            submitted = [ a for (a,b) in rpi if (b == u'submitted')]
+            # and also those which were checked
+            checked = [ a for (a,b) in rpi if (b == u'on')]
+
+            # from which we can deduce true/false for each user/role combination
+            # that was displayed in the form
+            table_dict={}
+            for a in submitted:
+                table_dict[a]=False
+            for a in checked:
+                table_dict[a]=True
+
+            # now we'll split up the user$role strings to make a dictionary from 
+            # (user,role) to True/False, which tells us what we need to do.
+            new_user_role_dict={}
+            for (ur,val) in table_dict.items():
+                u,r = ur.split('$')
+                new_user_role_dict[(u,r)] = val
+               
+            # we get the current user/role assignments 
+            # and make a dictionary of them
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_users_roles = [( uor.user.name, uor.role) for uor in current_uors if uor.user]
+            elif users_or_authz_groups=='authz_groups':
+                current_users_roles = [( uor.authorized_group.name, uor.role) for uor in current_uors if uor.authorized_group]        
             else:
-                authzgrouprole.purge()
-                if authzgrouprole.user:
-                    c.message = _(u'Deleted role \'%s\' for user \'%s\'') % \
-                                (authzgrouprole.role, authzgrouprole.user.name)
-                elif authzgrouprole.authorized_group:
-                    c.message = _(u'Deleted role \'%s\' for authorization group \'%s\'') % \
-                                (authzgrouprole.role, authzgrouprole.authorized_group.name)
-                model.Session.commit()
+                assert False, "shouldn't be here"
 
-        # retrieve group again ...
-        c.authorization_group = model.AuthorizationGroup.by_name(id)
-        fs = ckan.forms.get_authz_fieldset('authorization_group_authz_fs')\
-                .bind(c.authorization_group.roles)
-        c.form = fs.render()
-        c.new_roles_form = \
-            ckan.forms.get_authz_fieldset('new_authorization_group_roles_fs').render()
+            current_user_role_dict={}
+            for (u,r) in current_users_roles:
+                current_user_role_dict[(u,r)]=True
+
+            # and now we can loop through our dictionary of desired states
+            # checking whether a change needs to be made, and if so making it
+
+            # Here we check whether someone is already assigned a role, in order
+            # to avoid assigning it twice, or attempting to delete it when it
+            # doesn't exist. Otherwise problems can occur.
+            if users_or_authz_groups=='users':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_user_to_role(model.User.by_name(u),r,authorization_group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_user_from_role(model.User.by_name(u),r,authorization_group)
+            elif users_or_authz_groups=='authz_groups':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_authorization_group_to_role(model.AuthorizationGroup.by_name(u),r,authorization_group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_authorization_group_from_role(model.AuthorizationGroup.by_name(u),r,authorization_group)
+            else:
+                assert False, "shouldn't be here"
+
+            # finally commit the change to the database
+            model.repo.commit_and_remove()
+            h.flash_success("Changes Saved")
+
+
+
+        def action_add_form(users_or_authz_groups):
+            # The user is attempting to set new roles for a named user
+            new_user = request.params.get('new_user_name')
+            # this is the list of roles whose boxes were ticked
+            checked_roles = [ a for (a,b) in request.params.items() if (b == u'on')]
+            # this is the list of all the roles that were in the submitted form
+            submitted_roles = [ a for (a,b) in request.params.items() if (b == u'submitted')]
+
+            # from this we can make a dictionary of the desired states
+            # i.e. true for the ticked boxes, false for the unticked
+            desired_roles = {}
+            for r in submitted_roles:
+                desired_roles[r]=False
+            for r in checked_roles:
+                desired_roles[r]=True
+
+            # again, in order to avoid either creating a role twice or deleting one which is
+            # non-existent, we need to get the users' current roles (if any)
+  
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_roles = [uor.role for uor in current_uors if ( uor.user and uor.user.name == new_user )]
+                user_object = model.User.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown user:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_user_to_role(user_object, r, authorization_group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_user_from_role(user_object, r, authorization_group)
+                    h.flash_success("User Added")
+
+            elif users_or_authz_groups=='authz_groups':
+                current_roles = [uor.role for uor in current_uors if ( uor.authorized_group and uor.authorized_group.name == new_user )]
+                user_object = model.AuthorizationGroup.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown authorization group:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_authorization_group_to_role(user_object, r, authorization_group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_authorization_group_from_role(user_object, r, authorization_group)
+                    h.flash_success("Authorization Group Added")
+
+            else:
+                assert False, "shouldn't be here"
+
+            # and finally commit all these changes to the database
+            model.repo.commit_and_remove()
+
+
+        # In the event of a post request, work out which of the four possible actions
+        # is to be done, and do it before displaying the page
+        if 'add' in request.POST:
+            action_add_form('users')
+
+        if 'authz_add' in request.POST:
+            action_add_form('authz_groups')
+
+        if 'save' in request.POST:
+            action_save_form('users')
+
+        if 'authz_save' in request.POST:
+            action_save_form('authz_groups')
+
+        # =================
+        # Display the page
+
+        # Find out all the possible roles. At the moment, any role can be
+        # associated with any object, so that's easy:
+        possible_roles = model.Role.get_all()
+
+        # get the list of users who have roles on this object, with their roles
+        uors = get_userobjectroles()
+
+        # uniquify and sort
+        users = sorted(list(set([uor.user.name for uor in uors if uor.user])))
+        authz_groups = sorted(list(set([uor.authorized_group.name for uor in uors if uor.authorized_group])))
+
+        # make a dictionary from (user, role) to True, False
+        users_roles = [( uor.user.name, uor.role) for uor in uors if uor.user]
+        user_role_dict={}
+        for u in users:
+            for r in possible_roles:
+                if (u,r) in users_roles:
+                    user_role_dict[(u,r)]=True
+                else:
+                    user_role_dict[(u,r)]=False
+
+        # and similarly make a dictionary from (authz_group, role) to True, False
+        authz_groups_roles = [( uor.authorized_group.name, uor.role) for uor in uors if uor.authorized_group]
+        authz_groups_role_dict={}
+        for u in authz_groups:
+            for r in possible_roles:
+                if (u,r) in authz_groups_roles:
+                    authz_groups_role_dict[(u,r)]=True
+                else:
+                    authz_groups_role_dict[(u,r)]=False
+
+        # pass these variables to the template for rendering
+        c.roles = possible_roles
+
+        c.users = users
+        c.user_role_dict = user_role_dict
+
+        c.authz_groups = authz_groups
+        c.authz_groups_role_dict = authz_groups_role_dict
+
         return render('authorization_group/authz.html')
 
     def _render_edit_form(self, fs):


--- a/ckan/controllers/group.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/controllers/group.py	Tue Jun 07 23:22:22 2011 +0100
@@ -2,7 +2,7 @@
 
 from sqlalchemy.orm import eagerload_all
 from ckan.lib.base import BaseController, c, model, request, render, h
-from ckan.lib.base import ValidationException, abort
+from ckan.lib.base import ValidationException, abort, gettext
 from pylons.i18n import get_lang, _
 import ckan.authz as authz
 from ckan.authz import Authorizer
@@ -180,91 +180,222 @@
             return self.edit(id, data_dict, errors, error_summary)
 
     def authz(self, id):
-        c.group = model.Group.get(id)
-        if c.group is None:
+        group = model.Group.get(id)
+        if group is None:
             abort(404, _('Group not found'))
-        
-        c.groupname = c.group.name
-        c.grouptitle = c.group.display_name
+        c.groupname = group.name
+        c.grouptitle = group.display_name
 
-        c.authz_editable = self.authorizer.am_authorized(c, model.Action.EDIT_PERMISSIONS, c.group)
+        c.authz_editable = self.authorizer.am_authorized(c, model.Action.EDIT_PERMISSIONS, group)
         if not c.authz_editable:
-            abort(401, _('Not authorized to edit authorization for group'))
+            abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id))
+ 
 
-        if 'save' in request.params: # form posted
-            # needed because request is nested
-            # multidict which is read only
-            params = dict(request.params)
-            c.fs = ckan.forms.get_authz_fieldset('group_authz_fs').bind(c.group.roles, data=params or None)
-            try:
-                self._update_authz(c.fs)
-            except ValidationException, error:
-                # TODO: sort this out 
-                # fs = error.args[0]
-                # return render('group/authz.html')
-                raise
-            # now do new roles
-            newrole_user_id = request.params.get('GroupRole--user_id')
-            newrole_authzgroup_id = request.params.get('GroupRole--authorized_group_id')
-            if newrole_user_id != '__null_value__' and newrole_authzgroup_id != '__null_value__':
-                c.message = _(u'Please select either a user or an authorization group, not both.')
-            elif newrole_user_id != '__null_value__':
-                user = model.Session.query(model.User).get(newrole_user_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('GroupRole--role')
-                newgrouprole = model.GroupRole(user=user, group=c.group,
-                        role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                for extension in self.extensions:
-                    extension.authz_add_role(newgrouprole)
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for user \'%s\'') % (
-                    newgrouprole.role,
-                    newgrouprole.user.display_name)
-            elif newrole_authzgroup_id != '__null_value__':
-                authzgroup = model.Session.query(model.AuthorizationGroup).get(newrole_authzgroup_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('GroupRole--role')
-                newgrouprole = model.GroupRole(authorized_group=authzgroup, 
-                        group=c.group, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                for extension in self.extensions:
-                    extension.authz_add_role(newgrouprole)
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for authorization group \'%s\'') % (
-                    newgrouprole.role,
-                    newgrouprole.authorized_group.name)
-        elif 'role_to_delete' in request.params:
-            grouprole_id = request.params['role_to_delete']
-            grouprole = model.Session.query(model.GroupRole).get(grouprole_id)
-            if grouprole is None:
-                c.error = _(u'Error: No role found with that id')
+        #see package.py for comments
+        def get_userobjectroles():
+            group = model.Group.get(id)
+            uors = model.Session.query(model.GroupRole).join('group').filter_by(name=group.name).all()
+            return uors
+
+        def action_save_form(users_or_authz_groups):
+            # The permissions grid has been saved
+            # which is a grid of checkboxes named user$role
+            rpi = request.params.items()
+
+            # The grid passes us a list of the users/roles that were displayed
+            submitted = [ a for (a,b) in rpi if (b == u'submitted')]
+            # and also those which were checked
+            checked = [ a for (a,b) in rpi if (b == u'on')]
+
+            # from which we can deduce true/false for each user/role combination
+            # that was displayed in the form
+            table_dict={}
+            for a in submitted:
+                table_dict[a]=False
+            for a in checked:
+                table_dict[a]=True
+
+            # now we'll split up the user$role strings to make a dictionary from 
+            # (user,role) to True/False, which tells us what we need to do.
+            new_user_role_dict={}
+            for (ur,val) in table_dict.items():
+                u,r = ur.split('$')
+                new_user_role_dict[(u,r)] = val
+               
+            # we get the current user/role assignments 
+            # and make a dictionary of them
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_users_roles = [( uor.user.name, uor.role) for uor in current_uors if uor.user]
+            elif users_or_authz_groups=='authz_groups':
+                current_users_roles = [( uor.authorized_group.name, uor.role) for uor in current_uors if uor.authorized_group]        
             else:
-                for extension in self.extensions:
-                    extension.authz_remove_role(grouprole)
-                grouprole.purge()
-                if grouprole.user:
-                    c.message = _(u'Deleted role \'%s\' for user \'%s\'') % \
-                                (grouprole.role, grouprole.user.display_name)
-                elif grouprole.authorized_group:
-                    c.message = _(u'Deleted role \'%s\' for authorization group \'%s\'') % \
-                                (grouprole.role, grouprole.authorized_group.name)
-                model.Session.commit()
+                assert False, "shouldn't be here"
 
-        # retrieve group again ...
-        c.group = model.Group.get(id)
-        fs = ckan.forms.get_authz_fieldset('group_authz_fs').bind(c.group.roles)
-        c.form = fs.render()
-        c.new_roles_form = \
-            ckan.forms.get_authz_fieldset('new_group_roles_fs').render()
+            current_user_role_dict={}
+            for (u,r) in current_users_roles:
+                current_user_role_dict[(u,r)]=True
+
+            # and now we can loop through our dictionary of desired states
+            # checking whether a change needs to be made, and if so making it
+
+            # Here we check whether someone is already assigned a role, in order
+            # to avoid assigning it twice, or attempting to delete it when it
+            # doesn't exist. Otherwise problems can occur.
+            if users_or_authz_groups=='users':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_user_to_role(model.User.by_name(u),r,group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_user_from_role(model.User.by_name(u),r,group)
+            elif users_or_authz_groups=='authz_groups':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_authorization_group_to_role(model.AuthorizationGroup.by_name(u),r,group)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_authorization_group_from_role(model.AuthorizationGroup.by_name(u),r,group)
+            else:
+                assert False, "shouldn't be here"
+
+
+            # finally commit the change to the database
+            model.repo.commit_and_remove()
+            h.flash_success("Changes Saved")
+
+
+
+        def action_add_form(users_or_authz_groups):
+            # The user is attempting to set new roles for a named user
+            new_user = request.params.get('new_user_name')
+            # this is the list of roles whose boxes were ticked
+            checked_roles = [ a for (a,b) in request.params.items() if (b == u'on')]
+            # this is the list of all the roles that were in the submitted form
+            submitted_roles = [ a for (a,b) in request.params.items() if (b == u'submitted')]
+
+            # from this we can make a dictionary of the desired states
+            # i.e. true for the ticked boxes, false for the unticked
+            desired_roles = {}
+            for r in submitted_roles:
+                desired_roles[r]=False
+            for r in checked_roles:
+                desired_roles[r]=True
+
+            # again, in order to avoid either creating a role twice or deleting one which is
+            # non-existent, we need to get the users' current roles (if any)
+  
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_roles = [uor.role for uor in current_uors if ( uor.user and uor.user.name == new_user )]
+                user_object = model.User.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown user:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_user_to_role(user_object, r, group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_user_from_role(user_object, r, group)
+                    h.flash_success("User Added")
+
+            elif users_or_authz_groups=='authz_groups':
+                current_roles = [uor.role for uor in current_uors if ( uor.authorized_group and uor.authorized_group.name == new_user )]
+                user_object = model.AuthorizationGroup.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown authorization group:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_authorization_group_to_role(user_object, r, group)
+                        else:
+                            if (r in current_roles):
+                                model.remove_authorization_group_from_role(user_object, r, group)
+                    h.flash_success("Authorization Group Added")
+
+            else:
+                assert False, "shouldn't be here"
+
+            # and finally commit all these changes to the database
+            model.repo.commit_and_remove()
+
+
+        # In the event of a post request, work out which of the four possible actions
+        # is to be done, and do it before displaying the page
+        if 'add' in request.POST:
+            action_add_form('users')
+
+        if 'authz_add' in request.POST:
+            action_add_form('authz_groups')
+
+        if 'save' in request.POST:
+            action_save_form('users')
+
+        if 'authz_save' in request.POST:
+            action_save_form('authz_groups')
+
+        # =================
+        # Display the page
+
+        # Find out all the possible roles. At the moment, any role can be
+        # associated with any object, so that's easy:
+        possible_roles = model.Role.get_all()
+
+        # get the list of users who have roles on this object, with their roles
+        uors = get_userobjectroles()
+
+        # uniquify and sort
+        users = sorted(list(set([uor.user.name for uor in uors if uor.user])))
+        authz_groups = sorted(list(set([uor.authorized_group.name for uor in uors if uor.authorized_group])))
+
+        # make a dictionary from (user, role) to True, False
+        users_roles = [( uor.user.name, uor.role) for uor in uors if uor.user]
+        user_role_dict={}
+        for u in users:
+            for r in possible_roles:
+                if (u,r) in users_roles:
+                    user_role_dict[(u,r)]=True
+                else:
+                    user_role_dict[(u,r)]=False
+
+        # and similarly make a dictionary from (authz_group, role) to True, False
+        authz_groups_roles = [( uor.authorized_group.name, uor.role) for uor in uors if uor.authorized_group]
+        authz_groups_role_dict={}
+        for u in authz_groups:
+            for r in possible_roles:
+                if (u,r) in authz_groups_roles:
+                    authz_groups_role_dict[(u,r)]=True
+                else:
+                    authz_groups_role_dict[(u,r)]=False
+
+        # pass these variables to the template for rendering
+        c.roles = possible_roles
+
+        c.users = users
+        c.user_role_dict = user_role_dict
+
+        c.authz_groups = authz_groups
+        c.authz_groups_role_dict = authz_groups_role_dict
+
         return render('group/authz.html')
-        
+
+
+
+
+
+
+       
     def history(self, id):
         if 'diff' in request.params or 'selected1' in request.params:
             try:


--- a/ckan/controllers/package.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/controllers/package.py	Tue Jun 07 23:22:22 2011 +0100
@@ -482,79 +482,221 @@
         if not c.authz_editable:
             abort(401, gettext('User %r not authorized to edit %s authorizations') % (c.user, id))
 
-        if 'save' in request.params: # form posted
-            # A dict needed for the params because request.params is a nested
-            # multidict, which is read only.
-            params = dict(request.params)
-            c.fs = ckan.forms.get_authz_fieldset('package_authz_fs').bind(pkg.roles, data=params or None)
-            try:
-                self._update_authz(c.fs)
-            except ValidationException, error:
-                # TODO: sort this out 
-                # fs = error.args
-                # return render('package/authz.html')
-                raise
-            # now do new roles
-            newrole_user_id = request.params.get('PackageRole--user_id')
-            newrole_authzgroup_id = request.params.get('PackageRole--authorized_group_id')
-            if newrole_user_id != '__null_value__' and newrole_authzgroup_id != '__null_value__':
-                c.message = _(u'Please select either a user or an authorization group, not both.')
-            elif newrole_user_id != '__null_value__':
-                user = model.Session.query(model.User).get(newrole_user_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('PackageRole--role')
-                newpkgrole = model.PackageRole(user=user, package=pkg,
-                        role=role)
-                # With FA no way to get new PackageRole back to set package attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.PackageRole, data=params or None)
-                # new_roles.sync()
-                for item in self.extensions:
-                    item.authz_add_role(newpkgrole)
-                model.repo.commit_and_remove()
-                c.message = _(u'Added role \'%s\' for user \'%s\'') % (
-                    newpkgrole.role,
-                    newpkgrole.user.display_name)
-            elif newrole_authzgroup_id != '__null_value__':
-                authzgroup = model.Session.query(model.AuthorizationGroup).get(newrole_authzgroup_id)
-                # TODO: chech user is not None (should go in validation ...)
-                role = request.params.get('PackageRole--role')
-                newpkgrole = model.PackageRole(authorized_group=authzgroup, 
-                        package=pkg, role=role)
-                # With FA no way to get new GroupRole back to set group attribute
-                # new_roles = ckan.forms.new_roles_fs.bind(model.GroupRole, data=params or None)
-                # new_roles.sync()
-                for item in self.extensions:
-                    item.authz_add_role(newpkgrole)
-                model.Session.commit()
-                model.Session.remove()
-                c.message = _(u'Added role \'%s\' for authorization group \'%s\'') % (
-                    newpkgrole.role,
-                    newpkgrole.authorized_group.name)
-        elif 'role_to_delete' in request.params:
-            pkgrole_id = request.params['role_to_delete']
-            pkgrole = model.Session.query(model.PackageRole).get(pkgrole_id)
-            if pkgrole is None:
-                c.error = _(u'Error: No role found with that id')
+        # Three different ways of getting the list of userobjectroles for this package.
+        # They all take a frighteningly long time to retrieve
+        # the data, but I can't tell how they'll scale. On a large dataset it might
+        # be worth working out which is quickest, so I've made a function for
+        # ease of changing the query.
+        def get_userobjectroles():
+            # we already have a pkg variable in scope, but I found while testing
+            # that it occasionally mysteriously loses its value!  Redefine it
+            # here. 
+            pkg = model.Package.get(id)
+
+            # dread's suggestion for 'get all userobjectroles for this package':
+            uors = model.Session.query(model.PackageRole).join('package').filter_by(name=pkg.name).all()
+            # rgrp's version:
+            # uors = model.Session.query(model.PackageRole).filter_by(package=pkg)
+            # get them all and filter in python:
+            # uors = [uor for uor in model.Session.query(model.PackageRole).all() if uor.package==pkg]
+            return uors
+
+        def action_save_form(users_or_authz_groups):
+            # The permissions grid has been saved
+            # which is a grid of checkboxes named user$role
+            rpi = request.params.items()
+
+            # The grid passes us a list of the users/roles that were displayed
+            submitted = [ a for (a,b) in rpi if (b == u'submitted')]
+            # and also those which were checked
+            checked = [ a for (a,b) in rpi if (b == u'on')]
+
+            # from which we can deduce true/false for each user/role combination
+            # that was displayed in the form
+            table_dict={}
+            for a in submitted:
+                table_dict[a]=False
+            for a in checked:
+                table_dict[a]=True
+
+            # now we'll split up the user$role strings to make a dictionary from 
+            # (user,role) to True/False, which tells us what we need to do.
+            new_user_role_dict={}
+            for (ur,val) in table_dict.items():
+                u,r = ur.split('$')
+                new_user_role_dict[(u,r)] = val
+               
+            # we get the current user/role assignments 
+            # and make a dictionary of them
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_users_roles = [( uor.user.name, uor.role) for uor in current_uors if uor.user]
+            elif users_or_authz_groups=='authz_groups':
+                current_users_roles = [( uor.authorized_group.name, uor.role) for uor in current_uors if uor.authorized_group]        
             else:
-                for item in self.extensions:
-                    item.authz_remove_role(pkgrole)
-                if pkgrole.user:
-                    c.message = _(u'Deleted role \'%s\' for user \'%s\'') % \
-                                (pkgrole.role, pkgrole.user.display_name)
-                elif pkgrole.authorized_group:
-                    c.message = _(u'Deleted role \'%s\' for authorization group \'%s\'') % \
-                                (pkgrole.role, pkgrole.authorized_group.name)
-                pkgrole.purge()
-                model.repo.commit_and_remove()
+                assert False, "shouldn't be here"
 
-        # retrieve pkg again ...
-        c.pkg = model.Package.get(id)
-        fs = ckan.forms.get_authz_fieldset('package_authz_fs').bind(c.pkg.roles)
-        c.form = fs.render()
-        c.new_roles_form = \
-            ckan.forms.get_authz_fieldset('new_package_roles_fs').render()
+            current_user_role_dict={}
+            for (u,r) in current_users_roles:
+                current_user_role_dict[(u,r)]=True
+
+            # and now we can loop through our dictionary of desired states
+            # checking whether a change needs to be made, and if so making it
+
+            # Here we check whether someone is already assigned a role, in order
+            # to avoid assigning it twice, or attempting to delete it when it
+            # doesn't exist. Otherwise problems can occur.
+            if users_or_authz_groups=='users':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_user_to_role(model.User.by_name(u),r,pkg)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_user_from_role(model.User.by_name(u),r,pkg)
+            elif users_or_authz_groups=='authz_groups':
+                for ((u,r), val) in new_user_role_dict.items():
+                    if val:
+                        if not ((u,r) in current_user_role_dict):
+                            model.add_authorization_group_to_role(model.AuthorizationGroup.by_name(u),r,pkg)
+                    else:
+                        if ((u,r) in current_user_role_dict):
+                            model.remove_authorization_group_from_role(model.AuthorizationGroup.by_name(u),r,pkg)
+            else:
+                assert False, "shouldn't be here"
+
+
+            # finally commit the change to the database
+            model.repo.commit_and_remove()
+            h.flash_success("Changes Saved")
+
+
+
+        def action_add_form(users_or_authz_groups):
+            # The user is attempting to set new roles for a named user
+            new_user = request.params.get('new_user_name')
+            # this is the list of roles whose boxes were ticked
+            checked_roles = [ a for (a,b) in request.params.items() if (b == u'on')]
+            # this is the list of all the roles that were in the submitted form
+            submitted_roles = [ a for (a,b) in request.params.items() if (b == u'submitted')]
+
+            # from this we can make a dictionary of the desired states
+            # i.e. true for the ticked boxes, false for the unticked
+            desired_roles = {}
+            for r in submitted_roles:
+                desired_roles[r]=False
+            for r in checked_roles:
+                desired_roles[r]=True
+
+            # again, in order to avoid either creating a role twice or deleting one which is
+            # non-existent, we need to get the users' current roles (if any)
+  
+            current_uors = get_userobjectroles()
+
+            if users_or_authz_groups=='users':
+                current_roles = [uor.role for uor in current_uors if ( uor.user and uor.user.name == new_user )]
+                user_object = model.User.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown user:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_user_to_role(user_object, r, pkg)
+                        else:
+                            if (r in current_roles):
+                                model.remove_user_from_role(user_object, r, pkg)
+                    h.flash_success("User Added")
+
+            elif users_or_authz_groups=='authz_groups':
+                current_roles = [uor.role for uor in current_uors if ( uor.authorized_group and uor.authorized_group.name == new_user )]
+                user_object = model.AuthorizationGroup.by_name(new_user)
+                if user_object==None:
+                    # The submitted user does not exist. Bail with flash message
+                    h.flash_error('unknown authorization group:' + str (new_user))
+                else:
+                    # Whenever our desired state is different from our current state, change it.
+                    for (r,val) in desired_roles.items():
+                        if val:
+                            if (r not in current_roles):
+                                model.add_authorization_group_to_role(user_object, r, pkg)
+                        else:
+                            if (r in current_roles):
+                                model.remove_authorization_group_from_role(user_object, r, pkg)
+                    h.flash_success("Authorization Group Added")
+
+            else:
+                assert False, "shouldn't be here"
+
+            # and finally commit all these changes to the database
+            model.repo.commit_and_remove()
+
+
+        # In the event of a post request, work out which of the four possible actions
+        # is to be done, and do it before displaying the page
+        if 'add' in request.POST:
+            action_add_form('users')
+
+        if 'authz_add' in request.POST:
+            action_add_form('authz_groups')
+
+        if 'save' in request.POST:
+            action_save_form('users')
+
+        if 'authz_save' in request.POST:
+            action_save_form('authz_groups')
+
+        # =================
+        # Display the page
+
+        # Find out all the possible roles. At the moment, any role can be
+        # associated with any object, so that's easy:
+        possible_roles = model.Role.get_all()
+
+        # get the list of users who have roles on this object, with their roles
+        uors = get_userobjectroles()
+
+        # uniquify and sort
+        users = sorted(list(set([uor.user.name for uor in uors if uor.user])))
+        authz_groups = sorted(list(set([uor.authorized_group.name for uor in uors if uor.authorized_group])))
+
+        # make a dictionary from (user, role) to True, False
+        users_roles = [( uor.user.name, uor.role) for uor in uors if uor.user]
+        user_role_dict={}
+        for u in users:
+            for r in possible_roles:
+                if (u,r) in users_roles:
+                    user_role_dict[(u,r)]=True
+                else:
+                    user_role_dict[(u,r)]=False
+
+        # and similarly make a dictionary from (authz_group, role) to True, False
+        authz_groups_roles = [( uor.authorized_group.name, uor.role) for uor in uors if uor.authorized_group]
+        authz_groups_role_dict={}
+        for u in authz_groups:
+            for r in possible_roles:
+                if (u,r) in authz_groups_roles:
+                    authz_groups_role_dict[(u,r)]=True
+                else:
+                    authz_groups_role_dict[(u,r)]=False
+
+        # pass these variables to the template for rendering
+        c.roles = possible_roles
+
+        c.users = users
+        c.user_role_dict = user_role_dict
+
+        c.authz_groups = authz_groups
+        c.authz_groups_role_dict = authz_groups_role_dict
+
         return render('package/authz.html')
 
+
+
+
     def rate(self, id):
         package_name = id
         package = model.Package.get(package_name)


--- a/ckan/lib/create_test_data.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/lib/create_test_data.py	Tue Jun 07 23:22:22 2011 +0100
@@ -370,10 +370,12 @@
                              description=u'Roger likes these books.')
         for obj in [david, roger]:
             model.Session.add(obj)
+        
         cls.group_names.add(u'david')
         cls.group_names.add(u'roger')
-        david.packages = [pkg1, pkg2]
-        roger.packages = [pkg1]
+        model.Session.add(model.PackageGroup(package=pkg1, group=david))
+        model.Session.add(model.PackageGroup(package=pkg2, group=david))
+        model.Session.add(model.PackageGroup(package=pkg1, group=roger))
         # authz
         model.Session.add_all([
             model.User(name=u'tester', apikey=u'tester', password=u'tester'),


--- a/ckan/lib/dictization/__init__.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/lib/dictization/__init__.py	Tue Jun 07 23:22:22 2011 +0100
@@ -17,13 +17,19 @@
     model = context["model"]
     session = context["session"]
 
-    ModelClass = obj.__class__
-    table = class_mapper(ModelClass).mapped_table
-
-    fields = [field.name for field in table.c]
+    if isinstance(obj, sqlalchemy.engine.base.RowProxy):
+        fields = obj.keys()
+    else:
+        ModelClass = obj.__class__
+        table = class_mapper(ModelClass).mapped_table
+        fields = [field.name for field in table.c]
 
     for field in fields:
         name = field
+        if name in ('current', 'expired_timestamp', 'expired_id'):
+            continue
+        if name == 'continuity_id':
+            continue
         value = getattr(obj, name)
         if value is None:
             result_dict[name] = value
@@ -111,6 +117,10 @@
             continue
         setattr(obj, key, value)
 
+    if context.get('pending'):
+        if session.is_modified(obj, include_collections=False):
+            obj.state = 'pending'
+
     session.add(obj)
 
     return obj


--- a/ckan/lib/dictization/model_dictize.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/lib/dictization/model_dictize.py	Tue Jun 07 23:22:22 2011 +0100
@@ -1,4 +1,6 @@
 from pylons import config
+from sqlalchemy.sql import select, and_
+import datetime
 
 from ckan.lib.dictization import (obj_list_dictize,
                                   obj_dict_dictize,
@@ -6,6 +8,14 @@
 import ckan.misc
 import json
 
+END_DATE = datetime.datetime(9999,12,31)
+
+class FakeSqlAlchemyObject(object):
+
+    def __init__(self, **kw):
+        for key, value in kw.iteritems():
+            self.key = value
+
 ## package save
 
 def group_list_dictize(obj_list, context, sort_key=lambda x:x):
@@ -41,6 +51,17 @@
 
     return sorted(result_list, key=lambda x: x["key"])
 
+def extras_list_dictize(extras_list, context):
+    result_list = []
+    for extra in extras_list:
+        dictized = table_dictize(extra, context)
+        value = dictized["value"]
+        if not(context.get("extras_as_string") and isinstance(value, basestring)):
+            dictized["value"] = json.dumps(value)
+        result_list.append(dictized)
+
+    return sorted(result_list, key=lambda x: x["key"])
+
 def resource_dictize(res, context):
     resource = table_dictize(res, context)
     extras = resource.pop("extras", None)
@@ -48,23 +69,72 @@
         resource.update(extras)
     return resource
 
+def _execute_with_revision(q, rev_table, context):
+
+    model = context['model']
+    meta = model.meta
+    session = model.Session
+    revision_id = context.get('revision_date')
+    revision_date = context.get('revision_date')
+    pending = context.get('pending')
+
+    if revision_id:
+        model = session.query(context['model'].Revision).filter_by()
+    
+    if revision_date:
+        q = q.where(rev_table.c.revision_timestamp >= revision_date)
+        q = q.where(rev_table.c.expired_timestamp < revision_date)
+    elif pending:
+        q = q.where(rev_table.c.expired_timestamp == '9999-12-31')
+    else:
+        q = q.where(rev_table.c.current == '1')
+    return session.execute(q)
+
+
 def package_dictize(pkg, context):
-
-    result_dict = table_dictize(pkg, context)
-
-    result_dict["resources"] = resource_list_dictize(pkg.resources, context)
-
-    result_dict["tags"] = obj_list_dictize(
-        pkg.tags, context, lambda x: x["name"])
-    result_dict["extras"] = extras_dict_dictize(
-        pkg._extras, context)
-    result_dict["groups"] = group_list_dictize(
-        pkg.groups, context, lambda x: x["name"])
-    result_dict["relationships_as_subject"] = obj_list_dictize(
-        pkg.relationships_as_subject, context)
-    result_dict["relationships_as_object"] = obj_list_dictize(
-        pkg.relationships_as_object, context)
-
+    model = context['model']
+    #package
+    package_rev = model.package_revision_table
+    q = select([package_rev]).where(package_rev.c.id == pkg.id)
+    result = _execute_with_revision(q, package_rev, context).first()
+    result_dict = table_dictize(result, context)
+    #resources
+    res_rev = model.resource_revision_table
+    resource_group = model.resource_group_table
+    q = select([res_rev], from_obj = res_rev.join(resource_group, 
+               resource_group.c.id == res_rev.c.resource_group_id))
+    q = q.where(resource_group.c.package_id == pkg.id)
+    result = _execute_with_revision(q, res_rev, context)
+    result_dict["resources"] = resource_list_dictize(result, context)
+    #tags
+    tag_rev = model.package_tag_revision_table
+    tag = model.tag_table
+    q = select([tag], 
+        from_obj=tag_rev.join(tag, tag.c.id == tag_rev.c.tag_id)
+        ).where(tag_rev.c.package_id == pkg.id)
+    result = _execute_with_revision(q, tag_rev, context)
+    result_dict["tags"] = obj_list_dictize(result, context, lambda x: x["name"])
+    #extras
+    extra_rev = model.extra_revision_table
+    q = select([extra_rev]).where(extra_rev.c.package_id == pkg.id)
+    result = _execute_with_revision(q, extra_rev, context)
+    result_dict["extras"] = extras_list_dictize(result, context)
+    #groups
+    group_rev = model.package_group_revision_table
+    group = model.group_table
+    q = select([group],
+               from_obj=group_rev.join(group, group.c.id == group_rev.c.group_id)
+               ).where(group_rev.c.package_id == pkg.id)
+    result = _execute_with_revision(q, group_rev, context)
+    result_dict["groups"] = obj_list_dictize(result, context)
+    #relations
+    rel_rev = model.package_relationship_revision_table
+    q = select([rel_rev]).where(rel_rev.c.subject_package_id == pkg.id)
+    result = _execute_with_revision(q, rel_rev, context)
+    result_dict["relationships_as_subject"] = obj_list_dictize(result, context)
+    q = select([rel_rev]).where(rel_rev.c.object_package_id == pkg.id)
+    result = _execute_with_revision(q, rel_rev, context)
+    result_dict["relationships_as_object"] = obj_list_dictize(result, context)
     return result_dict
 
 def group_dictize(group, context):
@@ -102,12 +172,16 @@
 def resource_dict_to_api(res_dict, package_id, context):
     res_dict.pop("revision_id")
     res_dict.pop("state")
+    res_dict.pop("revision_timestamp")
     res_dict["package_id"] = package_id
 
 
 def package_to_api1(pkg, context):
 
     dictized = package_dictize(pkg, context)
+
+    dictized.pop("revision_timestamp")
+
     dictized["groups"] = [group["name"] for group in dictized["groups"]]
     dictized["tags"] = [tag["name"] for tag in dictized["tags"]]
     dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) 
@@ -160,7 +234,10 @@
 def package_to_api2(pkg, context):
 
     dictized = package_dictize(pkg, context)
+
     dictized["groups"] = [group["id"] for group in dictized["groups"]]
+    dictized.pop("revision_timestamp")
+    
     dictized["tags"] = [tag["name"] for tag in dictized["tags"]]
     dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) 
                               for extra in dictized["extras"])


--- a/ckan/lib/dictization/model_save.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/lib/dictization/model_save.py	Tue Jun 07 23:22:22 2011 +0100
@@ -21,31 +21,94 @@
 
     table = class_mapper(model.Resource).mapped_table
     fields = [field.name for field in table.c]
-
+    
     for key, value in res_dict.iteritems():
         if isinstance(value, list):
             continue
-        if key == 'extras':
+        if key in ('extras', 'revision_timestamp'):
             continue
         if key in fields:
             setattr(obj, key, value)
         else:
             obj.extras[key] = value
 
+    if context.get('pending'):
+        if session.is_modified(obj, include_collections=False):
+            obj.state = 'pending'
+
     session.add(obj)
 
     return obj
 
-def resource_list_save(res_dicts, context):
+def package_resource_list_save(res_dicts, package, context):
+
+    pending = context.get('pending')
+
+    resource_list = package.resource_groups_all[0].resources_all
+    old_list = package.resource_groups_all[0].resources_all[:]
 
     obj_list = []
     for res_dict in res_dicts:
         obj = resource_dict_save(res_dict, context)
         obj_list.append(obj)
 
-    return obj_list
+    resource_list[:] = obj_list
 
-def extras_save(extras_dicts, context):
+    for resource in set(old_list) - set(obj_list):
+        if pending and resource.state <> 'deleted':
+            resource.state = 'pending-deleted'
+        else:
+            resource.state = 'deleted'
+        resource_list.append(resource)
+    tag_package_tag = dict((package_tag.tag, package_tag) 
+                            for package_tag in
+                            package.package_tag_all)
+
+
+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"]
+
+    extras_as_string = context.get("extras_as_string", False)
+    extras_list = obj.extras_list
+    old_extras = dict((extra.key, extra) for extra in extras_list)
+
+    new_extras = {}
+    for extra_dict in extra_dicts:
+        if extra_dict.get("deleted"):
+            continue
+        if extras_as_string:
+            new_extras[extra_dict["key"]] = extra_dict["value"]
+        else:
+            new_extras[extra_dict["key"]] = json.loads(extra_dict["value"])
+    #new
+    for key in set(new_extras.keys()) - set(old_extras.keys()):
+        state = 'pending' if context.get('pending') else 'active'
+        extra = model.PackageExtra(state=state, key=key, value=new_extras[key])
+        session.add(extra)
+        extras_list.append(extra)
+    #changed
+    for key in set(new_extras.keys()) & set(old_extras.keys()):
+        extra = old_extras[key]
+        if new_extras[key] == extra.value:
+            continue
+        state = 'pending' if context.get('pending') else 'active'
+        extra.value = new_extras[key]
+        extra.state = state
+        session.add(extra)
+    #deleted
+    for key in set(old_extras.keys()) - set(new_extras.keys()):
+        extra = old_extras[key]
+        if extra.state == 'deleted':
+            continue
+        state = 'pending-deleted' if context.get('pending') else 'delete'
+        extra.state = state
+
+def group_extras_save(extras_dicts, context):
 
     model = context["model"]
     session = context["session"]
@@ -62,25 +125,55 @@
 
     return result_dict
 
+def package_tag_list_save(tag_dicts, package, context):
 
-def tag_list_save(tag_dicts, context):
+    
+    allow_partial_update = context.get("allow_partial_update", False)
+    if not tag_dicts and allow_partial_update:
+        return
 
     model = context["model"]
     session = context["session"]
+    pending = context.get('pending')
 
-    tag_list = []
-    for table_dict in tag_dicts:
-        obj = table_dict_save(table_dict, model.Tag, context)
-        tag_list.append(obj)
+    tag_package_tag = dict((package_tag.tag, package_tag) 
+                            for package_tag in
+                            package.package_tag_all)
 
-    return list(set(tag_list))
+    tags = set()
+    for tag_dict in tag_dicts:
+        obj = table_dict_save(tag_dict, model.Tag, context)
+        tags.add(obj)
 
-def group_list_save(group_dicts, context):
+    for tag in set(tag_package_tag.keys()) - tags:
+        package_tag = tag_package_tag[tag]
+        if pending and package_tag.state <> 'deleted':
+            package_tag.state = 'pending-deleted'
+        else:
+            package_tag.state = 'deleted'
+
+    for tag in tags - set(tag_package_tag.keys()):
+        state = 'pending' if pending else 'active'
+        package_tag_obj = model.PackageTag(package, tag, state)
+        session.add(package_tag_obj)
+        tag_package_tag[tag] = package_tag_obj
+
+    package.package_tag_all[:] = tag_package_tag.values()
+
+def package_group_list_save(group_dicts, package, context):
+
+    allow_partial_update = context.get("allow_partial_update", False)
+    if not group_dicts and allow_partial_update:
+        return
 
     model = context["model"]
     session = context["session"]
+    pending = context.get('pending')
 
-    group_list = []
+    group_package_group = dict((package_group.group, package_group) 
+                               for package_group in
+                               package.package_group_all)
+    groups = set()
     for group_dict in group_dicts:
         id = group_dict.get("id")
         name = group_dict.get("name")
@@ -88,23 +181,55 @@
             group = session.query(model.Group).get(id)
         else:
             group = session.query(model.Group).filter_by(name=name).first()
+        groups.add(group)
 
-        group_list.append(group)
+    for group in groups - set(group_package_group.keys()):
+        package_group_obj = model.PackageGroup(package = package,
+                                               group = group,
+                                               state = 'active')
+        session.add(package_group_obj)
+        group_package_group[group] = package_group_obj
 
-    return group_list
+    for group in set(group_package_group.keys()) - groups:
+        group_package_group.pop(group)
+        continue
+        ### this is alternate behavioiur below which is correct
+        ### but not compatible with old behaviour
+        package_group = group_package_group[group]
+        if pending and package_group.state <> 'deleted':
+            package_group.state = 'pending-deleted'
+        else:
+            package_group.state = 'deleted'
+
+    package.package_group_all[:] = group_package_group.values()
+
     
-def relationship_list_save(relationship_dicts, context):
+def relationship_list_save(relationship_dicts, package, attr, context):
 
+    allow_partial_update = context.get("allow_partial_update", False)
+    if not relationship_dicts and allow_partial_update:
+        return
     model = context["model"]
     session = context["session"]
+    pending = context.get('pending')
 
-    relationship_list = []
+    relationship_list = getattr(package, attr)
+    old_list = relationship_list[:]
+
+    relationships = []
     for relationship_dict in relationship_dicts:
         obj = table_dict_save(relationship_dict, 
                               model.PackageRelationship, context)
-        relationship_list.append(obj)
+        relationships.append(obj)
 
-    return relationship_list
+    relationship_list[:] = relationships
+
+    for relationship in set(old_list) - set(relationship_list):
+        if pending and relationship.state <> 'deleted':
+            relationship.state = 'pending-deleted'
+        else:
+            relationship.state = 'deleted'
+        relationship_list.append(relationship)
 
 def package_dict_save(pkg_dict, context):
 
@@ -117,33 +242,16 @@
 
     pkg = table_dict_save(pkg_dict, Package, context)
 
-    resources = resource_list_save(pkg_dict.get("resources", []), context)
-    if resources:
-        pkg.resources[:] = resources
+    package_resource_list_save(pkg_dict.get("resources", []), pkg, context)
+    package_tag_list_save(pkg_dict.get("tags", []), pkg, context)
+    package_group_list_save(pkg_dict.get("groups", []), pkg, context)
 
-    tags = tag_list_save(pkg_dict.get("tags", []), context)
-    if tags or not allow_partial_update:
-        pkg.tags[:] = tags
+    subjects = pkg_dict.get('relationships_as_subject', [])
+    relationship_list_save(subjects, pkg, 'relationships_as_subject', context)
+    objects = pkg_dict.get('relationships_as_object', [])
+    relationship_list_save(subjects, pkg, 'relationships_as_object', context)
 
-    groups = group_list_save(pkg_dict.get("groups", []), context)
-    if groups or not allow_partial_update:
-        pkg.groups[:] = groups
-
-    subjects = pkg_dict.get("relationships_as_subject", [])
-    if subjects or not allow_partial_update:
-        pkg.relationships_as_subject[:] = relationship_list_save(subjects, context)
-    objects = pkg_dict.get("relationships_as_object", [])
-    if objects or not allow_partial_update:
-        pkg.relationships_as_object[:] = relationship_list_save(objects, context)
-
-    extras = extras_save(pkg_dict.get("extras", {}), context)
-    if extras or not allow_partial_update:
-        old_extras = set(pkg.extras.keys())
-        new_extras = set(extras.keys())
-        for key in old_extras - new_extras:
-            del pkg.extras[key]
-        for key in new_extras:
-            pkg.extras[key] = extras[key] 
+    extras = package_extras_save(pkg_dict.get("extras", []), pkg, context)
 
     return pkg
 
@@ -161,7 +269,7 @@
         group_dict["id"] = group.id 
 
     group = table_dict_save(group_dict, Group, context)
-    extras = extras_save(group_dict.get("extras", {}), context)
+    extras = group_extras_save(group_dict.get("extras", {}), context)
     if extras or not allow_partial_update:
         old_extras = set(group.extras.keys())
         new_extras = set(extras.keys())


--- a/ckan/lib/package_saver.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/lib/package_saver.py	Tue Jun 07 23:22:22 2011 +0100
@@ -45,7 +45,10 @@
         c.pkg_maintainer_link = cls._person_email_link(c.pkg.maintainer, c.pkg.maintainer_email, "Maintainer")
         c.package_relationships = pkg.get_relationships_printable()
         c.pkg_extras = []
-        for k, v in sorted(pkg.extras.items()):
+        for extra in sorted(pkg.extras_list, key=lambda x:x.key):
+            if extra.state == 'deleted':
+                continue
+            k, v = extra.key, extra.value
             if k in g.package_hide_extras:
                 continue
             if isinstance(v, (list, tuple)):


--- a/ckan/logic/action/create.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/logic/action/create.py	Tue Jun 07 23:22:22 2011 +0100
@@ -71,7 +71,8 @@
     ## this is added so that the rest controller can make a new location 
     context["id"] = pkg.id
     log.debug('Created object %s' % str(pkg.name))
-    return package_dictize(pkg, context) 
+    if not preview:
+        return package_dictize(pkg, context) 
 
 def resource_create(data_dict, context):
     model = context['model']


--- a/ckan/logic/action/update.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/logic/action/update.py	Tue Jun 07 23:22:22 2011 +0100
@@ -1,5 +1,6 @@
 import logging
 import re
+import datetime
 
 import ckan.authz
 from ckan.plugins import PluginImplementations, IGroupController, IPackageController
@@ -67,8 +68,72 @@
     for group in groups:
         check_access(group, model.Action.EDIT, context)
 
+def _make_latest_rev_active(context, q):
+
+    session = context['model'].Session
+
+    old_current = q.filter_by(current=True).first()
+    if old_current:
+        old_current.current = '0'
+        session.add(old_current)
+
+    latest_rev = q.filter_by(expired_timestamp='9999-12-31').one()
+    latest_rev.current = True
+    if latest_rev.state in ('pending-deleted', 'deleted'):
+        latest_rev.state = 'deleted'
+    else:
+        latest_rev.state = 'active'
+
+    session.add(latest_rev)
+        
+    ##this is just a way to get the latest revision that changed
+    ##in order to timestamp
+    old_latest = context.get('latest_revision_date')
+    if old_latest:
+        if latest_rev.revision_timestamp > old_latest:
+            context['latest_revision_date'] = latest_rev.revision_timestamp
+            context['latest_revision'] = latest_rev.revision_id
+
+def make_latest_pending_package_active(context):
+
+    model = context['model']
+    session = model.Session
+    id = context["id"]
+    pkg = model.Package.get(id)
+
+    check_access(pkg, model.Action.EDIT, context)
+
+    #packages
+    q = session.query(model.PackageRevision).filter_by(id=id)
+    _make_latest_rev_active(context, q)
+
+    #resources
+    for resource in pkg.resource_groups_all[0].resources_all:
+        q = session.query(model.ResourceRevision).filter_by(id=resource.id)
+        _make_latest_rev_active(context, q)
+
+    #tags
+    for tag in pkg.package_tag_all:
+        q = session.query(model.PackageTagRevision).filter_by(id=tag.id)
+        _make_latest_rev_active(context, q)
+
+    #extras
+    for extra in pkg.extras_list:
+        q = session.query(model.PackageExtraRevision).filter_by(id=extra.id)
+        _make_latest_rev_active(context, q)
+
+    latest_revision = context.get('latest_revision')
+    if not latest_revision:
+        return
+
+    q = session.query(model.Revision).filter_by(id=latest_revision)
+    revision = q.first()
+    revision.approved_timestamp = datetime.datetime.now()
+    session.add(revision)
+    model.repo.commit()        
+
+
 def package_update(data_dict, context):
-
     model = context['model']
     user = context['user']
     id = context["id"]
@@ -156,8 +221,6 @@
     comment = data_dict.get('comment', u'')
     return _update_package_relationship(entity, comment, context)
 
-
-
 def group_update(data_dict, context):
 
     model = context['model']


--- a/ckan/logic/schema.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/logic/schema.py	Tue Jun 07 23:22:22 2011 +0100
@@ -38,6 +38,7 @@
         'hash': [ignore_missing, unicode],
         'state': [ignore],
         'position': [ignore],
+        'revision_timestamp': [ignore],
         '__extras': [ignore_missing, extras_unicode_convert, keep_extras],
     }
 
@@ -166,6 +167,7 @@
         'value': [not_missing, unicode],
         'state': [ignore],
         'deleted': [ignore_missing],
+        'revision_timestamp': [ignore],
     }
     return schema
 


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/migration/versions/039_add_expired_id_and_dates.py	Tue Jun 07 23:22:22 2011 +0100
@@ -0,0 +1,220 @@
+from migrate import *
+import uuid
+import datetime
+
+def upgrade(migrate_engine):
+
+    id = uuid.uuid4()
+
+    make_missing_revisions = '''
+
+-- make sure all tables have an entry in the revision_table
+
+insert into revision values ('%(id)s' , '%(timestamp)s', 'admin', 'Admin: make sure every object has a row in a revision table', 'active');
+
+insert into package_tag_revision (id,package_id,tag_id,revision_id,state,continuity_id) select id,package_id,tag_id, '%(id)s' ,state, id from package_tag where package_tag.id not in (select id from package_tag_revision);
+
+insert into resource_revision (id,resource_group_id,url,format,description,position,revision_id,hash,state,extras,continuity_id) select id,resource_group_id,url,format,description,position, '%(id)s' ,hash,state,extras, id from resource where resource.id not in (select id from resource_revision);
+
+insert into group_extra_revision (id,group_id,key,value,state,revision_id,continuity_id) select id,group_id,key,value,state, '%(id)s' , id from group_extra where group_extra.id not in (select id from group_extra_revision);
+
+insert into resource_group_revision (id,package_id,label,sort_order,extras,state,revision_id,continuity_id) select id,package_id,label,sort_order,extras,state, '%(id)s', id from resource_group where resource_group.id not in (select id from resource_group_revision);
+
+insert into package_extra_revision (id,package_id,key,value,revision_id,state,continuity_id) select id,package_id,key,value, '%(id)s',state, id from package_extra where package_extra.id not in (select id from package_extra_revision);
+
+insert into package_relationship_revision (id,subject_package_id,object_package_id,type,comment,revision_id,state,continuity_id) select id,subject_package_id,object_package_id,type,comment, '%(id)s',state, id from package_relationship where package_relationship.id not in (select id from package_relationship_revision);
+                           
+insert into group_revision (id,name,title,description,created,state,revision_id,continuity_id) select id,name,title,description,created,state, '%(id)s', id from "group" where "group".id not in (select id from group_revision);
+
+insert into package_revision (id,name,title,url,notes,license_id,revision_id,version,author,author_email,maintainer,maintainer_email,state,continuity_id) select id,name,title,url,notes,license_id, '%(id)s',version,author,author_email,maintainer,maintainer_email,state, id from package where package.id not in (select id from package_revision);
+
+''' % dict(id=id, timestamp=datetime.datetime.now().isoformat())
+
+
+    update_schema = '''
+ALTER TABLE package_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE package_extra_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE group_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE group_extra_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+
+ALTER TABLE package_group_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE package_tag_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE resource_group_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE resource_revision
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE package_relationship_revision 
+	ADD COLUMN expired_id text,
+	ADD COLUMN revision_timestamp timestamp without time zone,
+	ADD COLUMN expired_timestamp timestamp without time zone,
+	ADD COLUMN current boolean;
+
+ALTER TABLE revision
+	ADD COLUMN approved_timestamp timestamp without time zone;
+
+create table tmp_expired_id(id text, revision_id text, revision_timestamp timestamp, expired_timestamp timestamp, expired_id text);
+create index id_exp on tmp_expired_id(id, revision_id);
+
+--package revision
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from package_revision pr join revision r on pr.revision_id = r.id;
+update package_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_revision set state = 'active-current' where expired_timestamp = '9999-12-31';
+update package_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_package_period on package_revision(revision_timestamp, expired_timestamp, id);
+create index idx_package_expired on package_revision(expired_timestamp);
+
+--package extra revision 
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from package_extra_revision pr join revision r on pr.revision_id = r.id;
+update package_extra_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_extra_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_package_extra_period on package_extra_revision(revision_timestamp, expired_timestamp, id);
+create index idx_package_extra_period_package on package_extra_revision(revision_timestamp, expired_timestamp, package_id);
+create index idx_package_extra_expired on package_extra_revision(expired_timestamp);
+
+--package group revision
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from package_group_revision pr join revision r on pr.revision_id = r.id;
+update package_group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_group_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_package_group_period_package_group on package_group_revision(revision_timestamp, expired_timestamp, package_id, group_id);
+create index idx_package_group_expired on package_group_revision(expired_timestamp);
+
+
+-- package_tags
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from package_tag_revision pr join revision r on pr.revision_id = r.id;
+update package_tag_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_tag_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_period_package_tag on package_tag_revision(revision_timestamp, expired_timestamp, package_id, tag_id);
+create index idx_package_tag_expired on package_tag_revision(expired_timestamp);
+
+-- package relationship
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from package_relationship_revision pr join revision r on pr.revision_id = r.id;
+update package_relationship_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update package_relationship_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_period_package_relationship on package_relationship_revision(revision_timestamp, expired_timestamp, object_package_id, subject_package_id);
+create index idx_package_relationship_expired on package_relationship_revision(expired_timestamp);
+
+-- resource revision
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from resource_revision pr join revision r on pr.revision_id = r.id;
+update resource_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update resource_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_resource_period on resource_revision(revision_timestamp, expired_timestamp, id);
+create index idx_resource_period_resource_group on resource_revision(revision_timestamp, expired_timestamp, resource_group_id);
+create index idx_resource_expired on resource_revision(expired_timestamp);
+
+-- resource group revision;
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from resource_group_revision pr join revision r on pr.revision_id = r.id;
+update resource_group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update resource_group_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_resource_group_period on resource_group_revision(revision_timestamp, expired_timestamp, id);
+create index idx_resource_group_period_package on resource_group_revision(revision_timestamp, expired_timestamp, package_id);
+create index idx_resource_group_expired on resource_group_revision(expired_timestamp);
+
+--group revision;
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from group_revision pr join revision r on pr.revision_id = r.id;
+update group_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update group_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_group_period on group_revision(revision_timestamp, expired_timestamp, id);
+create index idx_group_expired on group_revision(expired_timestamp);
+
+--group extra revision 
+truncate tmp_expired_id;
+insert into tmp_expired_id select pr.id, revision_id, timestamp, lead(timestamp, 1, '9999-12-31') over (partition by pr.id order by timestamp), lead(pr.revision_id) over (partition by pr.id order by timestamp) from group_extra_revision pr join revision r on pr.revision_id = r.id;
+update group_extra_revision pr set revision_timestamp = (select revision_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_timestamp = (select expired_timestamp from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id),
+                               expired_id = (select expired_id from tmp_expired_id tmp where tmp.revision_id = pr.revision_id and tmp.id = pr.id);
+update group_extra_revision set current = '1' where expired_timestamp = '9999-12-31';
+
+create index idx_group_extra_period on group_extra_revision(revision_timestamp, expired_timestamp, id);
+create index idx_group_extra_period_group on group_extra_revision(revision_timestamp, expired_timestamp, group_id);
+create index idx_group_extra_expired on group_extra_revision(expired_timestamp);
+
+drop table tmp_expired_id;
+
+-- change state of revision tables
+
+update revision set state = 'active', approved_timestamp = timestamp;
+'''
+    
+    migrate_engine.execute('begin;  ' + make_missing_revisions + update_schema + ' commit;')
+    
+    for table in ['package', 'resource', 'resource_group', 'package_extra', 
+                  'package_tag', 'package_relationship', 'group', 'group_extra']:
+        count = migrate_engine.execute('''select count(*) from "%s"''' % table).first()[0]
+        revision_expired_id_count = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.expired_id is null''' % (table, table)).first()[0]
+        revision_expired_data_count = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.expired_timestamp = '9999-12-31' ''' % (table, table)).first()[0]
+        revision_current = migrate_engine.execute('''select count(*) from %s_revision where %s_revision.current = '1' ''' % (table, table)).first()[0]
+        assert count == revision_expired_id_count
+        assert count == revision_expired_data_count
+        assert count == revision_current
+
+    


--- a/ckan/model/changeset.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/changeset.py	Tue Jun 07 23:22:22 2011 +0100
@@ -981,7 +981,7 @@
 
     def get_columns(self):
         """Returns the model of the entity attributes."""
-        from ckan.model.core import orm
+        from sqlalchemy import orm
         table = orm.class_mapper(self.object_type).mapped_table
         return table.c
 


--- a/ckan/model/core.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/core.py	Tue Jun 07 23:22:22 2011 +0100
@@ -1,10 +1,12 @@
-from meta import *
+from meta import metadata, mapper
+from sqlalchemy import Column, DateTime, Text, Boolean
 import vdm.sqlalchemy
 
 from domain_object import DomainObject
 
 ## VDM-specific tables
 revision_table = vdm.sqlalchemy.make_revision_table(metadata)
+revision_table.append_column(Column('approved_timestamp', DateTime))
 
 class System(DomainObject):
     
@@ -26,5 +28,13 @@
 Revision = vdm.sqlalchemy.make_Revision(mapper, revision_table)
 
 
+def make_revisioned_table(table):
+    revision_table = vdm.sqlalchemy.make_revisioned_table(table)
+    revision_table.append_column(Column('expired_id', 
+                                 Text))
+    revision_table.append_column(Column('revision_timestamp', DateTime))
+    revision_table.append_column(Column('expired_timestamp', DateTime, 
+                                 default='9999-12-31'))
+    revision_table.append_column(Column('current', Boolean))
+    return revision_table
 
-


--- a/ckan/model/domain_object.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/domain_object.py	Tue Jun 07 23:22:22 2011 +0100
@@ -1,8 +1,9 @@
 import datetime
 
+from sqlalchemy import orm
 from sqlalchemy.util import OrderedDict
 
-from meta import *
+from meta import Session 
 
 class Enum(set):
     '''Simple enumeration


--- a/ckan/model/group.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/group.py	Tue Jun 07 23:22:22 2011 +0100
@@ -10,7 +10,8 @@
 from ckan.model import extension
 
 __all__ = ['group_table', 'Group', 'package_revision_table',
-           'PackageGroup', 'GroupRevision', 'PackageGroupRevision']
+           'PackageGroup', 'GroupRevision', 'PackageGroupRevision',
+           'package_group_revision_table']
 
 package_group_table = Table('package_group', metadata,
     Column('id', UnicodeText, primary_key=True, default=make_uuid),
@@ -19,7 +20,7 @@
     )
     
 vdm.sqlalchemy.make_table_stateful(package_group_table)
-package_group_revision_table = vdm.sqlalchemy.make_revisioned_table(package_group_table)
+package_group_revision_table = make_revisioned_table(package_group_table)
 
 group_table = Table('group', metadata,
     Column('id', UnicodeText, primary_key=True, default=make_uuid),
@@ -30,7 +31,7 @@
     )
 
 vdm.sqlalchemy.make_table_stateful(group_table)
-group_revision_table = vdm.sqlalchemy.make_revisioned_table(group_table)
+group_revision_table = make_revisioned_table(group_table)
 
 
 class PackageGroup(vdm.sqlalchemy.RevisionedObjectMixin,
@@ -123,7 +124,8 @@
     'packages': relation(Package, secondary=package_group_table,
         backref='groups',
         order_by=package_table.c.name
-    )},
+    ),
+},
     extension=[vdm.sqlalchemy.Revisioner(group_revision_table),],
 )
 
@@ -133,7 +135,14 @@
         group_revision_table)
 
 
-mapper(PackageGroup, package_group_table,
+mapper(PackageGroup, package_group_table, properties={
+    'group': relation(Group,
+        backref='package_group_all',
+    ),
+    'package': relation(Package,
+        backref='package_group_all',
+    ),
+},
     extension=[vdm.sqlalchemy.Revisioner(package_group_revision_table),],
 )
 


--- a/ckan/model/group_extra.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/group_extra.py	Tue Jun 07 23:22:22 2011 +0100
@@ -18,7 +18,7 @@
 )
 
 vdm.sqlalchemy.make_table_stateful(group_extra_table)
-group_extra_revision_table = vdm.sqlalchemy.make_revisioned_table(group_extra_table)
+group_extra_revision_table = make_revisioned_table(group_extra_table)
 
 
 class GroupExtra(vdm.sqlalchemy.RevisionedObjectMixin,


--- a/ckan/model/meta.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/meta.py	Tue Jun 07 23:22:22 2011 +0100
@@ -1,7 +1,9 @@
+from datetime import datetime
 """SQLAlchemy Metadata and Session object"""
 from sqlalchemy import MetaData, __version__ as sqav
 from sqlalchemy.orm import scoped_session, sessionmaker
 import sqlalchemy.orm as orm
+from sqlalchemy.orm.session import SessionExtension
 
 # TODO: remove these imports from here and put them in client model modules
 from sqlalchemy import Column, MetaData, Table, types, ForeignKey
@@ -12,6 +14,74 @@
 
 from ckan.model import extension
 
+class CkanSessionExtension(SessionExtension):
+
+    def before_flush(self, session, flush_context, instances):
+        if not hasattr(session, '_object_cache'):
+            session._object_cache= {'new': set(),
+                                    'deleted': set(),
+                                    'changed': set()}
+
+        changed = [obj for obj in session.dirty if 
+            session.is_modified(obj, include_collections=False)]
+
+        session._object_cache['new'].update(session.new)
+        session._object_cache['deleted'].update(session.deleted)
+        session._object_cache['changed'].update(changed)
+
+
+    def before_commit(self, session):
+        session.flush()
+        try:
+            obj_cache = session._object_cache
+            revision = session.revision
+        except AttributeError:
+            return
+
+        new = obj_cache['new']
+        changed = obj_cache['changed']
+        deleted = obj_cache['deleted']
+
+        for obj in new | changed | deleted:
+            
+            if not hasattr(obj, '__revision_class__'):
+                continue
+
+            revision_cls = obj.__revision_class__
+
+            ## when a normal active transaction happens
+            if 'pending' not in obj.state:
+                revision.approved_timestamp = datetime.now()
+                old = session.query(revision_cls).filter_by(
+                    current='1',
+                    id = obj.id
+                ).first()
+                if old:
+                    old.current = '0'
+                    session.add(old)
+
+            q = session.query(revision_cls)
+            q = q.filter_by(expired_timestamp='9999-12-31', id=obj.id)
+            results = q.all()
+
+            for rev_obj in results:
+                if rev_obj.revision_id == revision.id:
+                    rev_obj.revision_timestamp = revision.timestamp
+                    if 'pending' not in obj.state:
+                        rev_obj.current = '1'
+                else:
+                    rev_obj.expired_id = revision.id
+                    rev_obj.expired_timestamp = revision.timestamp
+                session.add(rev_obj)
+
+    def after_commit(self, session):
+        if hasattr(session, '_object_cache'):
+            del session._object_cache
+
+    def after_rollback(self, session):
+        if hasattr(session, '_object_cache'):
+            del session._object_cache
+
 # __all__ = ['Session', 'engine', 'metadata', 'mapper']
 
 # SQLAlchemy database engine. Updated by model.init_model()
@@ -22,14 +92,16 @@
     Session = scoped_session(sessionmaker(
         autoflush=False,
         transactional=True,
-        extension=extension.PluginSessionExtension(),
+        extension=[CkanSessionExtension(),
+                   extension.PluginSessionExtension()],
         ))
 else:
     Session = scoped_session(sessionmaker(
         autoflush=False,
         autocommit=False,
         expire_on_commit=False,
-        extension=extension.PluginSessionExtension(),
+        extension=[CkanSessionExtension(),
+                   extension.PluginSessionExtension()],
         ))
 
 #mapper = Session.mapper


--- a/ckan/model/modification.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/modification.py	Tue Jun 07 23:22:22 2011 +0100
@@ -26,20 +26,6 @@
     implements(ISession, inherit=True)
     observers = PluginImplementations(IDomainObjectModification)
 
-    def before_flush(self, session, flush_context, instances):
-
-        if not hasattr(session, '_object_cache'):
-            session._object_cache= {'new': set(),
-                                    'deleted': set(),
-                                    'changed': set()}
-
-        changed = [obj for obj in session.dirty if 
-            session.is_modified(obj, include_collections=False)]
-
-        session._object_cache['new'].update(session.new)
-        session._object_cache['deleted'].update(session.deleted)
-        session._object_cache['changed'].update(changed)
-
     def before_commit(self, session):
 
         session.flush()
@@ -73,7 +59,6 @@
                         changed_pkgs.add(package)
         for obj in changed_pkgs:
             self.notify(obj, DomainObjectOperation.changed)
-        del session._object_cache
 
 
     def notify(self, entity, operation):


--- a/ckan/model/package.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/package.py	Tue Jun 07 23:22:22 2011 +0100
@@ -2,14 +2,15 @@
 from time import gmtime
 from calendar import timegm
 
-from sqlalchemy.sql import select, and_, union, expression
+from sqlalchemy.sql import select, and_, union, expression, or_
 from sqlalchemy.orm import eagerload_all
+from sqlalchemy import types, Column, Table
 from pylons import config
-from meta import *
+from meta import metadata, Session
 import vdm.sqlalchemy
 
 from types import make_uuid
-from core import *
+from core import make_revisioned_table, Revision, State
 from license import License, LicenseRegister
 from domain_object import DomainObject
 import ckan.misc
@@ -37,7 +38,7 @@
 
 
 vdm.sqlalchemy.make_table_stateful(package_table)
-package_revision_table = vdm.sqlalchemy.make_revisioned_table(package_table)
+package_revision_table = make_revisioned_table(package_table)
 
 ## -------------------
 ## Mapped classes


--- a/ckan/model/package_extra.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/package_extra.py	Tue Jun 07 23:22:22 2011 +0100
@@ -7,7 +7,8 @@
 from types import JsonType
 from ckan.model import extension
 
-__all__ = ['PackageExtra', 'package_extra_table', 'PackageExtraRevision']
+__all__ = ['PackageExtra', 'package_extra_table', 'PackageExtraRevision',
+           'extra_revision_table']
 
 package_extra_table = Table('package_extra', metadata,
     Column('id', UnicodeText, primary_key=True, default=make_uuid),
@@ -18,7 +19,7 @@
 )
 
 vdm.sqlalchemy.make_table_stateful(package_extra_table)
-extra_revision_table= vdm.sqlalchemy.make_revisioned_table(package_extra_table)
+extra_revision_table= make_revisioned_table(package_extra_table)
 
 class PackageExtra(vdm.sqlalchemy.RevisionedObjectMixin,
         vdm.sqlalchemy.StatefulObjectMixin,
@@ -33,6 +34,11 @@
             collection_class=orm.collections.attribute_mapped_collection(u'key'),
             cascade='all, delete, delete-orphan',
             ),
+        ),
+    'package_no_state': orm.relation(Package,
+        backref=orm.backref('extras_list',
+            cascade='all, delete, delete-orphan',
+            ),
         )
     },
     order_by=[package_extra_table.c.package_id, package_extra_table.c.key],


--- a/ckan/model/package_relationship.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/package_relationship.py	Tue Jun 07 23:22:22 2011 +0100
@@ -24,7 +24,7 @@
      )
 
 vdm.sqlalchemy.make_table_stateful(package_relationship_table)
-package_relationship_revision_table = vdm.sqlalchemy.make_revisioned_table(package_relationship_table)
+package_relationship_revision_table = make_revisioned_table(package_relationship_table)
 
 class PackageRelationship(vdm.sqlalchemy.RevisionedObjectMixin,
                           vdm.sqlalchemy.StatefulObjectMixin,


--- a/ckan/model/resource.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/resource.py	Tue Jun 07 23:22:22 2011 +0100
@@ -39,10 +39,10 @@
     )
 
 vdm.sqlalchemy.make_table_stateful(resource_table)
-resource_revision_table = vdm.sqlalchemy.make_revisioned_table(resource_table)
+resource_revision_table = make_revisioned_table(resource_table)
 
 vdm.sqlalchemy.make_table_stateful(resource_group_table)
-resource_group_revision_table = vdm.sqlalchemy.make_revisioned_table(resource_group_table)
+resource_group_revision_table = make_revisioned_table(resource_group_table)
 
 class Resource(vdm.sqlalchemy.RevisionedObjectMixin,
                vdm.sqlalchemy.StatefulObjectMixin,


--- a/ckan/model/tag.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/model/tag.py	Tue Jun 07 23:22:22 2011 +0100
@@ -8,7 +8,7 @@
 from core import *
 
 __all__ = ['tag_table', 'package_tag_table', 'Tag', 'PackageTag',
-           'PackageTagRevision']
+           'PackageTagRevision', 'package_tag_revision_table']
 
 tag_table = Table('tag', metadata,
         Column('id', types.UnicodeText, primary_key=True, default=make_uuid),
@@ -23,7 +23,7 @@
 
 vdm.sqlalchemy.make_table_stateful(package_tag_table)
 # TODO: this has a composite primary key ...
-package_tag_revision_table = vdm.sqlalchemy.make_revisioned_table(package_tag_table)
+package_tag_revision_table = make_revisioned_table(package_tag_table)
 
 class Tag(DomainObject):
     def __init__(self, name=''):
@@ -103,12 +103,15 @@
 mapper(Tag, tag_table, properties={
     'package_tags':relation(PackageTag, backref='tag',
         cascade='all, delete, delete-orphan',
-        )
+        ),
     },
     order_by=tag_table.c.name,
     )
 
 mapper(PackageTag, package_tag_table, properties={
+    'pkg':relation(Package, backref='package_tag_all',
+        cascade='none',
+        )
     },
     order_by=package_tag_table.c.id,
     extension=[vdm.sqlalchemy.Revisioner(package_tag_revision_table),


--- a/ckan/templates/authorization_group/authz.html	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/templates/authorization_group/authz.html	Tue Jun 07 23:22:22 2011 +0100
@@ -9,21 +9,41 @@
       Authorization for authorization group: ${c.authorization_group_name}
     </h2>
 
-    <p py:if="c.message">${c.message}</p>
 
-    <form id="group-authz" action="" method="post">
-      <h3>Update Existing Roles</h3>
-      <table>
-      ${h.literal(c.form)}
-      </table>
+    <h2>Update Existing Roles</h2>
 
-      <h3>Create New User or Authorization Group Roles</h3>
-      ${h.literal(c.new_roles_form)}
-      
-      <br/>
+    <form id="theform" method="POST">
+      ${authz_form_table('theform', c.roles, c.users, c.user_role_dict)}
+      <button type="submit" name="save">
+        Save
+      </button>
+    </form>
 
-      ${h.submit('save', _('Save'))}
+    <h2>Add Roles for Any User</h2>
+
+    <form id="addform" method="POST">
+      ${authz_add_table(c.roles)}
+      <button type="submit" name="add"> Add </button></form>
+
+    <hr/>
+
+    <h2>Existing Roles for Authorization Groups</h2>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save">
+        Save
+      </button>
+    </form>
+
+    <h2>Add Roles for Any Authorization Group</h2>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add"> Add </button>
+    </form>
+
   </div><xi:include href="layout.html" />


--- a/ckan/templates/group/authz.html	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/templates/group/authz.html	Tue Jun 07 23:22:22 2011 +0100
@@ -9,21 +9,40 @@
       Authorization for group: ${c.grouptitle or c.groupname}
     </h2>
 
-    <p py:if="c.message">${c.message}</p>
+    <h2>Update Existing Roles</h2>
 
-    <form id="group-authz" action="" method="post">
-      <h3>Update Existing Roles</h3>
-      <table>
-      ${h.literal(c.form)}
-      </table>
+    <form id="theform" method="POST">
+      ${authz_form_table('theform', c.roles, c.users, c.user_role_dict)}
+      <button type="submit" name="save">
+        Save
+      </button>
+    </form>
 
-      <h3>Create New User Roles</h3>
-      ${h.literal(c.new_roles_form)}
-        
-      <br/>
+    <h2>Add Roles for Any User</h2>
 
-      ${h.submit('save', _('Save'))}
+    <form id="addform" method="POST">
+      ${authz_add_table(c.roles)}
+      <button type="submit" name="add"> Add </button></form>
+
+    <hr/>
+
+    <h2>Existing Roles for Authorization Groups</h2>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save">
+        Save
+      </button>
+    </form>
+
+    <h2>Add Roles for Any Authorization Group</h2>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add"> Add </button>
+    </form>
+
   </div><xi:include href="layout.html" />


--- a/ckan/templates/package/authz.html	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/templates/package/authz.html	Tue Jun 07 23:22:22 2011 +0100
@@ -9,21 +9,40 @@
       Authorization for Data Package: ${c.pkgname}
     </h2>
 
-    <p py:if="c.message">${c.message}</p>
+    <h2>Update Existing Roles</h2>
 
-    <form id="package-authz" action="" method="post">
-      <h3>Update Existing Roles</h3>
-      <table>
-      ${h.literal(c.form)}
-      </table>
+    <form id="theform" method="POST">
+      ${authz_form_table('theform', c.roles, c.users, c.user_role_dict)}
+      <button type="submit" name="save">
+        Save
+      </button>
+    </form>
 
-      <h3>Create New User Roles</h3>
-      ${h.literal(c.new_roles_form)}
-        
-      <br/>
+    <h2>Add Roles for Any User</h2>
 
-      ${h.submit('save', _('Save'))}
+    <form id="addform" method="POST">
+      ${authz_add_table(c.roles)}
+      <button type="submit" name="add"> Add </button></form>
+
+    <hr/>
+
+    <h2>Existing Roles for Authorization Groups</h2>
+
+    <form id="authzgroup_form" method="POST">
+      ${authz_form_group_table('authzgroup_form', c.roles, c.authz_groups, c.authz_groups_role_dict)}
+      <button type="submit" name="authz_save">
+        Save
+      </button>
+    </form>
+
+    <h2>Add Roles for Any Authorization Group</h2>
+
+    <form id="authzgroup_addform" method="POST">
+      ${authz_add_group_table(c.roles)}
+      <button type="submit" name="authz_add"> Add </button>
+    </form>
+
   </div><xi:include href="layout.html" />


--- a/ckan/tests/functional/api/model/test_package.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/tests/functional/api/model/test_package.py	Tue Jun 07 23:22:22 2011 +0100
@@ -260,6 +260,7 @@
         # - url
         self.assert_equal(package.url, self.package_fixture_data['url'])
         # - extras
+
         self.assert_equal(len(package.extras), 4)
         for key, value in {u'key1':u'val1',
                            u'key3':u'val3',


--- a/ckan/tests/functional/test_authorization_group.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/tests/functional/test_authorization_group.py	Tue Jun 07 23:22:22 2011 +0100
@@ -143,251 +143,255 @@
         model.repo.rebuild_db()
         model.Session.remove()
 
-    def test_authzgroups_walkthrough(self):
-        # very long test sequence repeating the series of things I did to
-        # convince myself that the authzgroups system worked as expected,
-        # starting off with the default test data
+
+    ## THIS WALKTHROUGH IS NOW COMPLETELY BROKEN BY THE CHANGES I MADE TO THE AUTHZ PAGE
+
+
+    # def test_authzgroups_walkthrough(self):
+    #     # very long test sequence repeating the series of things I did to
+    #     # convince myself that the authzgroups system worked as expected,
+    #     # starting off with the default test data
         
-        # The first thing to notice is that the authzgroup page:
-        auth_group_index_url = url_for(controller='/authorization_group', action='index')
-        # displays differently for different users.
+    #     # The first thing to notice is that the authzgroup page:
+    #     auth_group_index_url = url_for(controller='/authorization_group', action='index')
+    #     # displays differently for different users.
 
-        def get_page(url, expect_status, username, assert_text=None, error_text=None):
-            res= self.app.get(url, 
-                              status=expect_status, 
-                              extra_environ={'REMOTE_USER': username})
-            if assert_text and assert_text not in res:
-                errorstring = error_text + ' ( "' + assert_text + \
-                              '" not found in result of getting "' + \
-                              url + '" as user "' + username + '" )'
-                assert False, errorstring
-            return res
+    #     def get_page(url, expect_status, username, assert_text=None, error_text=None):
+    #         res= self.app.get(url, 
+    #                           status=expect_status, 
+    #                           extra_environ={'REMOTE_USER': username})
+    #         if assert_text and assert_text not in res:
+    #             errorstring = error_text + ' ( "' + assert_text + \
+    #                           '" not found in result of getting "' + \
+    #                           url + '" as user "' + username + '" )'
+    #             assert False, errorstring
+    #         return res
 
-        # testsysadmin sees the true picture, where the test data contains two groups
-        get_page(auth_group_index_url, 200, 'testsysadmin',
-                'There are <strong>2</strong> authorization groups',
-                'Should be accurate for testsysadmin')
+    #     # testsysadmin sees the true picture, where the test data contains two groups
+    #     get_page(auth_group_index_url, 200, 'testsysadmin',
+    #             'There are <strong>2</strong> authorization groups',
+    #             'Should be accurate for testsysadmin')
 
-        # But if we look at the same page as annafan, who does not have read 
-        # permissions on these groups, we should see neither
-        get_page(auth_group_index_url, 200, 'annafan',
-                'There are <strong>0</strong> authorization groups',
-                'Should lie to annafan about number of groups')
+    #     # But if we look at the same page as annafan, who does not have read 
+    #     # permissions on these groups, we should see neither
+    #     get_page(auth_group_index_url, 200, 'annafan',
+    #             'There are <strong>0</strong> authorization groups',
+    #             'Should lie to annafan about number of groups')
 
-        # There is a page for each group
-        anauthzgroup_url = url_for(controller='/authorization_group', 
-                                   action='read', 
-                                   id='anauthzgroup')
-        # And an edit page
-        anauthzgroup_edit_url = url_for(controller='/authorization_group',
-                                        action='edit', 
-                                        id='anauthzgroup')
+    #     # There is a page for each group
+    #     anauthzgroup_url = url_for(controller='/authorization_group', 
+    #                                action='read', 
+    #                                id='anauthzgroup')
+    #     # And an edit page
+    #     anauthzgroup_edit_url = url_for(controller='/authorization_group',
+    #                                     action='edit', 
+    #                                     id='anauthzgroup')
 
-        # testsysadmin should be able to see this, and check that there are no members
-        get_page(anauthzgroup_url, 200, 'testsysadmin',
-                 'There are 0 users in this',
-                 'should be no users in anauthzgroup')
+    #     # testsysadmin should be able to see this, and check that there are no members
+    #     get_page(anauthzgroup_url, 200, 'testsysadmin',
+    #              'There are 0 users in this',
+    #              'should be no users in anauthzgroup')
 
-        # now testsysadmin adds annafan to anauthzgroup via the edit page
-        res = get_page(anauthzgroup_edit_url, 200, 'testsysadmin')
-        group_edit_form = res.forms['group-edit']
-        group_edit_form['AuthorizationGroupUser--user_name'] = u'annafan'
-        submit_res = group_edit_form.submit('save',
-                                      extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     # now testsysadmin adds annafan to anauthzgroup via the edit page
+    #     res = get_page(anauthzgroup_edit_url, 200, 'testsysadmin')
+    #     group_edit_form = res.forms['group-edit']
+    #     group_edit_form['AuthorizationGroupUser--user_name'] = u'annafan'
+    #     submit_res = group_edit_form.submit('save',
+    #                                   extra_environ={'REMOTE_USER': 'testsysadmin'})
 
-        # adding a user to a group should both make her a member, and give her
-        # read permission on the group. We'll check those things have actually
-        # happened by looking directly in the model.
-        anauthzgroup = model.AuthorizationGroup.by_name('anauthzgroup')
-        anauthzgroup_users = [x.name for x in anauthzgroup.users]
-        anauthzgroup_user_roles = [(x.user.name, x.role) for x in anauthzgroup.roles if x.user]
-        assert anauthzgroup_users == [u'annafan'], \
-                                         'anauthzgroup should contain annafan (only)'
-        assert anauthzgroup_user_roles == [(u'annafan', u'reader')],\
-                                         'annafan should be a reader'
+    #     # adding a user to a group should both make her a member, and give her
+    #     # read permission on the group. We'll check those things have actually
+    #     # happened by looking directly in the model.
+    #     anauthzgroup = model.AuthorizationGroup.by_name('anauthzgroup')
+    #     anauthzgroup_users = [x.name for x in anauthzgroup.users]
+    #     anauthzgroup_user_roles = [(x.user.name, x.role) for x in anauthzgroup.roles if x.user]
+    #     assert anauthzgroup_users == [u'annafan'], \
+    #                                      'anauthzgroup should contain annafan (only)'
+    #     assert anauthzgroup_user_roles == [(u'annafan', u'reader')],\
+    #                                      'annafan should be a reader'
 
-        # Since annafan has been added to anauthzgroup, which is an admin on
-        # anotherauthzgroup, she should now be able to see both the groups.
-        get_page(auth_group_index_url, 200, 'annafan',
-                 'There are <strong>2</strong> auth',
-                 "annafan should now be able to see both groups")
+    #     # Since annafan has been added to anauthzgroup, which is an admin on
+    #     # anotherauthzgroup, she should now be able to see both the groups.
+    #     get_page(auth_group_index_url, 200, 'annafan',
+    #              'There are <strong>2</strong> auth',
+    #              "annafan should now be able to see both groups")
 
-        # When annafan looks at the page for anauthzgroup now
-        # She should see that there's one user:
-        get_page(anauthzgroup_url, 200,'annafan',
-                       'There are 1 users in this', 
-                       'annafan should be able to see the list of members')
+    #     # When annafan looks at the page for anauthzgroup now
+    #     # She should see that there's one user:
+    #     get_page(anauthzgroup_url, 200,'annafan',
+    #                    'There are 1 users in this', 
+    #                    'annafan should be able to see the list of members')
 
-        # Which is her, so her name should be in there somewhere:
-        get_page(anauthzgroup_url, 200,'annafan',
-                       'annafan', 
-                       'annafan should be listed as a member')
+    #     # Which is her, so her name should be in there somewhere:
+    #     get_page(anauthzgroup_url, 200,'annafan',
+    #                    'annafan', 
+    #                    'annafan should be listed as a member')
 
-        # But she shouldn't be able to see the edit page for that group.  
+    #     # But she shouldn't be able to see the edit page for that group.  
 
-        # The behaviour of the test setup here is a bit weird, since in the
-        # browser she gets redirected to the login page, but from these tests,
-        # she just gets a 401, with no apparent redirect.  Sources inform me
-        # that this is normal, and to do with repoze being in the application
-        # stack but not in the test stack.
-        get_page(anauthzgroup_edit_url, 401, 'annafan',
-                 'not authorized to edit', 
-                 'annafan should not be able to edit the list of members')
-        # this behaviour also means that we get a flash message left over, which appears on 
-        # whatever the next page is.
+    #     # The behaviour of the test setup here is a bit weird, since in the
+    #     # browser she gets redirected to the login page, but from these tests,
+    #     # she just gets a 401, with no apparent redirect.  Sources inform me
+    #     # that this is normal, and to do with repoze being in the application
+    #     # stack but not in the test stack.
+    #     get_page(anauthzgroup_edit_url, 401, 'annafan',
+    #              'not authorized to edit', 
+    #              'annafan should not be able to edit the list of members')
+    #     # this behaviour also means that we get a flash message left over, which appears on 
+    #     # whatever the next page is.
   
-        # I'm going to assert that behaviour here, just to note it. It's most
-        # definitely not required functionality!  We'll do a dummy fetch of the
-        # main page for anauthzgroup, which will have the errant flash message
-        get_page(anauthzgroup_url, 200, 'annafan',
-                 'not authorized to edit', 
-                 'flash message should carry over to next fetch')
+    #     # I'm going to assert that behaviour here, just to note it. It's most
+    #     # definitely not required functionality!  We'll do a dummy fetch of the
+    #     # main page for anauthzgroup, which will have the errant flash message
+    #     get_page(anauthzgroup_url, 200, 'annafan',
+    #              'not authorized to edit', 
+    #              'flash message should carry over to next fetch')
 
-        # But if we do the dummy fetch twice, the flash message should have gone
-        res = get_page(anauthzgroup_url, 200, 'annafan')
-        assert 'not authorized to edit' not in res, 'flash message should have gone'
+    #     # But if we do the dummy fetch twice, the flash message should have gone
+    #     res = get_page(anauthzgroup_url, 200, 'annafan')
+    #     assert 'not authorized to edit' not in res, 'flash message should have gone'
 
-        # Since annafan is now a member of anauthzgroup, she should have admin privileges
-        # on anotherauthzgroup
-        anotherauthzgroup_edit_url = url_for(controller='/authorization_group', 
-                                             action='edit', 
-                                             id='anotherauthzgroup')
+    #     # Since annafan is now a member of anauthzgroup, she should have admin privileges
+    #     # on anotherauthzgroup
+    #     anotherauthzgroup_edit_url = url_for(controller='/authorization_group', 
+    #                                          action='edit', 
+    #                                          id='anotherauthzgroup')
 
-        # Which means that she can go to the edit page:
-        res = get_page(anotherauthzgroup_edit_url, 200, 'annafan',
-                 'There are no users',
-                 "There shouldn't be any users in anotherauthzgroup")
+    #     # Which means that she can go to the edit page:
+    #     res = get_page(anotherauthzgroup_edit_url, 200, 'annafan',
+    #              'There are no users',
+    #              "There shouldn't be any users in anotherauthzgroup")
 
-        # And change the name of the group
-        # The group name editing box has a name dependent on the id of the group,
-        # so we find it by regex in the page.
-        import re
-        p = re.compile('AuthorizationGroup-.*-name')
-        groupnamebox = [ v for k,v in res.forms['group-edit'].fields.items() if p.match(k)][0][0]
-        groupnamebox.value = 'annasauthzgroup'
-        res = res.forms['group-edit'].submit('save', extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow()
+    #     # And change the name of the group
+    #     # The group name editing box has a name dependent on the id of the group,
+    #     # so we find it by regex in the page.
+    #     import re
+    #     p = re.compile('AuthorizationGroup-.*-name')
+    #     groupnamebox = [ v for k,v in res.forms['group-edit'].fields.items() if p.match(k)][0][0]
+    #     groupnamebox.value = 'annasauthzgroup'
+    #     res = res.forms['group-edit'].submit('save', extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow()
         
-        ## POTENTIAL BUG:
-        # note that she could change the name of the group to anauthzgroup,
-        # which causes problems due to the name collision. This should be
-        # guarded against.
+    #     ## POTENTIAL BUG:
+    #     # note that she could change the name of the group to anauthzgroup,
+    #     # which causes problems due to the name collision. This should be
+    #     # guarded against.
 
 
-        # annafan should still be able to see the admin and edit pages of the
-        # newly renamed group by virtue of being a member of anauthzgroup
-        annasauthzgroup_authz_url = url_for(controller='/authorization_group', 
-                                            action='authz', 
-                                            id='annasauthzgroup')
+    #     # annafan should still be able to see the admin and edit pages of the
+    #     # newly renamed group by virtue of being a member of anauthzgroup
+    #     annasauthzgroup_authz_url = url_for(controller='/authorization_group', 
+    #                                         action='authz', 
+    #                                         id='annasauthzgroup')
 
-        annasauthzgroup_edit_url = url_for(controller='/authorization_group', 
-                                            action='edit', 
-                                            id='annasauthzgroup')
+    #     annasauthzgroup_edit_url = url_for(controller='/authorization_group', 
+    #                                         action='edit', 
+    #                                         id='annasauthzgroup')
 
 
-        res = get_page(annasauthzgroup_authz_url, 200, 'annafan',
-                       'Authorization for authorization group: annasauthzgroup',
-                       'should be authz page')
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'annafan',
+    #                    'Authorization for authorization group: annasauthzgroup',
+    #                    'should be authz page')
 
-        # annafan has the power to remove anauthzgroup's admin role on her group
-        # The button to remove that role is a link, rather than a submit. I
-        # assume there is a better way to do this than searching by regex, but I
-        # can't find it.
-        import re
-        delete_links = re.compile('<a href="(.*)" title="delete">').findall(res.body)
-        assert len(delete_links) == 1, "There should only be one delete link here"
-        delete_link = delete_links[0]
+    #     # annafan has the power to remove anauthzgroup's admin role on her group
+    #     # The button to remove that role is a link, rather than a submit. I
+    #     # assume there is a better way to do this than searching by regex, but I
+    #     # can't find it.
+    #     import re
+    #     delete_links = re.compile('<a href="(.*)" title="delete">').findall(res.body)
+    #     assert len(delete_links) == 1, "There should only be one delete link here"
+    #     delete_link = delete_links[0]
 
-        # Paranoid check, try to follow link without credentials. Should be redirected.
-        res = self.app.get(delete_link, status=302)
-        res = res.follow()
-        assert 'Not authorized to edit authorization for group' in res,\
-                "following link without credentials should result in redirection to login page"
+    #     # Paranoid check, try to follow link without credentials. Should be redirected.
+    #     res = self.app.get(delete_link, status=302)
+    #     res = res.follow()
+    #     assert 'Not authorized to edit authorization for group' in res,\
+    #             "following link without credentials should result in redirection to login page"
 
-        # Now follow it as annafan, which should work.
-        get_page(delete_link, 200,'annafan',
-                 "Deleted role 'admin' for authorization group 'anauthzgroup'",
-                 "Page should mention the deleted role")
+    #     # Now follow it as annafan, which should work.
+    #     get_page(delete_link, 200,'annafan',
+    #              "Deleted role 'admin' for authorization group 'anauthzgroup'",
+    #              "Page should mention the deleted role")
         
-        # Trying it a second time should fail since she's now not an admin.
-        get_page(delete_link, 401,'annafan')
+    #     # Trying it a second time should fail since she's now not an admin.
+    #     get_page(delete_link, 401,'annafan')
  
-        # No one should now have any rights on annasauthzgroup, including
-        # annafan herself.  So this should fail too. Again, get a 401 error
-        # here, but in the browser we get redirected if we try.
-        get_page(annasauthzgroup_authz_url, 401,'annafan')
+    #     # No one should now have any rights on annasauthzgroup, including
+    #     # annafan herself.  So this should fail too. Again, get a 401 error
+    #     # here, but in the browser we get redirected if we try.
+    #     get_page(annasauthzgroup_authz_url, 401,'annafan')
 
-        # testsysadmin can put her back. 
-        # It appears that the select boxes on this form need to be set by id
-        anauthzgroupid = model.AuthorizationGroup.by_name(u'anauthzgroup').id
-        annafanid = model.User.by_name(u'annafan').id
+    #     # testsysadmin can put her back. 
+    #     # It appears that the select boxes on this form need to be set by id
+    #     anauthzgroupid = model.AuthorizationGroup.by_name(u'anauthzgroup').id
+    #     annafanid = model.User.by_name(u'annafan').id
 
-        # first try to make both anauthzgroup and annafan editors. This should fail.
-        res = get_page(annasauthzgroup_authz_url,200, 'testsysadmin')
-        gaf= res.forms['group-authz']
-        gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
-        gaf['AuthorizationGroupRole--role'] = 'editor'
-        gaf['AuthorizationGroupRole--user_id'] = annafanid
-        res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
-        assert 'Please select either a user or an authorization group, not both.' in res,\
-             'request should fail if you change both user and authz group'
+    #     # first try to make both anauthzgroup and annafan editors. This should fail.
+    #     res = get_page(annasauthzgroup_authz_url,200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
+    #     gaf['AuthorizationGroupRole--role'] = 'editor'
+    #     gaf['AuthorizationGroupRole--user_id'] = annafanid
+    #     res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert 'Please select either a user or an authorization group, not both.' in res,\
+    #          'request should fail if you change both user and authz group'
 
-        # settle for just doing one at a time. make anauthzgroup an editor
-        res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
-        gaf= res.forms['group-authz']
-        gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
-        gaf['AuthorizationGroupRole--role'] = 'editor'
-        res = gaf.submit('save',status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
-        assert "Added role 'editor' for authorization group 'anauthzgroup'" in res, \
-                                                            "no flash message"
+    #     # settle for just doing one at a time. make anauthzgroup an editor
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--authorized_group_id'] = anauthzgroupid
+    #     gaf['AuthorizationGroupRole--role'] = 'editor'
+    #     res = gaf.submit('save',status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert "Added role 'editor' for authorization group 'anauthzgroup'" in res, \
+    #                                                         "no flash message"
 
-        # and make annafan a reader 
-        res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
-        gaf= res.forms['group-authz']
-        gaf['AuthorizationGroupRole--user_id'] = annafanid
-        gaf['AuthorizationGroupRole--role'] = 'reader'
-        res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
-        assert "Added role 'reader' for user 'annafan'" in res, "no flash message"
+    #     # and make annafan a reader 
+    #     res = get_page(annasauthzgroup_authz_url, 200, 'testsysadmin')
+    #     gaf= res.forms['group-authz']
+    #     gaf['AuthorizationGroupRole--user_id'] = annafanid
+    #     gaf['AuthorizationGroupRole--role'] = 'reader'
+    #     res = gaf.submit('save', status=200, extra_environ={'REMOTE_USER': 'testsysadmin'})
+    #     assert "Added role 'reader' for user 'annafan'" in res, "no flash message"
 
-        # annafan should now be able to add her friends to annasauthzgroup
-        res = get_page(annasauthzgroup_edit_url, 200, 'annafan')
-        res.forms['group-edit']['AuthorizationGroupUser--user_name']='tester'
-        # this follows the post/redirect/get pattern
-        res = res.forms['group-edit'].submit('save', status=302,
-                                             extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
-        # and she gets redirected to the group view page
-        assert 'tester' in res, 'tester not added?'
+    #     # annafan should now be able to add her friends to annasauthzgroup
+    #     res = get_page(annasauthzgroup_edit_url, 200, 'annafan')
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='tester'
+    #     # this follows the post/redirect/get pattern
+    #     res = res.forms['group-edit'].submit('save', status=302,
+    #                                          extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     # and she gets redirected to the group view page
+    #     assert 'tester' in res, 'tester not added?'
  
-        # she needs to do them one by one
-        res = get_page(annasauthzgroup_edit_url, 200, 'annafan',
-                       'tester', 
-                       'tester not in edit form')
-        res.forms['group-edit']['AuthorizationGroupUser--user_name']='russianfan'        
-        res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     # she needs to do them one by one
+    #     res = get_page(annasauthzgroup_edit_url, 200, 'annafan',
+    #                    'tester', 
+    #                    'tester not in edit form')
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='russianfan'        
+    #     res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
         
-        # and finally adds herself
-        res = self.app.get(annasauthzgroup_edit_url, status=200, extra_environ={'REMOTE_USER': 'annafan'})
-        assert 'russianfan' in res, 'russianfan not added?'
-        res.forms['group-edit']['AuthorizationGroupUser--user_name']='annafan'        
-        res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
-        assert 'annafan' in res, 'annafan not added?'
+    #     # and finally adds herself
+    #     res = self.app.get(annasauthzgroup_edit_url, status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     assert 'russianfan' in res, 'russianfan not added?'
+    #     res.forms['group-edit']['AuthorizationGroupUser--user_name']='annafan'        
+    #     res = res.forms['group-edit'].submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     assert 'annafan' in res, 'annafan not added?'
 
-        # finally let's check that annafan can create a completely new authzgroup
-        new_authzgroup_url = url_for(controller='/authorization_group', action='new')
-        res = get_page(new_authzgroup_url, 200,'annafan',
-                       'New Authorization Group', 
-                       "wrong page?")
-        gef = res.forms['group-edit']
-        gef['AuthorizationGroup--name']="newgroup"
-        gef['AuthorizationGroupUser--user_name'] = "russianfan"
-        res = gef.submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
-        #post/redirect/get
-        res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
+    #     # finally let's check that annafan can create a completely new authzgroup
+    #     new_authzgroup_url = url_for(controller='/authorization_group', action='new')
+    #     res = get_page(new_authzgroup_url, 200,'annafan',
+    #                    'New Authorization Group', 
+    #                    "wrong page?")
+    #     gef = res.forms['group-edit']
+    #     gef['AuthorizationGroup--name']="newgroup"
+    #     gef['AuthorizationGroupUser--user_name'] = "russianfan"
+    #     res = gef.submit('save', status=302, extra_environ={'REMOTE_USER': 'annafan'})
+    #     #post/redirect/get
+    #     res = res.follow(status=200, extra_environ={'REMOTE_USER': 'annafan'})
         
-        assert 'newgroup'   in res, "should have redirected to the newgroup page"
-        assert 'russianfan' in res, "no russianfan"
-        assert 'There are 1 users in this authorization group' in res, "missing text"
+    #     assert 'newgroup'   in res, "should have redirected to the newgroup page"
+    #     assert 'russianfan' in res, "no russianfan"
+    #     assert 'There are 1 users in this authorization group' in res, "missing text"
 


--- a/ckan/tests/functional/test_authz.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/tests/functional/test_authz.py	Tue Jun 07 23:22:22 2011 +0100
@@ -188,6 +188,7 @@
         res = func(offset, params=postparams,
                    extra_environ=environ,
                    expect_errors=True)
+        
         tests = {}
         tests['str_required (%s)' % str_required_in_response] = bool(str_required_in_response in res)
         tests['error string'] = bool('error' not in res)
@@ -313,7 +314,7 @@
         cls.groupreader = model.User.by_name(u'groupreader')
         cls.mrloggedin = model.User.by_name(name=u'mrloggedin')
         cls.visitor = model.User.by_name(name=model.PSEUDO_USER__VISITOR)
-        
+
     # Tests numbered by the use case
 
     def test_14_visitor_reads_stopped(self):


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/test_edit_authz.py	Tue Jun 07 23:22:22 2011 +0100
@@ -0,0 +1,420 @@
+import ckan.model as model
+from ckan.tests import *
+from ckan.lib.base import *
+import ckan.authz as authz
+
+
+def check_and_set_checkbox(theform, user, role, should_be, set_to):
+   '''Given an authz form, find the checkbox associated with the strings user and role,
+   assert that it's in the state 'should_be', and set it to 'set_to' '''
+   user_role_string = '%s$%s' % (user, role)
+   checkboxes = [x for x in theform.fields[user_role_string] \
+                                   if x.__class__.__name__ == 'Checkbox']
+
+   assert(len(checkboxes)==1), \
+        "there should only be one checkbox for %s/%s" % (user, role)
+   checkbox = checkboxes[0]
+
+   #checkbox should be unticked
+   assert checkbox.checked==should_be, \
+                 "%s/%s checkbox in unexpected state" % (user, role)
+
+   #tick or untick the box and return the form
+   checkbox.checked=set_to
+   return theform
+
+
+class TestEditAuthz(TestController):
+    @classmethod
+    def setup_class(self):
+        # for the authorization editing tests we set up test data so:
+        # three users, sysadmin , administrator, and another
+        # one authzgroup, one group, one package
+        # and administrator is admin on all three
+        # one extra authzgroup, authzgroup2, with no permissions to start with
+        model.repo.init_db()
+        model.repo.new_revision()
+        
+        self.sysadmin = 'sysadmin'
+        sysadmin_user = model.User(name=unicode(self.sysadmin))
+        self.admin = 'administrator'
+        admin_user = model.User(name=unicode(self.admin))
+        self.another = 'another'
+        another_user = model.User(name=unicode(self.another))
+        self.authzgroup = 'authzgroup'
+        authzgroup = model.AuthorizationGroup(name=unicode(self.authzgroup))
+        self.group = 'group'
+        group = model.Group(name=unicode(self.group))
+        self.authzgroup2 = 'authzgroup2'
+        authzgroup2 = model.AuthorizationGroup(name=unicode(self.authzgroup2))
+
+
+        for obj in sysadmin_user, admin_user, another_user, authzgroup, group, authzgroup2:
+            model.Session.add(obj)
+
+        model.add_user_to_role(sysadmin_user, model.Role.ADMIN, model.System())
+        model.repo.commit_and_remove()
+
+        model.repo.new_revision()
+
+        self.pkg = u'package'
+        pkg = model.Package(name=self.pkg)
+        model.Session.add(pkg)
+
+        admin_user = model.User.by_name(unicode(self.admin))
+        assert admin_user
+
+        # setup all three authorization objects to have logged in and visitor as editors, and the admin as admin
+        model.setup_user_roles(pkg, ['editor'], ['editor'], [admin_user])
+        model.setup_user_roles(authzgroup, ['editor'], ['editor'], [admin_user])
+        model.setup_user_roles(group, ['editor'], ['editor'], [admin_user])
+
+        model.repo.commit_and_remove()
+
+    @classmethod
+    def teardown_class(self):
+        model.repo.rebuild_db()
+
+    def test_access_to_authz(self):
+        #for each of the three authz pages, check that the access permissions work correctly
+        for (c,i) in [('package', self.pkg),('group', self.group),('authorization_group', self.authzgroup)]:
+            offset = url_for(controller=c, action='authz', id=i)
+
+            # attempt to access the authz pages without credentials should result in getting redirected to the login page
+            res = self.app.get(offset, status=[302])
+            res = res.follow()
+            assert res.request.url.startswith('/user/login')
+
+            # for an ordinary user, it should result in access denied
+            # which is weird, because in the app proper he'd get redirected too.
+            # it behaves differently in the test setup, but this is a known strangeness.
+            res = self.app.get(offset, status=[401], extra_environ={'REMOTE_USER':self.another})
+
+            # going there as the package administrator or system administrator should be fine
+            for u in [self.admin,self.sysadmin]:
+                res = self.app.get(offset, status=[200], extra_environ={'REMOTE_USER':u})
+                # the name of the object should appear in the page
+                assert i in res
+                assert "Authorization for" in res
+
+
+    def roles_list(self, authzobj):
+        # get a list of username/roles for a given authorizable object
+        list = [ (r.user.name, r.role) for r in authzobj.roles if r.user]
+        list.extend([(r.authorized_group.name, r.role) for r in authzobj.roles if r.authorized_group])
+        return list
+
+    # get the users/roles for the specific objects created in our test data
+    def package_roles(self):
+        return self.roles_list(model.Package.by_name(self.pkg))
+
+    def group_roles(self):
+        return self.roles_list(model.Group.by_name(self.group))
+
+    def authzgroup_roles(self):
+        return self.roles_list(model.AuthorizationGroup.by_name(self.authzgroup))
+
+    # check that the authz page for each object contains certain key strings
+    def test_2_read_ok(self):
+        for (c,i,m) in [('package', self.pkg, self.package_roles),\
+                        ('group', self.group, self.group_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles)]:
+            offset = url_for(controller=c, action='authz', id=i)
+            res = self.app.get(offset, extra_environ={'REMOTE_USER': self.admin})
+            assert i in res
+            assert "Authorization for" in res
+
+            # all the package's users and roles should appear in tables
+            assert '<tr' in res
+            for (user,role) in m():
+                assert user in res
+                assert role in res
+
+
+    def assert_roles_to_be(self, actual_roles_list, expected_roles_list):
+        # given an actual and an expected list of user/roles, assert that they're as expected, 
+        # modulo ordering.
+        ok = ( len(actual_roles_list) == len(expected_roles_list) )
+        for r in actual_roles_list:
+           if not r in expected_roles_list:
+               ok = False
+        if not ok:
+           print "expected roles: ", expected_roles_list
+           print "actual roles: ", actual_roles_list
+           assert False, "roles not as expected"
+
+
+    # check that when we change one role and add another, that both the checkbox states and the database
+    # change as we expect them to, and that the roles on the other objects don't get changed by accident.
+    # this should guard against certain errors which might be introduced by copy and pasting the controller code.
+    def change_roles(self, user):
+
+        normal_roles=[('administrator', 'admin'),
+                      ('visitor', 'editor'),
+                      ('logged_in', 'editor')]
+
+        changed_roles=[('administrator', 'admin'),
+                       ('visitor', 'editor'),
+                       ('visitor', 'reader'),
+                       ('logged_in', 'admin')]
+
+        # loop variables here are the controller string, the name of the object we're changing, and three functions, 
+        # the first fn gets the roles which we'd like to change, and the other two get the roles which we'd like to stay the same.
+        for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
+
+            # load authz page
+            offset = url_for(controller=c, action='authz', id=i)
+            res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+            assert i in res
+
+            self.assert_roles_to_be(var(), normal_roles)
+            self.assert_roles_to_be(const1(), normal_roles)
+            self.assert_roles_to_be(const2(), normal_roles)
+
+            #admin makes visitor a reader and logged in an admin
+            form = res.forms['theform']
+            check_and_set_checkbox(form, u'visitor', u'reader', False, True)
+            check_and_set_checkbox(form, u'logged_in', u'admin', False, True)
+            check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+            check_and_set_checkbox(form, u'logged_in', u'editor', True, False)
+
+            res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+            # ensure db was changed
+            self.assert_roles_to_be(var(), changed_roles)
+            self.assert_roles_to_be(const1(), normal_roles)
+            self.assert_roles_to_be(const2(), normal_roles)
+
+            # ensure rerender of form is changed
+            offset = url_for(controller=c, action='authz', id=i)
+            res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+            assert i in res
+
+            # check that the checkbox states are what we think they should be
+            # and put things back how they were.
+            form = res.forms['theform']
+            check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+            check_and_set_checkbox(form, u'logged_in', u'admin', True, False)
+            check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+            check_and_set_checkbox(form, u'logged_in', u'editor', False, True)
+            res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+            # ensure db was changed
+            self.assert_roles_to_be(var(), normal_roles)
+            self.assert_roles_to_be(const1(), normal_roles)
+            self.assert_roles_to_be(const2(), normal_roles)
+
+
+    # do the change roles both as package/group/authzgroup admin, and also as sysadmin.
+    def test_3_admin_changes_role(self):
+        self.change_roles(self.admin)
+
+    def test_3_sysadmin_changes_role(self):
+        self.change_roles(self.sysadmin)
+
+    def delete_role_as(self,user):
+
+        normal_roles=[('administrator', 'admin'),
+                      ('visitor', 'editor'),
+                      ('logged_in', 'editor')]
+
+        changed_roles=[('administrator', 'admin'),
+                       ('logged_in', 'editor')]
+
+        changed_roles2=[('administrator', 'admin'),
+                        ('visitor', 'reader'),
+                        ('logged_in', 'editor')]
+
+
+        # loop variables here are the controller string, the name of the object we're changing, and three functions, 
+        # the first fn gets the roles which we'd like to change, and the other two get the roles which we'd like to stay the same.
+        for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
+
+           # get the authz page, check that visitor's in there
+           # remove visitor's role on the package
+           # re-get the page and make sure that visitor's not in there at all
+           offset = url_for(controller=c, action='authz', id=i)
+           res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+           assert self.pkg in res
+
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           #admin removes visitor's only role
+           form = res.forms['theform']
+           check_and_set_checkbox(form, u'visitor', u'editor', True, False)
+           res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+           # ensure db was changed
+           self.assert_roles_to_be(var(), changed_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # ensure rerender of form is changed
+           offset = url_for(controller=c, action='authz', id=i)
+           res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+           assert self.pkg in res
+
+           assert 'visitor' not in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           # check that the checkbox states are what we think they should be
+           form = res.forms['theform']
+           check_and_set_checkbox(form, u'logged_in', u'editor', True, True)
+           check_and_set_checkbox(form, u'administrator', u'admin', True, True)
+
+           # now we should add visitor back in, let's make him a reader
+           form = res.forms['addform']
+           form.fields['new_user_name'][0].value='visitor'
+           checkbox = [x for x in form.fields['reader'] \
+                         if x.__class__.__name__ == 'Checkbox'][0]
+           # check it's currently unticked
+           assert checkbox.checked == False
+           # tick it and submit
+           checkbox.checked=True
+           res = form.submit('add', extra_environ={'REMOTE_USER':user})
+           assert "User Added" in res, "don't see flash message"
+
+           # check that the page contains strings for everyone
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           # check that the roles in the db are back to normal
+           self.assert_roles_to_be(var(), changed_roles2)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # now change him back to being an editor
+           form = res.forms['theform']
+           check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+           check_and_set_checkbox(form, u'visitor', u'editor', False, True)
+           res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+           # check that the page contains strings for everyone
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+
+           # check that the roles in the db are back to normal
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+
+
+    def test_4_admin_deletes_role(self):
+        self.delete_role_as(self.admin)
+
+    def test_4_sysadmin_deletes_role(self):
+        self.delete_role_as(self.sysadmin)
+
+
+    # now a version of the above tests dealing with permissions assigned to authzgroups 
+    # (as opposed to on authzgroups)
+    def add_change_delete_authzgroup_as(self, user):
+
+        normal_roles=[('administrator', 'admin'),
+                      ('visitor', 'editor'),
+                      ('logged_in', 'editor')]
+
+        changed_roles=[('authzgroup2', 'admin'),
+                       ('administrator', 'admin'),
+                       ('visitor', 'editor'),
+                       ('logged_in', 'editor')]
+
+        changed_roles_2=[('authzgroup2', 'editor'),
+                         ('administrator', 'admin'),
+                         ('visitor', 'editor'),
+                         ('logged_in', 'editor')]
+
+        for (c,i,var,const1,const2) in [('package', self.pkg, self.package_roles, self.group_roles, self.authzgroup_roles),\
+                        ('group', self.group, self.group_roles, self.package_roles, self.authzgroup_roles),\
+                        ('authorization_group', self.authzgroup, self.authzgroup_roles, self.package_roles, self.group_roles)]:
+
+           # get the authz page, check that it contains the object name
+           offset = url_for(controller=c, action='authz', id=i)
+           res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+           assert i in res
+
+           # check the state of the database
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # and that corresponding user strings are in the authz page
+           # particularly that authzgroup2 isn't there (yet)
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+           assert 'authzgroup2' not in res
+ 
+           # add authzgroup2 as an admin
+           form = res.forms['authzgroup_addform']
+           form.fields['new_user_name'][0].value='authzgroup2'
+           checkbox = [x for x in form.fields['admin'] \
+                         if x.__class__.__name__ == 'Checkbox'][0]
+           # check the checkbox is currently unticked
+           assert checkbox.checked == False
+           # tick it and submit
+           checkbox.checked=True
+           res = form.submit('authz_add', extra_environ={'REMOTE_USER':user})
+           assert "Authorization Group Added" in res, "don't see flash message"
+
+           # examine the new page for user names/authzgroup names
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+           assert 'authzgroup2' in res
+
+           # and ensure that the database has changed as expected
+           self.assert_roles_to_be(var(), changed_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+ 
+           # check that the checkbox states are what we think they should be
+           # and change authzgroup2 from admin to editor
+           form = res.forms['authzgroup_form']
+           check_and_set_checkbox(form, u'authzgroup2', u'editor', False, True)
+           check_and_set_checkbox(form, u'authzgroup2', u'admin', True, False)
+           res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+           #check database has changed.
+           self.assert_roles_to_be(var(), changed_roles_2)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+
+           # now remove authzgroup2 entirely
+           form = res.forms['authzgroup_form']
+           check_and_set_checkbox(form, u'authzgroup2', u'editor', True, False)
+           check_and_set_checkbox(form, u'authzgroup2', u'admin', False, False)
+           res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+           #check database is back to normal
+           self.assert_roles_to_be(var(), normal_roles)
+           self.assert_roles_to_be(const1(), normal_roles)
+           self.assert_roles_to_be(const2(), normal_roles)
+
+           # and that page contains only the expected strings
+           assert 'visitor' in res
+           assert 'administrator' in res
+           assert 'logged_in' in res
+           assert 'authzgroup2' not in res
+
+
+    def test_5_admin_changes_adds_deletes_authzgroup(self):
+        self.add_change_delete_authzgroup_as(self.admin)
+
+    def test_5_sysadmin_changes_adds_deletes_authzgroup(self):
+        self.add_change_delete_authzgroup_as(self.sysadmin)


--- a/ckan/tests/functional/test_group_edit_authz.py	Tue May 24 22:17:14 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-import ckan.model as model
-from ckan.tests import *
-from ckan.lib.base import *
-import ckan.authz as authz
-
-class TestGroupEditAuthz(TestController):
-    @classmethod
-    def setup_class(self):
-        model.repo.init_db()
-        model.repo.new_revision()
-        self.admin = 'madeup-administrator'
-        user = model.User(name=unicode(self.admin))
-        model.Session.add(user)
-        self.another = u'madeup-another'
-        model.Session.add(model.User(name=unicode(self.another)))
-        self.groupname = u'test6'
-        group = model.Group(name=self.groupname)
-        model.setup_default_user_roles(group, admins=[user])
-        model.repo.commit_and_remove()
-
-    @classmethod
-    def teardown_class(self):
-        model.repo.rebuild_db()
-
-    def test_0_nonadmin_cannot_edit_authz(self):
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, status=[302, 401])
-        res = res.follow()
-        assert res.request.url.startswith('/user/login')
-        # Alternative if we allowed read-only access
-        # res = self.app.get(offset)
-        # assert not '<form' in res, res
-    
-    def test_1_admin_has_access(self):
-        offset_authz = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset_authz, extra_environ={'REMOTE_USER':
-            self.admin}, status=200)
-
-        # check link is there too
-        offset_read = url_for(controller='group', action='read', id=self.groupname)
-        res = self.app.get(offset_read, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert offset_authz in res
-        
-
-    def test_2_read_ok(self):
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert self.groupname in res
-        assert '<tr' in res
-        assert self.admin in res
-        assert 'Role' in res
-        for uname in [ model.PSEUDO_USER__VISITOR, self.admin ]:
-            assert '%s' % uname in res
-        # crude but roughly correct
-        group = model.Group.by_name(self.groupname)
-        for r in group.roles:
-            assert '<select id="GroupRole-%s-role' % r.id in res
-
-        # now test delete links
-        pr = group.roles[0]
-        href = '%s' % pr.id
-        assert href in res, res
-
-    def _prs(self, groupname):
-        group = model.Group.by_name(groupname)
-        return dict([ (getattr(r.user, 'name', 'USER NAME IS NONE'), r) for r in group.roles ])
-
-    def test_3_admin_changes_role(self):
-        # create a role to be deleted
-        group = model.Group.by_name(self.groupname)
-        model.add_user_to_role(model.User.by_name(u'visitor'), model.Role.READER, group)
-        model.repo.commit_and_remove()
-
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert self.groupname in res
-
-        group = model.Group.by_name(self.groupname)
-        assert len(group.roles) == 3, [(grouprole.user.name, grouprole.role) for grouprole in group.roles]
-
-        def _r(r):
-            return 'GroupRole-%s-role' % r.id
-        def _u(r):
-            return 'GroupRole-%s-user_id' % r.id
-
-        prs = self._prs(self.groupname)
-        assert prs.has_key('visitor')
-        assert prs.has_key('logged_in')
-        assert prs.has_key(self.admin), prs
-        form = res.forms['group-authz']
-        
-        # change role assignments
-        form.select(_r(prs['visitor']), model.Role.EDITOR)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-
-        model.Session.remove()
-        prs = self._prs(self.groupname)
-        assert len(prs) == 3, prs
-        assert prs['visitor'].role == model.Role.EDITOR
-    
-    def test_4_admin_deletes_role(self):
-        group = model.Group.by_name(self.groupname)
-        
-        # create a role to be deleted
-        model.add_user_to_role(model.User.by_name(u'logged_in'), model.Role.READER, group)
-        model.repo.commit_and_remove()
-        
-        group = model.Group.by_name(self.groupname)
-        num_roles_start = len(group.roles)
-
-        # make sure not admin
-        pr_id = [ r for r in group.roles if r.user.name != self.admin ][0].id
-        offset = url_for(controller='group', action='authz', id=self.groupname,
-                role_to_delete=pr_id)
-        # need this here as o/w conflicts over session binding
-        model.Session.remove()
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert 'Deleted role' in res, res
-        assert 'error' not in res, res
-        group = model.Group.by_name(self.groupname)
-        assert len(group.roles) == num_roles_start - 1
-        assert model.Session.query(model.GroupRole).filter_by(id=pr_id).count() == 0
-
-    def test_5_admin_adds_role(self):
-        model.repo.commit_and_remove()
-        offset = url_for(controller='group', action='authz', id=self.groupname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert self.groupname in res
-        prs = self._prs(self.groupname) 
-        startlen = len(prs)
-        # could be 2 or 3 depending on whether we ran this test alone or not
-        # assert len(prs) == 2, prs
-
-        assert 'Create New User Roles' in res
-        assert '<select id="GroupRole--user_id"' in res, res
-        assert '<td>madeup-administrator</td>' not in res, res
-        form = res.forms['group-authz']
-        another = model.User.by_name(self.another)
-        form.select('GroupRole--user_id', another.id)
-        form.select('GroupRole--role', model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-        model.Session.remove()
-
-        prs = self._prs(self.groupname)
-        assert len(prs) == startlen+1, prs
-        assert prs[self.another].role == model.Role.ADMIN
-


--- a/ckan/tests/functional/test_package.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/tests/functional/test_package.py	Tue Jun 07 23:22:22 2011 +0100
@@ -293,6 +293,7 @@
         for txt in txt_order_non_deterministic:
             for pkg_ in (pkg_by_name_main, pkg_by_id_main):
                 pkg_ = pkg_.replace(txt, 'placeholder')
+        print pkg_by_name_main
         res_diff = self.diff_html(pkg_by_name_main, pkg_by_id_main)
         assert not res_diff, res_diff.encode('utf8')
         # not true as language selection link return url differs: 
@@ -882,7 +883,7 @@
             assert len(pkg.groups) == 0
             grp = model.Group.by_name(u'david')
             model.repo.new_revision()
-            pkg.groups.append(grp)
+            model.Session.add(model.PackageGroup(package=pkg, group=grp))
             model.repo.commit_and_remove()
             pkg = model.Package.by_name(u'editpkgtest')
             assert len(pkg.groups) == 1
@@ -891,8 +892,10 @@
             prefix = ''
             field_name = prefix + "groups__0__id"
             fv = res.forms['package-edit']
+            print field_name
             fv[field_name] = False
             res = fv.submit('save', extra_environ={'REMOTE_USER':'russianfan'})
+            model.repo.commit_and_remove()
             pkg = model.Package.by_name(u'editpkgtest')
             assert len(pkg.groups) == 0
         finally:


--- a/ckan/tests/functional/test_package_edit_authz.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/tests/functional/test_package_edit_authz.py	Tue Jun 07 23:22:22 2011 +0100
@@ -3,9 +3,34 @@
 from ckan.lib.base import *
 import ckan.authz as authz
 
+
+def check_and_set_checkbox(theform, user, role, should_be, set_to):
+   '''Given an authz form, find the checkbox associated with the strings user and role,
+   assert that it's in the state 'should_be', and set it to 'set_to' '''
+   user_role_string = '%s$%s' % (user, role)
+   checkboxes = [x for x in theform.fields[user_role_string] \
+                                   if x.__class__.__name__ == 'Checkbox']
+
+   assert(len(checkboxes)==1), \
+        "there should only be one checkbox for %s/%s" % (user, role)
+   checkbox = checkboxes[0]
+
+   #checkbox should be unticked
+   assert checkbox.checked==should_be, \
+                 "%s/%s checkbox in unexpected state" % (user, role)
+
+   #tick or untick the box and return the form
+   checkbox.checked=set_to
+   return theform
+
+
 class TestPackageEditAuthz(TestController):
     @classmethod
     def setup_class(self):
+        # for the authorization editing tests we set up test data so:
+        # three users, madeup-sysadmin , madeup-administrator, and madeup-another
+        # one authzgroup
+        # two packages test6 and test6a, m-a is admin on both
         model.repo.init_db()
         model.repo.new_revision()
         
@@ -15,7 +40,9 @@
         admin_user = model.User(name=unicode(self.admin))
         self.another = u'madeup-another'
         another_user = model.User(name=unicode(self.another))
-        for obj in sysadmin_user, admin_user, another_user:
+        self.authzgroup = u'madeup-authzgroup'
+        authzgroup = model.AuthorizationGroup(name=unicode(self.authzgroup))
+        for obj in sysadmin_user, admin_user, another_user, authzgroup:
             model.Session.add(obj)
 
         model.add_user_to_role(sysadmin_user, model.Role.ADMIN, model.System())
@@ -43,10 +70,7 @@
         res = self.app.get(offset, status=[302, 401])
         res = res.follow()
         assert res.request.url.startswith('/user/login')
-        # Alternative if we allowed read-only access
-        # res = self.app.get(offset)
-        # assert not '<form' in res, res
-    
+     
     def test_1_admin_has_access(self):
         offset = url_for(controller='package', action='authz', id=self.pkgname)
         res = self.app.get(offset, extra_environ={'REMOTE_USER':
@@ -62,182 +86,247 @@
         res = self.app.get(offset, extra_environ={'REMOTE_USER':
             self.admin})
         assert self.pkgname in res
+
+        # all the package's users and roles should appear in tables
         assert '<tr' in res
-        assert self.admin in res
-        assert 'Role' in res
-        for uname in [ model.PSEUDO_USER__VISITOR, self.admin ]:
-            assert '%s' % uname in res
-        # crude but roughly correct
+        for (user,role) in self.package_roles():
+            assert user in res
+            assert role in res
+
+
+    def package_roles(self):
         pkg = model.Package.by_name(self.pkgname)
-        for r in pkg.roles:
-            assert '<select id="PackageRole-%s-role' % r.id in res
+        list = [ (r.user.name, r.role) for r in pkg.roles if r.user]
+        list.extend([(r.authorized_group.name, r.role) for r in pkg.roles if r.authorized_group])
+        return list
 
-        # now test delete links
-        pr = pkg.roles[0]
-        href = '%s' % pr.id
-        assert href in res, res
+    def assert_package_roles_to_be(self, roles_list):
+        prs=self.package_roles()
+        ok = ( len(prs) == len(roles_list) )
+        for r in roles_list:
+           if not r in prs:
+               ok = False
+        if not ok:
+           print "expected roles: ", roles_list
+           print "actual roles: ", prs
+           assert False, "roles not as expected"
 
-    def _prs(self, pkgname):
-        pkg = model.Package.by_name(pkgname)
-        return dict([ (getattr(r.user, 'name', 'USER NAME IS NONE'), r) for r in pkg.roles ])
-
-    def test_3_admin_changes_role(self):
+    def change_roles(self, user):
         # load authz page
         offset = url_for(controller='package', action='authz', id=self.pkgname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
         assert self.pkgname in res
 
-        def _r(r):
-            return 'PackageRole-%s-role' % r.id
-        def _u(r):
-            return 'PackageRole-%s-user_id' % r.id
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
 
-        prs = self._prs(self.pkgname)
-        assert prs['visitor'].role == model.Role.EDITOR
-        assert prs['logged_in'].role == model.Role.EDITOR
-        form = res.forms['package-authz']
-        
-        # change role assignments
-        form.select(_r(prs['visitor']), model.Role.READER)
-        form.select(_r(prs['logged_in']), model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-        model.repo.commit_and_remove()
+        #admin makes visitor a reader and logged in an admin
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'reader', False, True)
+        check_and_set_checkbox(form, u'logged_in', u'admin', False, True)
+        check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+        check_and_set_checkbox(form, u'logged_in', u'editor', True, False)
+
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
 
         # ensure db was changed
-        prs = self._prs(self.pkgname)
-        assert len(prs) == 3, prs
-        assert prs['visitor'].role == model.Role.READER
-        assert prs['logged_in'].role == model.Role.ADMIN
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('visitor', 'reader'),
+           ('logged_in', 'admin')])
 
         # ensure rerender of form is changed
         offset = url_for(controller='package', action='authz', id=self.pkgname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
         assert self.pkgname in res
-        fv = res.forms['package-authz']
-        visitor_options = fv[_r(prs['visitor'])].options
-        assert ('reader', True) in visitor_options, visitor_options
-        logged_in_options = fv[_r(prs['logged_in'])].options
-        assert ('admin', True) in logged_in_options, logged_in_options
+
+        # check that the checkbox states are what we think they should be
+        # and put things back how they were.
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+        check_and_set_checkbox(form, u'logged_in', u'admin', True, False)
+        check_and_set_checkbox(form, u'visitor', u'editor', True, True)
+        check_and_set_checkbox(form, u'logged_in', u'editor', False, True)
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
+
+        # ensure db was changed
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+
+    def test_3_admin_changes_role(self):
+        self.change_roles(self.admin)
 
     def test_3_sysadmin_changes_role(self):
-        # load authz page
-        offset = url_for(controller='package', action='authz', id=self.pkgname2)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert self.pkgname2 in res
+        self.change_roles(self.sysadmin)
 
-        def _r(r):
-            return 'PackageRole-%s-role' % r.id
-        def _u(r):
-            return 'PackageRole-%s-user_id' % r.id
+    def delete_role_as(self,user):
+        # get the authz page, check that visitor's in there
+        # remove visitor's role on the package
+        # re-get the page and make sure that visitor's not in there at all
+        offset = url_for(controller='package', action='authz', id=self.pkgname)
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+        assert self.pkgname in res
 
-        prs = self._prs(self.pkgname2)
-        assert prs['visitor'].role == model.Role.EDITOR
-        assert prs['logged_in'].role == model.Role.EDITOR
-        form = res.forms['package-authz']
-        
-        # change role assignments
-        form.select(_r(prs['visitor']), model.Role.READER)
-        form.select(_r(prs['logged_in']), model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.sysadmin})
-        model.repo.commit_and_remove()
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        #admin removes visitor's only role
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'editor', True, False)
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
 
         # ensure db was changed
-        prs = self._prs(self.pkgname2)
-        assert len(prs) == 3, prs
-        assert prs['visitor'].role == model.Role.READER
-        assert prs['logged_in'].role == model.Role.ADMIN
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('logged_in', 'editor')])
 
         # ensure rerender of form is changed
-        offset = url_for(controller='package', action='authz', id=self.pkgname2)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert self.pkgname2 in res
-        fv = res.forms['package-authz']
-        visitor_options = fv[_r(prs['visitor'])].options
-        assert ('reader', True) in visitor_options, visitor_options
-        logged_in_options = fv[_r(prs['logged_in'])].options
-        assert ('admin', True) in logged_in_options, logged_in_options
-    
+        offset = url_for(controller='package', action='authz', id=self.pkgname)
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
+        assert self.pkgname in res
+
+        assert 'visitor' not in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        # check that the checkbox states are what we think they should be
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'logged_in', u'editor', True, True)
+        check_and_set_checkbox(form, u'madeup-administrator', u'admin', True, True)
+
+        # now we should add visitor back in, let's make him a reader
+        form = res.forms['addform']
+        form.fields['new_user_name'][0].value='visitor'
+        checkbox = [x for x in form.fields['reader'] \
+                      if x.__class__.__name__ == 'Checkbox'][0]
+        # check it's currently unticked
+        assert checkbox.checked == False
+        # tick it and submit
+        checkbox.checked=True
+        res = form.submit('add', extra_environ={'REMOTE_USER':user})
+        assert "User Added" in res, "don't see flash message"
+
+       # check that the page contains strings for everyone
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        # check that the roles in the db are back to normal
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'reader'),
+           ('logged_in', 'editor')])
+
+        # now change him back to being an editor
+        form = res.forms['theform']
+        check_and_set_checkbox(form, u'visitor', u'reader', True, False)
+        check_and_set_checkbox(form, u'visitor', u'editor', False, True)
+        res = form.submit('save', extra_environ={'REMOTE_USER': user})
+ 
+        # check that the page contains strings for everyone
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+
+        # check that the roles in the db are back to normal
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+
     def test_4_admin_deletes_role(self):
-        pkg = model.Package.by_name(self.pkgname)
-        assert len(pkg.roles) == 3
-        # make sure not admin
-        pr_id = [ r for r in pkg.roles if r.user.name != self.admin ][0].id
-        offset = url_for(controller='package', action='authz', id=self.pkgname,
-                role_to_delete=pr_id)
-        # need this here as o/w conflicts over session binding
-        model.Session.remove()
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
-        assert 'Deleted role' in res, res
-        assert 'error' not in res, res
-        pkg = model.Package.by_name(self.pkgname)
-        assert len(pkg.roles) == 2
-        assert model.Session.query(model.PackageRole).filter_by(id=pr_id).count() == 0
+        self.delete_role_as(self.admin)
 
     def test_4_sysadmin_deletes_role(self):
-        pkg = model.Package.by_name(self.pkgname2)
-        assert len(pkg.roles) == 3
-        # make sure not admin
-        pr_id = [ r for r in pkg.roles if r.user.name != self.admin ][0].id
-        offset = url_for(controller='package', action='authz', id=self.pkgname2,
-                role_to_delete=pr_id)
-        # need this here as o/w conflicts over session binding
-        model.Session.remove()
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert 'Deleted role' in res, res
-        assert 'error' not in res, res
-        pkg = model.Package.by_name(self.pkgname2)
-        assert len(pkg.roles) == 2
-        assert model.Session.query(model.PackageRole).filter_by(id=pr_id).count() == 0
+        self.delete_role_as(self.sysadmin)
 
-    def test_5_admin_adds_role(self):
+
+    def test_5_add_change_delete_authzgroup(self):
+        user=self.admin
+
+        # get the authz page, check that authzgroup isn't in there
         offset = url_for(controller='package', action='authz', id=self.pkgname)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.admin})
+        res = self.app.get(offset, extra_environ={'REMOTE_USER':user})
         assert self.pkgname in res
-        prs = self._prs(self.pkgname) 
-        startlen = len(prs)
-        # could be 2 or 3 depending on whether we ran this test alone or not
-        # assert len(prs) == 2, prs
 
-        assert 'Create New User Roles' in res
-        assert '<select id=' in res, res
-        form = res.forms['package-authz']
-        another = model.User.by_name(self.another)
-        form.select('PackageRole--user_id', another.id)
-        form.select('PackageRole--role', model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.admin})
-        model.Session.remove()
+        # check the state of the database
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
 
-        prs = self._prs(self.pkgname)
-        assert len(prs) == startlen+1, prs
-        assert prs[self.another].role == model.Role.ADMIN
+        # and that corresponding user strings are in the authz page
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' not in res
 
-    def test_5_sysadmin_adds_role(self):
-        offset = url_for(controller='package', action='authz', id=self.pkgname2)
-        res = self.app.get(offset, extra_environ={'REMOTE_USER':
-            self.sysadmin})
-        assert self.pkgname2 in res
-        prs = self._prs(self.pkgname2) 
-        startlen = len(prs)
-        # could be 2 or 3 depending on whether we ran this test alone or not
-        # assert len(prs) == 2, prs
+        # add madeup-authzgroup as an admin
+        form = res.forms['authzgroup_addform']
+        form.fields['new_user_name'][0].value='madeup-authzgroup'
+        checkbox = [x for x in form.fields['admin'] \
+                      if x.__class__.__name__ == 'Checkbox'][0]
+        # check the checkbox is currently unticked
+        assert checkbox.checked == False
+        # tick it and submit
+        checkbox.checked=True
+        res = form.submit('authz_add', extra_environ={'REMOTE_USER':user})
+        assert "Authorization Group Added" in res, "don't see flash message"
 
-        assert 'Create New User Roles' in res
-        assert '<select id=' in res, res
-        form = res.forms['package-authz']
-        another = model.User.by_name(self.another)
-        form.select('PackageRole--user_id', another.id)
-        form.select('PackageRole--role', model.Role.ADMIN)
-        res = form.submit('save', extra_environ={'REMOTE_USER': self.sysadmin})
-        model.Session.remove()
+        # examine the new page for user names/authzgroup names
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' in res
 
-        prs = self._prs(self.pkgname2)
-        assert len(prs) == startlen+1, prs
-        assert prs[self.another].role == model.Role.ADMIN
+        # and ensure that the database has changed as expected
+        self.assert_package_roles_to_be([
+           ('madeup-authzgroup', 'admin'),
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
 
+        # check that the checkbox states are what we think they should be
+        # and change madeup-authzgroup from admin to editor
+        form = res.forms['authzgroup_form']
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'editor', False, True)
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'admin', True, False)
+        res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+        #check database has changed.
+        self.assert_package_roles_to_be([
+           ('madeup-authzgroup', 'editor'),
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # now remove madeup-authzgroup entirely
+        form = res.forms['authzgroup_form']
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'editor', True, False)
+        check_and_set_checkbox(form, u'madeup-authzgroup', u'admin', False, False)
+        res = form.submit('authz_save', extra_environ={'REMOTE_USER': user})
+
+        #check database is back to normal
+        self.assert_package_roles_to_be([
+           ('madeup-administrator', 'admin'),
+           ('visitor', 'editor'),
+           ('logged_in', 'editor')])
+
+        # and that page contains only the expected strings
+        assert 'visitor' in res
+        assert 'madeup-administrator' in res
+        assert 'logged_in' in res
+        assert 'madeup-authzgroup' not in res


--- a/ckan/tests/lib/test_dictization.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/tests/lib/test_dictization.py	Tue Jun 07 23:22:22 2011 +0100
@@ -13,30 +13,38 @@
                                                 package_to_api1,
                                                 package_to_api2,
                                                )
-
 from ckan.lib.dictization.model_save import (package_dict_save,
                                              resource_dict_save,
                                              group_dict_save,
                                              package_api_to_dict,
                                              group_api_to_dict,
                                             )
+from ckan.logic.action.update import make_latest_pending_package_active
 
 class TestBasicDictize:
     @classmethod
     def setup_class(cls):
         CreateTestData.create()
+        
 
     @classmethod
     def teardown_class(cls):
         model.repo.rebuild_db()
         model.Session.remove()
 
+    def teardonwn(self):
+        model.Session.remove()
+
+
+
     def remove_changable_columns(self, dict):
         for key, value in dict.items():
             if key.endswith('id') and key <> 'license_id':
                 dict.pop(key)
             if key == 'created':
                 dict.pop(key)
+            if 'timestamp' in key:
+                dict.pop(key)
             if isinstance(value, list):
                 for new_dict in value:
                     self.remove_changable_columns(new_dict)
@@ -44,7 +52,8 @@
 
     def remove_revision_id(self, dict):
         for key, value in dict.items():
-            if key == 'revision_id':
+            if key in ('revision_id', 'revision_timestamp',
+                       'expired_timestamp', 'expired_id'):
                 dict.pop(key)
             if isinstance(value, list):
                 for new_dict in value:
@@ -54,10 +63,9 @@
     def test_01_dictize_main_objects_simple(self):
         
         context = {"model": model,
-                 "session": model.Session}
+                   "session": model.Session}
 
         ## package
-
         pkg = model.Session.query(model.Package).filter_by(name='annakarenina').first()
         result = table_dictize(pkg, context)
         self.remove_changable_columns(result)
@@ -112,16 +120,20 @@
         context = {"model": model,
                  "session": model.Session}
 
+        model.Session.remove()
         pkg = model.Session.query(model.Package).filter_by(name='annakarenina').first()
 
         result = package_dictize(pkg, context)
         self.remove_changable_columns(result)
 
         
-        assert result ==\
-            {'author': None,
+        expected = {'author': None,
              'author_email': None,
-             'extras': [{'key': u'original media', 'state': u'active', 'value': u'"book"'}],
+             'extras': [
+                {'key': u'genre',
+                 'state': u'active',
+                 'value': '"romantic novel"'},
+                {'key': u'original media', 'state': u'active', 'value': u'"book"'}],
              'groups': [{'description': u'These are books that David likes.',
                          'name': u'david',
                          'state': u'active',
@@ -157,7 +169,13 @@
              'tags': [{'name': u'russian'}, {'name': u'tolstoy'}],
              'title': u'A Novel By Tolstoy',
              'url': u'http://www.annakarenina.com',
-             'version': u'0.7a'}, pprint(result)
+             'version': u'0.7a'}
+
+        pprint(result)
+        pprint(expected)
+
+        assert sorted(result.values()) == sorted(expected.values())
+        assert result == expected
 
 
 
@@ -257,10 +275,12 @@
     def test_08_package_save(self):
 
         context = {"model": model,
-                 "session": model.Session}
+                   "session": model.Session}
 
         anna1 = model.Session.query(model.Package).filter_by(name='annakarenina').one()
 
+        
+
         anna_dictized = self.remove_changable_columns(package_dictize(anna1, context))
 
         anna_dictized["name"] = u'annakarenina3' 
@@ -275,12 +295,10 @@
 
         anna_original = pformat(anna_dictized)
         anna_after_save = pformat(package_dictized)
-        print anna_original
-        print anna_after_save
 
         assert self.remove_changable_columns(package_dictize(pkg, context)) == anna_dictized, "\n".join(unified_diff(anna_original.split("\n"), anna_after_save.split("\n")))
 
-    def test_10_package_alter(self):
+    def test_09_package_alter(self):
 
         context = {"model": model,
                  "session": model.Session}
@@ -301,6 +319,13 @@
 
         package_dictized = package_dictize(pkg, context)
 
+        resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
+
+        sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
+        for res in sorted_resources:
+            print res.id, res.revision_timestamp, res.expired_timestamp, res.state, res.current
+        assert len(sorted_resources) == 3
+
         anna_original = pformat(anna_dictized)
         anna_after_save = pformat(package_dictized)
 
@@ -310,8 +335,258 @@
         assert self.remove_revision_id(anna_dictized) == self.remove_revision_id(package_dictized),\
                 "\n".join(unified_diff(anna_original.split("\n"), anna_after_save.split("\n")))
 
+    def test_10_package_alter_pending(self):
 
-    def test_11_resource_no_id(self):
+        context = {'model': model,
+                   'session': model.Session,
+                   'pending': True}
+
+        anna1 = model.Session.query(model.Package).filter_by(name='annakarenina_changed').one()
+
+        anna_dictized = package_dictize(anna1, context)
+
+        anna_dictized['name'] = u'annakarenina_changed2' 
+        anna_dictized['resources'][0]['url'] = u'new_url2' 
+        anna_dictized['tags'][0]['name'] = u'new_tag' 
+        anna_dictized['tags'][0].pop('id') #test if 
+        anna_dictized['extras'][0]['value'] = u'"new_value"' 
+
+        model.repo.new_revision()
+        package_dict_save(anna_dictized, context)
+        model.Session.commit()
+        model.Session.remove()
+
+        pkgrevisions = model.Session.query(model.PackageRevision).filter_by(id=anna1.id).all()
+
+        sorted_packages = sorted(pkgrevisions, key=lambda x:x.revision_timestamp)[::-1]
+
+        assert len(sorted_packages) == 3
+        assert sorted_packages[0].state == 'pending'
+        assert sorted_packages[1].state == 'active'
+        assert sorted_packages[1].current
+        assert sorted_packages[2].state == 'active'
+
+        assert str(sorted_packages[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_packages[1].expired_timestamp) != '9999-12-31 00:00:00'
+        assert str(sorted_packages[2].expired_timestamp) != '9999-12-31 00:00:00'
+
+        resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
+        sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
+
+        for pkg in sorted_resources:
+            print pkg.url, pkg.id, pkg.revision_timestamp, pkg.expired_timestamp, pkg.state, pkg.current
+
+        assert len(sorted_resources) == 4
+        assert sorted_resources[0].state == 'pending'
+        assert sorted_resources[1].state == 'active'
+        assert sorted_resources[1].current
+        assert sorted_resources[2].state == 'active'
+        assert sorted_resources[2].current
+        assert sorted_resources[3].state == 'active'
+
+        assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[1].expired_timestamp) != '9999-12-31 00:00:00'
+        assert str(sorted_resources[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[3].expired_timestamp) != '9999-12-31 00:00:00'
+
+        tag_revisions = model.Session.query(model.PackageTagRevision).filter_by(package_id=anna1.id).all()
+
+        sorted_tags = sorted(tag_revisions, key=lambda x: (x.revision_timestamp, x.tag.name))[::-1]
+
+        print [(tag.state, tag.tag.name) for tag in sorted_tags]
+
+        assert len(sorted_tags) == 4, len(sorted_tags)
+        assert sorted_tags[0].state == 'pending-deleted'
+        assert sorted_tags[1].state == 'pending'
+        assert sorted_tags[2].state == 'active'
+        assert sorted_resources[2].current
+        assert sorted_tags[3].state == 'active'
+
+        assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[3].expired_timestamp) != '9999-12-31 00:00:00'
+
+        extras_revisions = model.Session.query(model.PackageExtraRevision).filter_by(package_id=anna1.id).all()
+
+        sorted_extras = sorted(extras_revisions, 
+                               key=lambda x: (x.revision_timestamp, x.key))[::-1]
+
+        assert sorted_extras[0].state == 'pending'
+        assert sorted_resources[1].current
+        assert sorted_extras[1].state == 'active'
+        assert sorted_resources[1].current
+        assert sorted_extras[2].state == 'active'
+
+        assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[2].expired_timestamp) != '9999-12-31 00:00:00'
+
+
+    def test_11_add_pending(self):
+
+        context = {'model': model,
+                   'session': model.Session,
+                   'pending': True}
+
+        anna1 = model.Session.query(model.Package).filter_by(name='annakarenina_changed2').one()
+        anna_dictized = package_dictize(anna1, context)
+
+
+        anna_dictized['resources'].append({
+                            'format': u'plain text',
+                            'url': u'newurl'}
+                            )
+        anna_dictized['tags'].append({'name': u'newnew_tag'})
+        anna_dictized['extras'].append({'key': 'david', 
+                                        'value': u'"new_value"'})
+
+        model.repo.new_revision()
+        package_dict_save(anna_dictized, context)
+        model.Session.commit()
+        model.Session.remove()
+
+        resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
+
+        sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
+        pprint(anna_dictized['resources'])
+
+        for pkg in sorted_resources:
+            print pkg.url, pkg.id, pkg.revision_timestamp, pkg.expired_timestamp, pkg.state, pkg.current
+
+
+        assert len(sorted_resources) == 5, len(sorted_resources)
+        assert sorted_resources[0].state == 'pending'
+        assert sorted_resources[1].state == 'pending'
+        assert sorted_resources[2].current
+        assert sorted_resources[2].state == 'active'
+        assert sorted_resources[3].current
+        assert sorted_resources[3].state == 'active'
+        assert sorted_resources[4].state == 'active'
+
+        assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[2].expired_timestamp) != '9999-12-31 00:00:00'
+        assert str(sorted_resources[3].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[4].expired_timestamp) != '9999-12-31 00:00:00'
+
+        tag_revisions = model.Session.query(model.PackageTagRevision).filter_by(package_id=anna1.id).all()
+
+        sorted_tags = sorted(tag_revisions, key=lambda x: (x.revision_timestamp, x.tag.name))[::-1]
+
+        print [(tag.state, tag.tag.name) for tag in sorted_tags]
+
+        assert len(sorted_tags) == 5, len(sorted_tags)
+        assert sorted_tags[0].state == 'pending'
+        assert sorted_tags[1].state == 'pending-deleted'
+        assert sorted_tags[2].state == 'pending'
+        assert sorted_tags[3].state == 'active'
+        assert sorted_tags[3].current
+        assert sorted_tags[4].state == 'active'
+        assert sorted_tags[4].current
+
+        assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[3].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[4].expired_timestamp) != '9999-12-31 00:00:00'
+
+        extras_revisions = model.Session.query(model.PackageExtraRevision).filter_by(package_id=anna1.id).all()
+
+        sorted_extras = sorted(extras_revisions, 
+                               key=lambda x: (x.revision_timestamp, x.key))[::-1]
+
+        print [(extra.state, extra.key, extra.value) for extra in sorted_extras]
+
+        assert sorted_extras[0].state == 'pending'
+        assert sorted_extras[1].state == 'pending'
+        assert sorted_extras[2].state == 'active'
+        assert sorted_extras[3].state == 'active'
+
+        assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[3].expired_timestamp) != '9999-12-31 00:00:00'
+
+    def test_12_make_active(self):
+
+        anna1 = model.Session.query(model.Package).filter_by(name='annakarenina_changed2').one()
+        context = {"model": model,
+                   "session": model.Session,
+                   'user': 'testsysadmin',
+                   "id": anna1.id}
+        make_latest_pending_package_active(context)
+
+        pkgrevisions = model.Session.query(model.PackageRevision).filter_by(id=anna1.id).all()
+        sorted_packages = sorted(pkgrevisions, key=lambda x:x.revision_timestamp)[::-1]
+
+        assert len(sorted_packages) == 3
+        assert sorted_packages[0].state == 'active', sorted_packages[0].state #was pending
+        assert sorted_packages[0].current == True #was pending
+        assert sorted_packages[1].state == 'active' #was active-current
+        assert sorted_packages[2].state == 'active'
+
+        resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
+        sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
+
+        assert len(sorted_resources) == 5
+        for res in sorted_resources:
+            print res.id, res.revision_timestamp, res.expired_timestamp, res.state
+        assert sorted_resources[0].state == 'active'
+        assert sorted_resources[0].current == True
+        assert sorted_resources[1].state == 'active'
+        assert sorted_resources[1].current == True
+        assert sorted_resources[2].state == 'active'
+        assert sorted_resources[3].state == 'active'
+        assert sorted_resources[3].current == True
+        assert sorted_resources[4].state == 'active'
+
+        assert str(sorted_resources[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[2].expired_timestamp) != '9999-12-31 00:00:00'
+        assert str(sorted_resources[3].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_resources[4].expired_timestamp) != '9999-12-31 00:00:00'
+
+        tag_revisions = model.Session.query(model.PackageTagRevision).filter_by(package_id=anna1.id).all()
+
+        sorted_tags = sorted(tag_revisions, key=lambda x: (x.revision_timestamp, x.tag.name))[::-1]
+
+        print [(tag.state, tag.tag.name) for tag in sorted_tags]
+
+        assert len(sorted_tags) == 5, len(sorted_tags)
+        assert sorted_tags[0].state == 'active'
+        assert sorted_tags[0].current
+        assert sorted_tags[1].state == 'deleted'
+        assert sorted_tags[1].current
+        assert sorted_tags[2].state == 'active'
+        assert sorted_tags[2].current
+        assert sorted_tags[3].state == 'active'
+        assert sorted_tags[4].state == 'active'
+
+        assert str(sorted_tags[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[3].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_tags[4].expired_timestamp) != '9999-12-31 00:00:00'
+
+        extras_revisions = model.Session.query(model.PackageExtraRevision).filter_by(package_id=anna1.id).all()
+
+        sorted_extras = sorted(extras_revisions, 
+                               key=lambda x: (x.revision_timestamp, x.key))[::-1]
+
+        print [(extra.state, extra.key, extra.value) for extra in sorted_extras]
+
+        assert sorted_extras[0].state == 'active'
+        assert sorted_extras[1].state == 'active'
+        assert sorted_extras[2].state == 'active'
+        assert sorted_extras[3].state == 'active'
+
+        assert str(sorted_extras[0].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[1].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[2].expired_timestamp) == '9999-12-31 00:00:00'
+        assert str(sorted_extras[3].expired_timestamp) != '9999-12-31 00:00:00'
+
+    def test_12_resource_no_id(self):
 
         context = {"model": model,
                  "session": model.Session}
@@ -337,15 +612,11 @@
 
         res = model.Session.query(model.Resource).filter_by(url=u'test').one()
 
-
         res_dictized = self.remove_changable_columns(resource_dictize(res, context))
 
-        pprint(res_dictized)
-        pprint(new_resource)
-
         assert res_dictized == new_resource, res_dictized 
 
-    def test_12_api_to_dictize(self):
+    def test_13_api_to_dictize(self):
 
         context = {"model": model,
                  "session": model.Session}
@@ -410,9 +681,8 @@
         pkg = model.Session.query(model.Package).filter_by(name=u'testpkg').one()
 
         package_dictized = self.remove_changable_columns(package_dictize(pkg, context))
-        pprint(package_dictized)
 
-    def test_13_group_dictized(self):
+    def test_14_group_dictized(self):
 
         context = {"model": model,
                   "session": model.Session}
@@ -488,9 +758,6 @@
 
 
         assert group_api_to_dict(api_group, context) == {'description': u'Great group!',
-                                                           'name': u'testgroup',
-                                                           'packages': [{'id': u'annakarenina'}, {'id': u'warandpeace'}],
-                                                           'title': u'Some Group Title'}, pformat(group_api1_to_dict(api_group, context))
-
-
-
+                                                         'name': u'testgroup',
+                                                         'packages': [{'id': u'annakarenina'}, {'id': u'warandpeace'}],
+                                                         'title': u'Some Group Title'}, pformat(group_api_to_dict(api_group, context))


--- a/ckan/tests/models/test_package.py	Tue May 24 22:17:14 2011 +0100
+++ b/ckan/tests/models/test_package.py	Tue Jun 07 23:22:22 2011 +0100
@@ -37,14 +37,35 @@
         rev = model.repo.new_revision()
         package = model.Package(name=name)
         model.Session.add(package)
+        model.Session.flush()
+        revision_id = model.Session().revision.id
+        timestamp = model.Session().revision.timestamp
         model.repo.commit_and_remove()
 
+        package = model.Package.by_name(name)
+        assert len(package.all_revisions) == 1
+        assert package.all_revisions[0].revision_id == revision_id
+        assert package.all_revisions[0].revision_timestamp == timestamp
+        assert package.all_revisions[0].expired_id is None
+
         # change it
         rev = model.repo.new_revision()
         package = model.Package.by_name(name)
         package.title = "wobsnasm"
+        revision_id2 = model.Session().revision.id
+        timestamp2 = model.Session().revision.timestamp
         model.repo.commit_and_remove()
 
+        package = model.Package.by_name(name)
+        assert len(package.all_revisions) == 2
+        assert package.all_revisions[0].revision_id == revision_id2
+        assert package.all_revisions[0].revision_timestamp == timestamp2
+        assert package.all_revisions[0].expired_id is None
+
+        assert package.all_revisions[1].revision_id == revision_id
+        assert package.all_revisions[1].revision_timestamp == timestamp
+        assert package.all_revisions[1].expired_id == revision_id2
+
     def test_create_package(self):
         package = model.Package.by_name(self.name)
         assert package.name == self.name


http://bitbucket.org/okfn/ckan/changeset/819aaa80e2cd/
changeset:   819aaa80e2cd
branch:      feature-1141-moderated-edits-ajax
user:        kindly
date:        2011-06-08 11:24:28
summary:     [mederated edits] test get package in the past
affected #:  5 files (3.4 KB)

--- a/ckan/lib/dictization/__init__.py	Tue Jun 07 23:22:22 2011 +0100
+++ b/ckan/lib/dictization/__init__.py	Wed Jun 08 10:24:28 2011 +0100
@@ -51,8 +51,11 @@
     '''Get a list of model object and represent it as a list of dicts'''
 
     result_list = []
+    active = context.get('active', True)
 
     for obj in obj_list:
+        if active and obj.state not in ('active', 'pending'):
+            continue
         result_list.append(table_dictize(obj, context))
 
     return sorted(result_list, key=sort_key)


--- a/ckan/lib/dictization/model_dictize.py	Tue Jun 07 23:22:22 2011 +0100
+++ b/ckan/lib/dictization/model_dictize.py	Wed Jun 08 10:24:28 2011 +0100
@@ -8,30 +8,30 @@
 import ckan.misc
 import json
 
-END_DATE = datetime.datetime(9999,12,31)
-
-class FakeSqlAlchemyObject(object):
-
-    def __init__(self, **kw):
-        for key, value in kw.iteritems():
-            self.key = value
-
 ## package save
 
 def group_list_dictize(obj_list, context, sort_key=lambda x:x):
 
+    active = context.get('active', True)
+
     result_list = []
 
     for obj in obj_list:
         group_dict = table_dictize(obj, context)
         group_dict.pop('created')
+        if active and obj.state not in ('active', 'pending'):
+            continue
+
         result_list.append(group_dict)
     return sorted(result_list, key=sort_key)
 
 def resource_list_dictize(res_list, context):
 
+    active = context.get('active', True)
     result_list = []
     for res in res_list:
+        if active and res.state not in ('active', 'pending'):
+            continue
         result_list.append(resource_dictize(res, context))
 
     return sorted(result_list, key=lambda x: x["position"])
@@ -53,7 +53,10 @@
 
 def extras_list_dictize(extras_list, context):
     result_list = []
+    active = context.get('active', True)
     for extra in extras_list:
+        if active and extra.state not in ('active', 'pending'):
+            continue
         dictized = table_dictize(extra, context)
         value = dictized["value"]
         if not(context.get("extras_as_string") and isinstance(value, basestring)):
@@ -74,20 +77,25 @@
     model = context['model']
     meta = model.meta
     session = model.Session
-    revision_id = context.get('revision_date')
+    active = context.get('active', False)
+    revision_id = context.get('revision_id')
     revision_date = context.get('revision_date')
     pending = context.get('pending')
 
     if revision_id:
-        model = session.query(context['model'].Revision).filter_by()
+        revision_date = session.query(context['model'].Revision).filter_by(
+            id=revision_id).one().timestamp
     
     if revision_date:
-        q = q.where(rev_table.c.revision_timestamp >= revision_date)
-        q = q.where(rev_table.c.expired_timestamp < revision_date)
+        q = q.where(rev_table.c.revision_timestamp <= revision_date)
+        q = q.where(rev_table.c.expired_timestamp > revision_date)
     elif pending:
         q = q.where(rev_table.c.expired_timestamp == '9999-12-31')
     else:
-        q = q.where(rev_table.c.current == '1')
+        q = q.where(rev_table.c.current == True)
+    if active:
+        q = q.where(rev_table.c.state.in_(['active', 'pending']))
+
     return session.execute(q)
 
 
@@ -109,7 +117,7 @@
     #tags
     tag_rev = model.package_tag_revision_table
     tag = model.tag_table
-    q = select([tag], 
+    q = select([tag, tag_rev.c.state, tag_rev.c.revision_timestamp], 
         from_obj=tag_rev.join(tag, tag.c.id == tag_rev.c.tag_id)
         ).where(tag_rev.c.package_id == pkg.id)
     result = _execute_with_revision(q, tag_rev, context)


--- a/ckan/logic/action/update.py	Tue Jun 07 23:22:22 2011 +0100
+++ b/ckan/logic/action/update.py	Wed Jun 08 10:24:28 2011 +0100
@@ -74,7 +74,7 @@
 
     old_current = q.filter_by(current=True).first()
     if old_current:
-        old_current.current = '0'
+        old_current.current = False
         session.add(old_current)
 
     latest_rev = q.filter_by(expired_timestamp='9999-12-31').one()
@@ -93,6 +93,9 @@
         if latest_rev.revision_timestamp > old_latest:
             context['latest_revision_date'] = latest_rev.revision_timestamp
             context['latest_revision'] = latest_rev.revision_id
+    else:
+        context['latest_revision_date'] = latest_rev.revision_timestamp
+        context['latest_revision'] = latest_rev.revision_id
 
 def make_latest_pending_package_active(context):
 
@@ -130,7 +133,9 @@
     revision = q.first()
     revision.approved_timestamp = datetime.datetime.now()
     session.add(revision)
-    model.repo.commit()        
+    
+    session.commit()        
+    session.remove()        
 
 
 def package_update(data_dict, context):


--- a/ckan/logic/schema.py	Tue Jun 07 23:22:22 2011 +0100
+++ b/ckan/logic/schema.py	Wed Jun 08 10:24:28 2011 +0100
@@ -52,6 +52,8 @@
                  tag_length_validator,
                  tag_name_validator,
                  tag_not_uppercase],
+        'revision_timestamp': [ignore],
+        'state': [ignore],
     }
     return schema
 


--- a/ckan/tests/lib/test_dictization.py	Tue Jun 07 23:22:22 2011 +0100
+++ b/ckan/tests/lib/test_dictization.py	Wed Jun 08 10:24:28 2011 +0100
@@ -25,6 +25,52 @@
     @classmethod
     def setup_class(cls):
         CreateTestData.create()
+
+        cls.package_expected = {
+            'author': None,
+            'author_email': None,
+            'extras': [
+               {'key': u'genre',
+                'state': u'active',
+                'value': '"romantic novel"'},
+               {'key': u'original media', 'state': u'active', 'value': u'"book"'}],
+            'groups': [{'description': u'These are books that David likes.',
+                        'name': u'david',
+                        'state': u'active',
+                        'title': u"Dave's books"},
+                       {'description': u'Roger likes these books.',
+                        'name': u'roger',
+                        'state': u'active',
+                        'title': u"Roger's books"}],
+            'license_id': u'other-open',
+            'maintainer': None,
+            'maintainer_email': None,
+            'name': u'annakarenina',
+            'notes': u'Some test notes\n\n### A 3rd level heading\n\n**Some bolded text.**\n\n*Some italicized text.*\n\nForeign characters:\nu with umlaut \xfc\n66-style quote \u201c\nforeign word: th\xfcmb\n \nNeeds escaping:\nleft arrow <\n\n<http://ckan.net/>\n\n',
+            'relationships_as_object': [],
+            'relationships_as_subject': [],
+            'resources': [{'alt_url': u'alt123',
+                           'description': u'Full text. Needs escaping: " Umlaut: \xfc',
+                           'size': u'123',
+                           'format': u'plain text',
+                           'hash': u'abc123',
+                           'position': 0,
+                           'state': u'active',
+                           'url': u'http://www.annakarenina.com/download/x=1&y=2'},
+                          {'alt_url': u'alt345',
+                           'description': u'Index of the novel',
+                           'size': u'345',
+                           'format': u'json',
+                           'hash': u'def456',
+                           'position': 1,
+                           'state': u'active',
+                           'url': u'http://www.annakarenina.com/index.json'}],
+            'state': u'active',
+                        'tags': [{'name': u'russian', 'state': u'active'},
+                                 {'name': u'tolstoy', 'state': u'active'}],
+            'title': u'A Novel By Tolstoy',
+            'url': u'http://www.annakarenina.com',
+            'version': u'0.7a'}
         
 
     @classmethod
@@ -91,6 +137,7 @@
         result = resource_dictize(resource, context)
         self.remove_changable_columns(result)
 
+
         assert result == {
              'alt_url': u'alt123',
              'description': u'Full text. Needs escaping: " Umlaut: \xfc',
@@ -126,56 +173,11 @@
         result = package_dictize(pkg, context)
         self.remove_changable_columns(result)
 
-        
-        expected = {'author': None,
-             'author_email': None,
-             'extras': [
-                {'key': u'genre',
-                 'state': u'active',
-                 'value': '"romantic novel"'},
-                {'key': u'original media', 'state': u'active', 'value': u'"book"'}],
-             'groups': [{'description': u'These are books that David likes.',
-                         'name': u'david',
-                         'state': u'active',
-                         'title': u"Dave's books"},
-                        {'description': u'Roger likes these books.',
-                         'name': u'roger',
-                         'state': u'active',
-                         'title': u"Roger's books"}],
-             'license_id': u'other-open',
-             'maintainer': None,
-             'maintainer_email': None,
-             'name': u'annakarenina',
-             'notes': u'Some test notes\n\n### A 3rd level heading\n\n**Some bolded text.**\n\n*Some italicized text.*\n\nForeign characters:\nu with umlaut \xfc\n66-style quote \u201c\nforeign word: th\xfcmb\n \nNeeds escaping:\nleft arrow <\n\n<http://ckan.net/>\n\n',
-             'relationships_as_object': [],
-             'relationships_as_subject': [],
-             'resources': [{'alt_url': u'alt123',
-                            'description': u'Full text. Needs escaping: " Umlaut: \xfc',
-                            'size': u'123',
-                            'format': u'plain text',
-                            'hash': u'abc123',
-                            'position': 0,
-                            'state': u'active',
-                            'url': u'http://www.annakarenina.com/download/x=1&y=2'},
-                           {'alt_url': u'alt345',
-                            'description': u'Index of the novel',
-                            'size': u'345',
-                            'format': u'json',
-                            'hash': u'def456',
-                            'position': 1,
-                            'state': u'active',
-                            'url': u'http://www.annakarenina.com/index.json'}],
-             'state': u'active',
-             'tags': [{'name': u'russian'}, {'name': u'tolstoy'}],
-             'title': u'A Novel By Tolstoy',
-             'url': u'http://www.annakarenina.com',
-             'version': u'0.7a'}
+        pprint(result)
+        pprint(self.package_expected)
 
-        pprint(result)
-        pprint(expected)
-
-        assert sorted(result.values()) == sorted(expected.values())
-        assert result == expected
+        assert sorted(result.values()) == sorted(self.package_expected.values())
+        assert result == self.package_expected
 
 
 
@@ -433,6 +435,7 @@
         anna_dictized = package_dictize(anna1, context)
 
 
+        anna_dictized['notes'] = 'wee'
         anna_dictized['resources'].append({
                             'format': u'plain text',
                             'url': u'newurl'}
@@ -520,11 +523,13 @@
         pkgrevisions = model.Session.query(model.PackageRevision).filter_by(id=anna1.id).all()
         sorted_packages = sorted(pkgrevisions, key=lambda x:x.revision_timestamp)[::-1]
 
-        assert len(sorted_packages) == 3
+        assert len(sorted_packages) == 4
         assert sorted_packages[0].state == 'active', sorted_packages[0].state #was pending
-        assert sorted_packages[0].current == True #was pending
-        assert sorted_packages[1].state == 'active' #was active-current
+        assert sorted_packages[0].current == True 
+
+        assert sorted_packages[1].state == 'pending' 
         assert sorted_packages[2].state == 'active'
+        assert sorted_packages[3].state == 'active'
 
         resources_revisions = model.Session.query(model.ResourceRevision).filter_by(resource_group_id=anna1.resource_groups[0].id).all()
         sorted_resources = sorted(resources_revisions, key=lambda x: (x.revision_timestamp, x.url))[::-1]
@@ -586,7 +591,64 @@
         assert str(sorted_extras[2].expired_timestamp) == '9999-12-31 00:00:00'
         assert str(sorted_extras[3].expired_timestamp) != '9999-12-31 00:00:00'
 
-    def test_12_resource_no_id(self):
+    def test_13_get_package_in_past(self):
+
+        context = {'model': model,
+                   'session': model.Session}
+
+        anna1 = model.Session.query(model.Package).filter_by(name='annakarenina_changed2').one()
+
+        pkgrevisions = model.Session.query(model.PackageRevision).filter_by(id=anna1.id).all()
+        sorted_packages = sorted(pkgrevisions, key=lambda x:x.revision_timestamp)
+
+        context['revision_id'] = sorted_packages[0].revision_id #original state
+
+        first_dictized = self.remove_changable_columns(package_dictize(anna1, context))
+        assert self.package_expected == first_dictized
+
+        context['revision_id'] = sorted_packages[1].revision_id #original state
+
+        second_dictized = self.remove_changable_columns(package_dictize(anna1, context))
+
+        first_dictized["name"] = u'annakarenina_changed' 
+        first_dictized["resources"][0]["url"] = u'new_url' 
+
+        assert second_dictized == first_dictized
+
+        context['revision_id'] = sorted_packages[2].revision_id #original state
+        third_dictized = self.remove_changable_columns(package_dictize(anna1, context))
+        
+        second_dictized['name'] = u'annakarenina_changed2' 
+        second_dictized['resources'][0]['url'] = u'new_url2' 
+        second_dictized['tags'][0]['name'] = u'new_tag' 
+        second_dictized['extras'][0]['value'] = u'"new_value"' 
+        second_dictized['state'] = 'pending'
+
+        assert second_dictized == third_dictized
+
+        context['revision_id'] = sorted_packages[3].revision_id #original state
+        forth_dictized = self.remove_changable_columns(package_dictize(anna1, context))
+
+        third_dictized['notes'] = 'wee'
+        third_dictized['resources'].insert(2, {u'description': u'',
+                                            u'format': u'plain text',
+                                            u'hash': u'',
+                                            u'position': 2,
+                                            u'state': u'active',
+                                            u'url': u'newurl'})
+
+        third_dictized['tags'].insert(1, {'name': u'newnew_tag', 'state': 'active'})
+        third_dictized['extras'].insert(0, {'key': 'david', 
+                                         'value': u'"new_value"',
+                                         'state': u'active'})
+        third_dictized['state'] = 'active'
+
+        pprint(third_dictized)
+        pprint(forth_dictized)
+
+        assert third_dictized == forth_dictized
+
+    def test_14_resource_no_id(self):
 
         context = {"model": model,
                  "session": model.Session}
@@ -616,7 +678,7 @@
 
         assert res_dictized == new_resource, res_dictized 
 
-    def test_13_api_to_dictize(self):
+    def test_15_api_to_dictize(self):
 
         context = {"model": model,
                  "session": model.Session}
@@ -682,7 +744,7 @@
 
         package_dictized = self.remove_changable_columns(package_dictize(pkg, context))
 
-    def test_14_group_dictized(self):
+    def test_16_group_dictized(self):
 
         context = {"model": model,
                   "session": model.Session}
@@ -744,7 +806,7 @@
         assert result == expected, pformat(result)
 
 
-    def test_14_group_apis_to_dict(self):
+    def test_17_group_apis_to_dict(self):
 
         context = {"model": model,
                   "session": model.Session}


http://bitbucket.org/okfn/ckan/changeset/cb5d3bfb4d32/
changeset:   cb5d3bfb4d32
branch:      feature-1141-moderated-edits-ajax
user:        kindly
date:        2011-06-08 13:37:00
summary:     [moderated edits] use revision_id in history
affected #:  2 files (131 bytes)

--- a/ckan/controllers/package.py	Wed Jun 08 10:24:28 2011 +0100
+++ b/ckan/controllers/package.py	Wed Jun 08 12:37:00 2011 +0100
@@ -353,7 +353,8 @@
         context = {'model': model, 'session': model.Session,
                    'user': c.user or c.author,
                    'id': id, 'extras_as_string': True,
-                   'schema': self._form_to_db_schema()}
+                   'schema': self._form_to_db_schema(),
+                   'revision_id': revision}
 
         try:
             data = get.package_show(context)
@@ -367,12 +368,6 @@
         data['tag_string'] = ' '.join([tag['name'] for tag in data.get('tags', [])])
         data.pop('tags')
         data = flatten_to_string_key(data)
-        
-        if revision:
-            revision = model.Session.query(model.PackageRevision).filter_by(
-                revision_id=revision, id=data['id']).one()
-            data.update(table_dictize(revision, context))
-
         response.headers['Content-Type'] = 'application/json;charset=utf-8'
         return json.dumps(data)
 
@@ -383,12 +378,16 @@
                    'id': id, 'extras_as_string': True}
         pkg = model.Package.get(id)
         data = []
+        approved = False
         for num, (revision, revision_obj) in enumerate(pkg.all_related_revisions):
+            if not approved and revision.approved_timestamp:
+                current_approved, approved = True, True
+            else:
+                current_approved = False
             data.append({'revision_id': revision.id,
                          'message': revision.message,
                          'timestamp': revision.timestamp.isoformat(),
-                         'current_approved': True if num == 0 else False})
-
+                         'current_approved': current_approved})
         response.headers['Content-Type'] = 'application/json;charset=utf-8'
         return json.dumps(data)
 


--- a/ckan/lib/dictization/model_dictize.py	Wed Jun 08 10:24:28 2011 +0100
+++ b/ckan/lib/dictization/model_dictize.py	Wed Jun 08 12:37:00 2011 +0100
@@ -77,7 +77,6 @@
     model = context['model']
     meta = model.meta
     session = model.Session
-    active = context.get('active', False)
     revision_id = context.get('revision_id')
     revision_date = context.get('revision_date')
     pending = context.get('pending')
@@ -93,8 +92,6 @@
         q = q.where(rev_table.c.expired_timestamp == '9999-12-31')
     else:
         q = q.where(rev_table.c.current == True)
-    if active:
-        q = q.where(rev_table.c.state.in_(['active', 'pending']))
 
     return session.execute(q)

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