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

Bitbucket commits-noreply at bitbucket.org
Mon Jul 25 13:46:15 UTC 2011


4 new changesets in ckan:

http://bitbucket.org/okfn/ckan/changeset/a0c9bbc76709/
changeset:   a0c9bbc76709
branch:      feature-1229-db-out-of-controllers
user:        amercader
date:        2011-07-22 14:57:30
summary:     [logic] Move user index and read
affected #:  9 files (5.9 KB)

--- a/ckan/controllers/user.py	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/controllers/user.py	Fri Jul 22 13:57:30 2011 +0100
@@ -1,13 +1,14 @@
 import logging
 
 import genshi
-from sqlalchemy import or_, func, desc
 from urllib import quote
 
 import ckan.misc
 from ckan.lib.base import *
 from ckan.lib import mailer
 
+import ckan.logic.action.get as get
+
 log = logging.getLogger(__name__)
 
 def login_form():
@@ -15,7 +16,7 @@
 
 class UserController(BaseController):
 
-    def index(self, id=None):
+    def index(self):
         LIMIT = 20
 
         if not self.authorizer.am_authorized(c, model.Action.USER_READ, model.System):
@@ -25,25 +26,18 @@
         c.q  = request.params.get('q', '')
         c.order_by = request.params.get('order_by', 'name')
 
-        query = model.Session.query(model.User, func.count(model.User.id))
-        if c.q:
-            query = model.User.search(c.q, query)
+        context = {'model': model,
+                   'user': c.user or c.author}
 
-        if c.order_by == 'edits':
-            query = query.join((model.Revision, or_(
-                    model.Revision.author==model.User.name,
-                    model.Revision.author==model.User.openid
-                    )))
-            query = query.group_by(model.User)
-            query = query.order_by(desc(func.count(model.User.id)))
-        else:
-            query = query.group_by(model.User)
-            query = query.order_by(model.User.name)
+        data_dict = {'q':c.q,
+                     'order_by':c.order_by}
+
+        users_list = get.user_list(context,data_dict)
 
         c.page = h.Page(
-            collection=query,
+            collection=users_list,
             page=page,
-            item_count=query.count(),
+            item_count=len(users_list),
             items_per_page=LIMIT
             )
         return render('user/list.html')
@@ -51,21 +45,21 @@
     def read(self, id=None):
         if not self.authorizer.am_authorized(c, model.Action.USER_READ, model.System):
             abort(401, _('Not authorized to see this page'))
-        if id:
-            user = model.User.get(id)
-        else:
-            user = c.userobj
-        if not user:
+
+        context = {'model': model,
+                   'user': c.user or c.author}
+
+        data_dict = {'id':id,
+                     'user':c.userobj}
+
+        user_dict = get.user_show(context,data_dict)
+        if not user_dict:
             h.redirect_to(controller='user', action='login', id=None)
-        c.read_user = user.display_name
-        c.is_myself = user.name == c.user
-        c.api_key = user.apikey
-        c.about_formatted = self._format_about(user.about)
-        revisions_q = model.Session.query(model.Revision
-                ).filter_by(author=user.name)
-        c.num_edits = user.number_of_edits()
-        c.num_pkg_admin = user.number_administered_packages()
-        c.activity = revisions_q.limit(20).all()
+
+        c.user_dict = user_dict
+        c.is_myself = user_dict['name'] == c.user
+        c.about_formatted = self._format_about(user_dict['about'])
+
         return render('user/read.html')
     
     def me(self):


--- a/ckan/lib/dictization/model_dictize.py	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/lib/dictization/model_dictize.py	Fri Jul 22 13:57:30 2011 +0100
@@ -166,6 +166,17 @@
     
     return result_dict 
 
+def user_dictize(user, context):
+
+    result_dict = table_dictize(user, context)
+
+    del result_dict['password']
+    
+    result_dict['display_name'] = user.display_name
+    result_dict['number_of_edits'] = user.number_of_edits()
+    result_dict['number_administered_packages'] = user.number_administered_packages()
+
+    return result_dict 
 
 ## conversion to api
 


--- a/ckan/lib/helpers.py	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/lib/helpers.py	Fri Jul 22 13:57:30 2011 +0100
@@ -5,6 +5,7 @@
 Consists of functions to typically be used within templates, but also
 available to Controllers. This module is available to templates as 'h'.
 """
+from datetime import datetime
 from webhelpers.html import escape, HTML, literal, url_escape
 from webhelpers.html.tools import mail_to
 from webhelpers.html.tags import *
@@ -209,3 +210,5 @@
     else:
         return ''
 
+def time_ago_in_words_from_str(date_str, format='%Y-%m-%dT%H:%M:%S.%f',granularity='month'):
+    return date.time_ago_in_words(datetime.strptime(date_str, format), granularity=granularity)


--- a/ckan/logic/action/get.py	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/logic/action/get.py	Fri Jul 22 13:57:30 2011 +0100
@@ -1,5 +1,5 @@
 from sqlalchemy.sql import select
-from sqlalchemy import or_
+from sqlalchemy import or_, func, desc
 
 from ckan.logic import NotFound, check_access
 from ckan.plugins import (PluginImplementations,
@@ -11,7 +11,8 @@
 from ckan.lib.dictization.model_dictize import (package_dictize,
                                                 resource_list_dictize,
                                                 group_dictize,
-                                                tag_dictize)
+                                                tag_dictize,
+                                                user_dictize)
 
 from ckan.lib.dictization.model_dictize import (package_to_api1,
                                                 package_to_api2,
@@ -81,7 +82,8 @@
     revision_dicts = []
     for revision, object_revisions in pkg.all_related_revisions:
         revision_dicts.append(model.revision_as_dict(revision,
-                                                     include_packages=False))
+                                                     include_packages=False,
+                                                     include_groups=False))
     return revision_dicts
 
 def group_list(context, data_dict):
@@ -146,6 +148,37 @@
     tag_list = [tag.name for tag in tags]
     return tag_list
 
+def user_list(context, data_dict):
+    '''Lists the current users'''
+    model = context['model']
+    user = context['user']
+
+    q = data_dict.get('q','')
+    order_by = data_dict.get('order_by','name')
+
+    query = model.Session.query(model.User, func.count(model.User.id))
+    if q:
+        query = model.User.search(q, query)
+
+    if order_by == 'edits':
+        query = query.join((model.Revision, or_(
+                model.Revision.author==model.User.name,
+                model.Revision.author==model.User.openid
+                )))
+        query = query.group_by(model.User)
+        query = query.order_by(desc(func.count(model.User.id)))
+    else:
+        query = query.group_by(model.User)
+        query = query.order_by(model.User.name)
+
+    users_list = []
+
+    for user in query.all():
+        result_dict = user_dictize(user[0], context)
+        del result_dict['apikey']
+        users_list.append(result_dict)
+
+    return users_list
 
 def package_relationships_list(context, data_dict):
 
@@ -243,9 +276,8 @@
     model = context['model']
     api = context.get('api_version') or '1'
     id = data_dict['id']
-    #ref_package_by = 'id' if api == '2' else 'name'
 
-    tag = model.Tag.get(id) #TODO tags
+    tag = model.Tag.get(id)
     context['tag'] = tag
 
     if tag is None:
@@ -259,10 +291,36 @@
     tag_dict['packages'] = extended_packages
 
     return tag_dict
-    package_list = [getattr(pkgtag.package, ref_package_by)
-                    for pkgtag in obj.package_tags]
-    return package_list 
 
+def user_show(context, data_dict):
+    '''Shows user details'''
+    model = context['model']
+
+    id = data_dict.get('id',None)
+    provided_user = data_dict.get('user',None)
+    if id:
+        user = model.User.get(id)
+        context['user'] = user
+    elif provided_user:
+        context['user'] = user = provided_user
+    else:
+        return None
+
+    user_dict = user_dictize(user,context)
+
+
+    revisions_q = model.Session.query(model.Revision
+            ).filter_by(author=user.name)
+    
+    revisions_list = []
+    for revision in revisions_q.limit(20).all():
+        revision_dict = revision_show(context,{'id':revision.id})
+        revision_dict['state'] = revision.state
+        revisions_list.append(revision_dict)
+
+    user_dict['activity'] = revisions_list
+
+    return user_dict
 
 def package_show_rest(context, data_dict):
 


--- a/ckan/model/__init__.py	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/model/__init__.py	Fri Jul 22 13:57:30 2011 +0100
@@ -226,7 +226,7 @@
 def strftimestamp(t):
     return t.isoformat()
 
-def revision_as_dict(revision, include_packages=True, ref_package_by='name'):
+def revision_as_dict(revision, include_packages=True, include_groups=True,ref_package_by='name'):
     revision_dict = OrderedDict((
         ('id', revision.id),
         ('timestamp', strftimestamp(revision.timestamp)),
@@ -237,4 +237,8 @@
     if include_packages:
         revision_dict['packages'] = [getattr(pkg, ref_package_by) \
                                      for pkg in revision.packages]
+    if include_groups:
+        revision_dict['groups'] = [getattr(grp, ref_package_by) \
+                                     for grp in revision.groups]
+       
     return revision_dict


--- a/ckan/templates/_util.html	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/templates/_util.html	Fri Jul 22 13:57:30 2011 +0100
@@ -384,4 +384,54 @@
     </tr></table>
 
+  
+  <table py:def="revision_list_from_dict(revisions, allow_compare=False)">
+    <tr>
+      <th>Revision</th><th>Timestamp</th><th>Author</th><th>Entity</th><th>Log Message</th>
+    </tr>
+    <tr
+      class="state-${revision['state']}"
+      py:for="revision in revisions"
+      >
+      <td>
+        ${
+          h.link_to(revision['id'],
+            h.url_for(
+              controller='revision',
+              action='read',
+              id=revision['id'])
+            )
+        }
+        <py:if test="c.revision_change_state_allowed">
+        <div class="actions">
+          <form
+            method="POST"
+            action="${h.url_for(controller='revision',
+                action='edit',
+                id=revision['id'])}"
+            >
+            <py:if test="revision['state']!='deleted'">
+            <button type="submit" name="action" value="delete">Delete</button>
+            </py:if>
+            <py:if test="revision['state']=='deleted'">
+            <button type="submit" name="action" value="undelete">Undelete</button>
+            </py:if>
+          </form>
+        </div>
+        </py:if>
+      </td>
+      <td>${revision['timestamp']}</td>
+      <td>${h.linked_user(revision['author'])}</td>
+      <td>
+        <py:for each="pkg in revision['packages']">
+          <a href="${h.url_for(controller='package', action='read', id=pkg)}">${pkg}</a>
+        </py:for>
+        <py:for each="grp in revision['groups']">
+          <a href="${h.url_for(controller='group', action='read', id=grp)}">${grp}</a>
+        </py:for>
+      </td>
+      <td>${revision['message']}</td>
+    </tr>
+  </table>
+
 </html>


--- a/ckan/templates/user/list.html	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/templates/user/list.html	Fri Jul 22 13:57:30 2011 +0100
@@ -25,25 +25,24 @@
     <hr /><ul class="userlist">
-      <li py:for="(user,count) in c.page.items" class="user">
+      <li py:for="user in c.page.items" class="user"><ul><li class="username">
-          ${h.linked_user(user, maxlength=20)}
+          ${h.linked_user(user['name'], maxlength=20)}
           </li><li class="created">
-            Member for ${h.date.time_ago_in_words(user.created,
-              granularity='month')}
+            Member for ${h.time_ago_in_words_from_str(user['created'],granularity='month')}
           </li><li>
-            <span class="edits" title="${user.number_of_edits()} Edits">
-              ${user.number_of_edits()}
+            <span class="edits" title="${user['number_of_edits']} Edits">
+              ${user['number_of_edits']}
             </span><span class="administered"
-              title="${user.number_administered_packages()} Administered">
+              title="${user['number_administered_packages']} Administered"><span class="badge">
                 ●
               </span>
-              ${user.number_administered_packages()}
+              ${user['number_administered_packages']}
             </span></li></ul>


--- a/ckan/templates/user/read.html	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/templates/user/read.html	Fri Jul 22 13:57:30 2011 +0100
@@ -2,18 +2,18 @@
   xmlns:xi="http://www.w3.org/2001/XInclude"
   py:strip="">
   
-  <py:def function="page_title">${c.read_user} - User</py:def>
+  <py:def function="page_title">${c.user_dict['display_name']} - User</py:def><py:def function="body_class">user-view</py:def><div py:match="content">
 
-    <h2>${c.read_user}</h2>
+    <h2>${c.user_dict['display_name']}</h2><py:if test="c.is_myself"><h3>My Account</h3><p>You are logged in.</p><ul>
-        <li>Your API key is: ${c.api_key}</li>
+        <li>Your API key is: ${c.user_dict['apikey']}</li><li><a href="${h.url_for(controller='user', action='edit')}">Edit your profile</a></li><li><a href="${h.url_for('/user/logout')}">Log out</a></li></ul>
@@ -26,14 +26,14 @@
     <div class="activity"><h3>Activity</h3><ul>
-        <li><strong>Number of edits:</strong> ${c.num_edits}</li>
-        <li><strong>Number of packages administered:</strong> ${c.num_pkg_admin}</li>
+        <li><strong>Number of edits:</strong> ${c.user_dict['number_of_edits']}</li>
+        <li><strong>Number of packages administered:</strong> ${c.user_dict['number_administered_packages']}</li></ul></div><div class="changes"><h3>Recent changes</h3>
-      ${revision_list(c.activity)}
+      ${revision_list_from_dict(c.user_dict['activity'])}
     </div></div>
 


--- a/ckan/tests/functional/api/test_action.py	Thu Jul 21 18:28:19 2011 +0100
+++ b/ckan/tests/functional/api/test_action.py	Fri Jul 22 13:57:30 2011 +0100
@@ -77,4 +77,30 @@
         package_created.pop('revision_timestamp')
         assert package_updated == package_created#, (pformat(json.loads(res.body)), pformat(package_created['result']))
 
+    def test_04_user_list(self):
+        postparams = '%s=1' % json.dumps({})
+        res = self.app.post('/api/action/user_list', params=postparams)
+        res_obj = json.loads(res.body)
+        assert res_obj['help'] == 'Lists the current users'
+        assert res_obj['success'] == True
+        assert len(res_obj['result']) == 7
+        assert res_obj['result'][0]['name'] == 'annafan'
+        assert res_obj['result'][0]['about'] == 'I love reading Annakarenina. My site: <a href="http://anna.com">anna.com</a>'
+        assert not 'apikey' in res_obj['result'][0]
 
+    def test_05_user_show(self):
+        postparams = '%s=1' % json.dumps({'id':'annafan'})
+        res = self.app.post('/api/action/user_show', params=postparams)
+        res_obj = json.loads(res.body)
+        assert res_obj['help'] == 'Shows user details'
+        assert res_obj['success'] == True
+        result = res_obj['result']
+        assert result['name'] == 'annafan'
+        assert result['about'] == 'I love reading Annakarenina. My site: <a href="http://anna.com">anna.com</a>'
+        assert 'apikey' in result
+        assert 'activity' in result
+        assert 'created' in result
+        assert 'display_name' in result
+        assert 'number_administered_packages' in result
+        assert 'number_of_edits' in result
+


http://bitbucket.org/okfn/ckan/changeset/92fafb868234/
changeset:   92fafb868234
branch:      feature-1229-db-out-of-controllers
user:        amercader
date:        2011-07-22 16:29:19
summary:     [logic] Add tests for tag actions
affected #:  2 files (1006 bytes)

--- a/ckan/logic/action/get.py	Fri Jul 22 13:57:30 2011 +0100
+++ b/ckan/logic/action/get.py	Fri Jul 22 15:29:19 2011 +0100
@@ -126,6 +126,7 @@
     return licences
 
 def tag_list(context, data_dict):
+    '''Lists tags by name'''
     model = context['model']
     user = context['user']
 
@@ -273,6 +274,8 @@
 
 
 def tag_show(context, data_dict):
+    '''Shows tag details'''
+
     model = context['model']
     api = context.get('api_version') or '1'
     id = data_dict['id']


--- a/ckan/tests/functional/api/test_action.py	Fri Jul 22 13:57:30 2011 +0100
+++ b/ckan/tests/functional/api/test_action.py	Fri Jul 22 15:29:19 2011 +0100
@@ -104,3 +104,21 @@
         assert 'number_administered_packages' in result
         assert 'number_of_edits' in result
 
+    def test_06_tag_list(self):
+        postparams = '%s=1' % json.dumps({})
+        res = self.app.post('/api/action/tag_list', params=postparams)
+        assert json.loads(res.body) == {'help': 'Lists tags by name',
+                                        'success': True,
+                                        'result': ['russian', 'tolstoy']}
+
+    def test_07_tag_show(self):
+        postparams = '%s=1' % json.dumps({'id':'russian'})
+        res = self.app.post('/api/action/tag_show', params=postparams)
+        res_obj = json.loads(res.body)
+        assert res_obj['help'] == 'Shows tag details'
+        assert res_obj['success'] == True
+        result = res_obj['result']
+        assert result['name'] == 'russian'
+        assert 'id' in result
+        assert 'packages' in result and len(result['packages']) == 3 
+        assert [package['name'] for package in result['packages']].sort() == ['annakarenina', 'warandpeace', 'moo'].sort()


http://bitbucket.org/okfn/ckan/changeset/b8162453f650/
changeset:   b8162453f650
branch:      feature-1229-db-out-of-controllers
user:        amercader
date:        2011-07-25 13:05:07
summary:     Fix typo
affected #:  1 file (1 byte)

--- a/ckan/controllers/api.py	Fri Jul 22 15:29:19 2011 +0100
+++ b/ckan/controllers/api.py	Mon Jul 25 12:05:07 2011 +0100
@@ -181,7 +181,7 @@
             return self._finish(403, return_dict, content_type='json')
         except ValidationError, e:
             error_dict = e.error_dict 
-            error_dict['__type'] = 'Validtion Error'
+            error_dict['__type'] = 'Validation Error'
             return_dict['error'] = error_dict
             return_dict['success'] = False
             log.error('Validation error: %r' % str(e.error_dict))


http://bitbucket.org/okfn/ckan/changeset/4f1ca1eb2630/
changeset:   4f1ca1eb2630
branch:      feature-1229-db-out-of-controllers
user:        amercader
date:        2011-07-25 15:45:56
summary:     [logic,forms] Move user create. The forms have also been migrated, using the new
schema and validators functions. TODO: edit user.
affected #:  11 files (12.1 KB)

--- a/ckan/controllers/user.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/controllers/user.py	Mon Jul 25 14:45:56 2011 +0100
@@ -6,8 +6,14 @@
 import ckan.misc
 from ckan.lib.base import *
 from ckan.lib import mailer
+from ckan.authz import Authorizer
+from ckan.lib.navl.dictization_functions import DataError, unflatten
+from ckan.logic import NotFound, NotAuthorized, ValidationError
+from ckan.logic import tuplize_dict, clean_dict, parse_params
+from ckan.logic.schema import user_form_schema
 
 import ckan.logic.action.get as get
+import ckan.logic.action.create as create
 
 log = logging.getLogger(__name__)
 
@@ -16,6 +22,21 @@
 
 class UserController(BaseController):
 
+    ## hooks for subclasses 
+    user_form = 'user/new_user_form.html'
+
+    def _form_to_db_schema(self):
+        return user_form_schema()
+
+    def _db_to_form_schema(self):
+        '''This is an interface to manipulate data from the database
+        into a format suitable for the form (optional)'''
+
+    def _setup_template_variables(self, context):
+        c.is_sysadmin = Authorizer().is_sysadmin(c.user)
+
+    ## end hooks
+
     def index(self):
         LIMIT = 20
 
@@ -51,8 +72,11 @@
 
         data_dict = {'id':id,
                      'user':c.userobj}
-
-        user_dict = get.user_show(context,data_dict)
+        try:
+            user_dict = get.user_show(context,data_dict)
+        except NotFound:
+             abort(404, _('User not found'))
+ 
         if not user_dict:
             h.redirect_to(controller='user', action='login', id=None)
 
@@ -68,52 +92,65 @@
         user_ref = c.userobj.get_reference_preferred_for_uri()
         h.redirect_to(controller='user', action='read', id=user_ref)
 
-    def register(self):
-        if not self.authorizer.am_authorized(c, model.Action.USER_CREATE, model.System):
-            abort(401, _('Not authorized to see this page'))
-        if request.method == 'POST':
-            try:
-                c.login = request.params.getone('login')
-                c.fullname = request.params.getone('fullname')
-                c.email = request.params.getone('email')
-            except KeyError, e:
-                abort(401, _('Missing parameter: %r') % e)
-            if not c.login:
-                h.flash_error(_("Please enter a login name."))
-                return render("user/register.html")
-            if not model.User.check_name_valid(c.login):
-                h.flash_error(_('That login name is not valid. It must be at least 3 characters, restricted to alphanumerics and these symbols: %s') % '_\-')
-                return render("user/register.html")
-            if not model.User.check_name_available(c.login):
-                h.flash_error(_("That login name is not available."))
-                return render("user/register.html")
-            if not request.params.getone('password1'):
-                h.flash_error(_("Please enter a password."))
-                return render("user/register.html")                
-            try:
-                password = self._get_form_password()
-            except ValueError, ve:
-                h.flash_error(ve)
-                return render('user/register.html')
-            user = model.User(name=c.login, fullname=c.fullname,
-                              email=c.email, password=password)
-            model.Session.add(user)
-            model.Session.commit() 
-            model.Session.remove()
-            h.redirect_to('/login_generic?login=%s&password=%s' % (str(c.login), quote(password.encode('utf-8'))))
+    def register(self, data=None, errors=None, error_summary=None):
+        return self.new(data, errors, error_summary)
 
-        return render('user/register.html')
+    def new(self, data=None, errors=None, error_summary=None):
+        context = {'model': model, 'session': model.Session,
+                   'user': c.user or c.author,
+                   'schema': self._form_to_db_schema(),
+                   'save': 'save' in request.params}
+
+        auth_for_create = Authorizer().am_authorized(c, model.Action.USER_CREATE, model.System())
+        if not auth_for_create:
+            abort(401, _('Unauthorized to create a user'))
+
+        if context['save'] and not data:
+            return self._save_new(context)
+        
+        data = data or {}
+        errors = errors or {}
+        error_summary = error_summary or {}
+        vars = {'data': data, 'errors': errors, 'error_summary': error_summary}
+
+        self._setup_template_variables(context)
+        c.form = render(self.user_form, extra_vars=vars)
+        return render('user/new.html')
+
+    def _save_new(self, context):
+        try:
+            data_dict = clean_dict(unflatten(
+                tuplize_dict(parse_params(request.params))))
+            context['message'] = data_dict.get('log_message', '')
+            user = create.user_create(context, data_dict)
+            h.redirect_to(controller='user', action='read', id=user['name'])
+        except NotAuthorized:
+            abort(401, _('Unauthorized to create user %s') % '')
+        except NotFound, e:
+            abort(404, _('User not found'))
+        except DataError:
+            abort(400, _(u'Integrity Error'))
+        except ValidationError, e:
+            errors = e.error_dict
+            error_summary = e.error_summary
+            return self.new(data_dict, errors, error_summary)
 
     def login(self):
         return render('user/login.html')
     
     def logged_in(self):
         if c.user:
-            userobj = model.User.by_name(c.user)
-            response.set_cookie("ckan_user", userobj.name)
-            response.set_cookie("ckan_display_name", userobj.display_name)
-            response.set_cookie("ckan_apikey", userobj.apikey)
-            h.flash_success(_("Welcome back, %s") % userobj.display_name)
+            context = {'model': model,
+                       'user': c.user}
+
+            data_dict = {'id':c.user}
+
+            user_dict = get.user_show(context,data_dict)
+
+            response.set_cookie("ckan_user", user_dict['name'])
+            response.set_cookie("ckan_display_name", user_dict['display_name'])
+            response.set_cookie("ckan_apikey", user_dict['apikey'])
+            h.flash_success(_("Welcome back, %s") % user_dict['display_name'])
             h.redirect_to(controller='user', action='me', id=None)
         else:
             h.flash_error('Login failed. Bad username or password.')


--- a/ckan/lib/dictization/model_save.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/lib/dictization/model_save.py	Mon Jul 25 14:45:56 2011 +0100
@@ -297,6 +297,19 @@
 
     return group
 
+def user_dict_save(user_dict, context):
+
+    model = context['model']
+    session = context['session']
+    user = context.get('userobj')
+    
+    User = model.User
+    if user:
+        user_dict['id'] = user.id 
+
+    user = table_dict_save(user_dict, User, context)
+
+    return user
 
 def package_api_to_dict(api1_dict, context):
 


--- a/ckan/logic/action/create.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/logic/action/create.py	Mon Jul 25 14:45:56 2011 +0100
@@ -14,15 +14,17 @@
 from ckan.lib.dictization.model_save import (group_api_to_dict,
                                              group_dict_save,
                                              package_api_to_dict,
-                                             package_dict_save)
+                                             package_dict_save,
+                                             user_dict_save)
 
 from ckan.lib.dictization.model_dictize import (group_dictize,
-                                                package_dictize)
+                                                package_dictize,
+                                                user_dictize)
 
 
 from ckan.logic.schema import default_create_package_schema, default_resource_schema
 
-from ckan.logic.schema import default_group_schema
+from ckan.logic.schema import default_group_schema, default_user_schema
 from ckan.lib.navl.dictization_functions import validate 
 from ckan.logic.action.update import (_update_package_relationship,
                                       package_error_summary,
@@ -194,6 +196,36 @@
                 'rating count': len(package.ratings)}
     return ret_dict
 
+def user_create(context, data_dict):
+    '''Creates a new user'''
+
+    model = context['model']
+    user = context['user']
+    schema = context.get('schema') or default_user_schema()
+
+    check_access(model.System(), model.Action.USER_CREATE, context)
+
+    data, errors = validate(data_dict, schema, context)
+
+    if errors:
+        model.Session.rollback()
+        raise ValidationError(errors, group_error_summary(errors))
+
+    rev = model.repo.new_revision()
+    rev.author = user
+
+    if 'message' in context:
+        rev.message = context['message']
+    else:
+        rev.message = _(u'REST API: Create user %s') % data.get('name')
+
+    user = user_dict_save(data, context)
+
+    model.repo.commit()        
+    context['user'] = user
+    context['id'] = user.id
+    log.debug('Created user %s' % str(user.name))
+    return user_dictize(user, context)
 
 ## Modifications for rest api
 


--- a/ckan/logic/action/get.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/logic/action/get.py	Mon Jul 25 14:45:56 2011 +0100
@@ -304,6 +304,8 @@
     if id:
         user = model.User.get(id)
         context['user'] = user
+        if user is None:
+            raise NotFound
     elif provided_user:
         context['user'] = user = provided_user
     else:
@@ -311,7 +313,6 @@
 
     user_dict = user_dictize(user,context)
 
-
     revisions_q = model.Session.query(model.Revision
             ).filter_by(author=user.name)
     


--- a/ckan/logic/schema.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/logic/schema.py	Mon Jul 25 14:45:56 2011 +0100
@@ -20,7 +20,12 @@
                                    duplicate_extras_key,
                                    ignore_not_admin,
                                    no_http,
-                                   tag_not_uppercase)
+                                   tag_not_uppercase,
+                                   user_name_validator,
+                                   user_password_validator,
+                                   user_both_passwords_entered,
+                                   user_passwords_match,
+                                   user_password_not_empty)
 from formencode.validators import OneOf
 import ckan.model
 
@@ -185,4 +190,27 @@
          'state': [ignore],
      }
 
+def default_user_schema():
 
+    schema = {
+        'id': [ignore_missing, unicode],
+        'name': [not_empty, unicode, user_name_validator],
+        'fullname': [ignore_missing, unicode],
+        'password': [user_password_validator, user_password_not_empty, ignore_missing, unicode],
+        'email': [ignore_missing, unicode],
+        'about': [ignore_missing, unicode],
+        'created': [ignore],
+        'openid': [ignore],
+        'apikey': [ignore],
+        'reset_key': [ignore],
+    }
+    return schema
+
+def user_form_schema():
+    schema = default_user_schema()
+    
+    schema['password1'] = [unicode,user_both_passwords_entered,user_password_validator,user_passwords_match]
+    schema['password2'] = [unicode]
+
+    return schema
+


--- a/ckan/logic/validators.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/logic/validators.py	Mon Jul 25 14:45:56 2011 +0100
@@ -1,6 +1,6 @@
 import re
 from pylons.i18n import _, ungettext, N_, gettext
-from ckan.lib.navl.dictization_functions import Invalid, missing, unflatten
+from ckan.lib.navl.dictization_functions import Invalid, Missing, missing, unflatten
 from ckan.authz import Authorizer
 
 def package_id_not_changed(value, context):
@@ -168,3 +168,52 @@
 
     data.pop(key)
 
+def user_name_validator(value,context):
+    model = context['model']
+
+    if not model.User.check_name_valid(value):
+        raise Invalid(
+            _('That login name is not valid. It must be at least 3 characters, restricted to alphanumerics and these symbols: %s') % '_\-'
+        )
+
+    if not model.User.check_name_available(value):
+        raise Invalid(
+            _("That login name is not available.")
+        )
+
+    return value
+
+def user_both_passwords_entered(key, data, errors, context):
+    
+    password1 = data.get(('password1',),None)
+    password2 = data.get(('password2',),None)
+
+    if password1 is None or password1 == '' or \
+       password2 is None or password2 == '':
+        errors[('password',)].append(_('Please enter both passwords'))
+
+def user_password_validator(key, data, errors, context):
+    value = data[key]
+       
+    if not isinstance(value, Missing) and not len(value) >= 4:
+        errors[('password',)].append(_('Your password must be 4 characters or longer'))
+
+def user_passwords_match(key, data, errors, context):
+    
+    password1 = data.get(('password1',),None)
+    password2 = data.get(('password2',),None)
+
+    if not password1 == password2:
+        errors[key].append(_('The passwords you entered do not match'))
+    else:
+        #Set correct password
+        data[('password',)] = password1
+
+def user_password_not_empty(key, data, errors, context):
+    '''Only check if password is present if the user is created via action API.
+       If not, user_both_passwords_entered will handle the validation'''
+     
+    if not ('password1',) in data and not ('password2',) in data:
+        password = data.get(('password',),None)
+        if not password:
+            errors[key].append(_('Missing value'))


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/templates/user/new.html	Mon Jul 25 14:45:56 2011 +0100
@@ -0,0 +1,35 @@
+<html xmlns:py="http://genshi.edgewall.org/"
+
+  xmlns:xi="http://www.w3.org/2001/XInclude"
+  py:strip="">
+  
+  <py:def function="page_title">Register - User</py:def>
+
+  <py:def function="optional_head">
+    <link rel="stylesheet" href="${g.site_url}/css/forms.css" type="text/css" media="screen, print" />
+  </py:def>
+
+  <py:match path="primarysidebar">
+    <li class="widget-container widget_text">
+      <h2>Have an OpenID?</h2>
+      <p>
+        If you have an account with Google, Yahoo or one of many other 
+        OpenID providers, you can log in without signing up. 
+      </p>
+      <ul>
+        <li>${h.link_to(_('Log in now'), h.url_for(action='login'))}</li>
+      </ul>
+    </li>
+  </py:match>
+
+  <div py:match="content">
+    <h2>
+      Join the community
+    </h2>
+
+    ${Markup(c.form)}
+  </div>
+
+  <xi:include href="layout.html" />
+</html>
+


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/templates/user/new_user_form.html	Mon Jul 25 14:45:56 2011 +0100
@@ -0,0 +1,42 @@
+<form id="user-edit" action="" method="post"
+    py:attrs="{'class':'has-errors'} if errors else {}"
+    xmlns:i18n="http://genshi.edgewall.org/i18n"
+    xmlns:py="http://genshi.edgewall.org/"
+    xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<div class="error-explanation" py:if="error_summary">
+<h2>Errors in form</h2>
+<p>The form contains invalid entries:</p>
+<ul>
+  <li py:for="key, error in error_summary.items()">${"%s: %s" % (key, error)}</li>
+</ul>
+</div>
+
+<fieldset>
+    <legend i18n:msg="site_title">Register with CKAN</legend>
+    <dl>
+        <dt><label class="field_opt" for="name">Login:</label></dt>
+        <dd><input type="text" name="name" value="${data.get('name','')}" /></dd>
+        <dd class="instructions basic">3+ chars, using only 'a-z0-9' and '-_'</dd>
+        <dd class="field_error" py:if="errors.get('name', '')">${errors.get('name', '')}</dd>
+
+        <dt><label class="field_opt" for="fullname">Full name (optional):</label></dt>
+        <dd><input type="text" name="fullname" value="${data.get('fullname','')}" /></dd>
+        <dd class="field_error" py:if="errors.get('fullname', '')">${errors.get('fullname', '')}</dd>
+
+        <dt><label class="field_opt" for="email">E-Mail (optional):</label></dt>
+        <dd><input type="text" name="email" value="${data.get('email','')}" /></dd>
+
+        <dt><label class="field_opt" for="password1">Password:</label></dt>
+        <dd><input type="password" name="password1" value="" /></dd>
+        <dd class="field_error" py:if="errors.get('password1', '')">${errors.get('password1', '')}</dd>
+
+        <dt><label class="field_opt" for="password2">Password (repeat):</label></dt>
+        <dd><input type="password" name="password2" value="" /></dd>
+
+    </dl>
+</fieldset>
+
+  <br />
+  <input id="save" name="save" type="submit" value="Save" />
+</form>


--- a/ckan/templates/user/register.html	Mon Jul 25 12:05:07 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-<html xmlns:py="http://genshi.edgewall.org/"
-  xmlns:i18n="http://genshi.edgewall.org/i18n"
-  xmlns:xi="http://www.w3.org/2001/XInclude"
-  py:strip="">
-  
-  <py:match path="primarysidebar">
-    <li class="widget-container widget_text">
-      <h2>Have an OpenID?</h2>
-      <p>
-        If you have an account with Google, Yahoo or one of many other 
-        OpenID providers, you can log in without signing up. 
-      </p>
-      <ul>
-        <li>${h.link_to(_('Log in now'), h.url_for(action='login'))}</li>
-      </ul>
-    </li>
-  </py:match>
-  
-  <py:def function="page_title">Register - User</py:def>
-
-  <div py:match="content">
-    <h2>Join the community</h2>
-    
-    <form action="/user/register" method="post" class="simple-form" id="register_form">  
-      <fieldset>
-        <legend i18n:msg="site_title">Register with CKAN</legend>
-
-        <label for="login">Login:</label>
-        <input name="login" value="${c.login}" />
-        <br/>
-        <label for="fullname">Full name (optional):</label>
-        <input name="fullname" value="${c.fullname}" />
-        <br/>
-        <label for="email">E-Mail (optional):</label>
-        <input name="email" value="${c.email}" />
-        <br/>
-        <label for="password1">Password:</label>
-        <input type="password" name="password1" value="" />
-        <br/>
-        <label for="password2">Password (repeat):</label>
-        <input type="password" name="password2" value="" />
-        <br/>
-      </fieldset>
-      ${h.submit('signup', _('Sign up'))}
-    </form>
-  </div>
-  <xi:include href="layout.html" />
-</html>


--- a/ckan/tests/functional/api/test_action.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/tests/functional/api/test_action.py	Mon Jul 25 14:45:56 2011 +0100
@@ -6,10 +6,22 @@
 
 class TestAction(WsgiAppCase):
 
+    STATUS_200_OK = 200
+    STATUS_201_CREATED = 201
+    STATUS_400_BAD_REQUEST = 400
+    STATUS_403_ACCESS_DENIED = 403
+    STATUS_404_NOT_FOUND = 404
+    STATUS_409_CONFLICT = 409
+
+    sysadmin_user = None
+
+
     @classmethod
     def setup_class(self):
         CreateTestData.create()
 
+        self.sysadmin_user = model.User.get('testsysadmin')
+
     @classmethod
     def teardown_class(self):
         model.repo.rebuild_db()
@@ -120,5 +132,71 @@
         result = res_obj['result']
         assert result['name'] == 'russian'
         assert 'id' in result
-        assert 'packages' in result and len(result['packages']) == 3 
+        assert 'packages' in result and len(result['packages']) == 3
         assert [package['name'] for package in result['packages']].sort() == ['annakarenina', 'warandpeace', 'moo'].sort()
+
+    def test_08_user_create_not_authorized(self):
+        postparams = '%s=1' % json.dumps({'name':'test_create_from_action_api', 'password':'testpass'})
+        res = self.app.post('/api/action/user_create', params=postparams,
+                            status=self.STATUS_403_ACCESS_DENIED)
+        res_obj = json.loads(res.body)
+        assert res_obj == {'help': 'Creates a new user',
+                           'success': False,
+                           'error': {'message': 'Access denied', '__type': 'Authorization Error'}}
+
+    def test_09_user_create(self):
+        user_dict = {'name':'test_create_from_action_api',
+                      'about': 'Just a test user',
+                      'password':'testpass'}
+
+        postparams = '%s=1' % json.dumps(user_dict)
+        res = self.app.post('/api/action/user_create', params=postparams,
+                            extra_environ={'Authorization': str(self.sysadmin_user.apikey)})
+        res_obj = json.loads(res.body)
+        assert res_obj['help'] == 'Creates a new user'
+        assert res_obj['success'] == True
+        result = res_obj['result']
+        assert result['name'] == user_dict['name']
+        assert result['about'] == user_dict['about']
+        assert 'apikey' in result
+        assert 'created' in result
+        assert 'display_name' in result
+        assert 'number_administered_packages' in result
+        assert 'number_of_edits' in result
+
+    def test_10_user_create_parameters_missing(self):
+        user_dict = {}
+
+        postparams = '%s=1' % json.dumps(user_dict)
+        res = self.app.post('/api/action/user_create', params=postparams,
+                            extra_environ={'Authorization': str(self.sysadmin_user.apikey)},
+                            status=self.STATUS_409_CONFLICT)
+        res_obj = json.loads(res.body)
+        assert res_obj == {
+            'error': {
+                '__type': 'Validation Error',
+                'name': ['Missing value'],
+                'password': ['Missing value']
+            },
+            'help': 'Creates a new user',
+            'success': False
+        }
+
+    def test_11_user_create_wrong_password(self):
+        user_dict = {'name':'test_create_from_action_api_2',
+                      'password':'tes'} #Too short
+
+        postparams = '%s=1' % json.dumps(user_dict)
+        res = self.app.post('/api/action/user_create', params=postparams,
+                            extra_environ={'Authorization': str(self.sysadmin_user.apikey)},
+                            status=self.STATUS_409_CONFLICT)
+
+        res_obj = json.loads(res.body)
+        assert res_obj == {
+            'error': {
+                '__type': 'Validation Error',
+                'password': ['Your password must be 4 characters or longer']
+            },
+            'help': 'Creates a new user',
+            'success': False
+        }


--- a/ckan/tests/functional/test_user.py	Mon Jul 25 12:05:07 2011 +0100
+++ b/ckan/tests/functional/test_user.py	Mon Jul 25 14:45:56 2011 +0100
@@ -185,12 +185,13 @@
         res = self.app.get(offset, status=200)
         main_res = self.main_div(res)
         assert 'Register' in main_res, main_res
-        fv = res.forms['register_form']
-        fv['login'] = username
+        from pprint import pprint
+        fv = res.forms['user-edit']
+        fv['name'] = username
         fv['fullname'] = fullname
         fv['password1'] = password
         fv['password2'] = password
-        res = fv.submit('signup')
+        res = fv.submit('save')
         
         # view user
         assert res.status == 302, self.main_div(res).encode('utf8')
@@ -203,7 +204,6 @@
             res = res.follow()
         assert res.status == 200, res
         main_res = self.main_div(res)
-        assert username in main_res, main_res
         assert fullname in main_res, main_res
 
         user = model.User.by_name(unicode(username))
@@ -223,12 +223,12 @@
         res = self.app.get(offset, status=200)
         main_res = self.main_div(res)
         assert 'Register' in main_res, main_res
-        fv = res.forms['register_form']
-        fv['login'] = username
+        fv = res.forms['user-edit']
+        fv['name'] = username
         fv['fullname'] = fullname.encode('utf8')
         fv['password1'] = password.encode('utf8')
         fv['password2'] = password.encode('utf8')
-        res = fv.submit('signup')
+        res = fv.submit('save')
         
         # view user
         assert res.status == 302, self.main_div(res).encode('utf8')
@@ -241,7 +241,6 @@
             res = res.follow()
         assert res.status == 200, res
         main_res = self.main_div(res)
-        assert username in main_res, main_res
         assert fullname in main_res, main_res
 
         user = model.User.by_name(unicode(username))
@@ -258,13 +257,13 @@
         res = self.app.get(offset, status=200)
         main_res = self.main_div(res)
         assert 'Register' in main_res, main_res
-        fv = res.forms['register_form']
+        fv = res.forms['user-edit']
         fv['password1'] = password
         fv['password2'] = password
-        res = fv.submit('signup')
+        res = fv.submit('save')
         assert res.status == 200, res
         main_res = self.main_div(res)
-        assert 'Please enter a login name' in main_res, main_res
+        assert 'Name: Missing value' in main_res, main_res
 
     def test_user_create_bad_name(self):
         # create/register user
@@ -275,15 +274,15 @@
         res = self.app.get(offset, status=200)
         main_res = self.main_div(res)
         assert 'Register' in main_res, main_res
-        fv = res.forms['register_form']
-        fv['login'] = username
+        fv = res.forms['user-edit']
+        fv['name'] = username
         fv['password1'] = password
         fv['password2'] = password
-        res = fv.submit('signup')
+        res = fv.submit('save')
         assert res.status == 200, res
         main_res = self.main_div(res)
         assert 'login name is not valid' in main_res, main_res
-        self.check_named_element(main_res, 'input', 'name="login"', 'value="%s"' % username)
+        self.check_named_element(main_res, 'input', 'name="name"', 'value="%s"' % username)
 
     def test_user_create_bad_password(self):
         # create/register user
@@ -294,15 +293,15 @@
         res = self.app.get(offset, status=200)
         main_res = self.main_div(res)
         assert 'Register' in main_res, main_res
-        fv = res.forms['register_form']
-        fv['login'] = username
+        fv = res.forms['user-edit']
+        fv['name'] = username
         fv['password1'] = password
         fv['password2'] = password
-        res = fv.submit('signup')
+        res = fv.submit('save')
         assert res.status == 200, res
         main_res = self.main_div(res)
         assert 'password must be 4 characters or longer' in main_res, main_res
-        self.check_named_element(main_res, 'input', 'name="login"', 'value="%s"' % username)
+        self.check_named_element(main_res, 'input', 'name="name"', 'value="%s"' % username)
 
     def test_user_create_without_password(self):
         # create/register user
@@ -313,14 +312,54 @@
         res = self.app.get(offset, status=200)
         main_res = self.main_div(res)
         assert 'Register' in main_res, main_res
-        fv = res.forms['register_form']
-        fv['login'] = username
+        fv = res.forms['user-edit']
+        fv['name'] = username
         # no password
-        res = fv.submit('signup')
+        res = fv.submit('save')
         assert res.status == 200, res
         main_res = self.main_div(res)
-        assert 'Please enter a password' in main_res, main_res
-        self.check_named_element(main_res, 'input', 'name="login"', 'value="%s"' % username)
+        assert 'Password: Please enter both passwords' in main_res, main_res
+        self.check_named_element(main_res, 'input', 'name="name"', 'value="%s"' % username)
+
+    def test_user_create_only_one_password(self):
+        # create/register user
+        username = 'testcreate4'
+        password = u'testpassword'
+        user = model.User.by_name(unicode(username))
+
+        offset = url_for(controller='user', action='register')
+        res = self.app.get(offset, status=200)
+        main_res = self.main_div(res)
+        assert 'Register' in main_res, main_res
+        fv = res.forms['user-edit']
+        fv['name'] = username
+        fv['password1'] = password
+        # Only password1
+        res = fv.submit('save')
+        assert res.status == 200, res
+        main_res = self.main_div(res)
+        assert 'Password: Please enter both passwords' in main_res, main_res
+        self.check_named_element(main_res, 'input', 'name="name"', 'value="%s"' % username)
+
+    def test_user_invalid_password(self):
+        # create/register user
+        username = 'testcreate4'
+        password = u'tes' # Too short
+        user = model.User.by_name(unicode(username))
+
+        offset = url_for(controller='user', action='register')
+        res = self.app.get(offset, status=200)
+        main_res = self.main_div(res)
+        assert 'Register' in main_res, main_res
+        fv = res.forms['user-edit']
+        fv['name'] = username
+        fv['password1'] = password
+        fv['password2'] = password
+        res = fv.submit('save')
+        assert res.status == 200, res
+        main_res = self.main_div(res)
+        assert 'Password: Your password must be 4 characters or longer' in main_res, main_res
+        self.check_named_element(main_res, 'input', 'name="name"', 'value="%s"' % username)
 
     def test_user_edit(self):
         # create user

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