[ckan-changes] commit/ckan: dread: [merge] fixes from release_v1.4.1.

Bitbucket commits-noreply at bitbucket.org
Thu Jun 23 09:31:27 UTC 2011


1 new changeset in ckan:

http://bitbucket.org/okfn/ckan/changeset/33258ee77b33/
changeset:   33258ee77b33
user:        dread
date:        2011-06-23 11:24:38
summary:     [merge] fixes from release_v1.4.1.
affected #:  30 files (14.2 KB)

--- a/ckan/config/deployment.ini_tmpl	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/config/deployment.ini_tmpl	Thu Jun 23 10:24:38 2011 +0100
@@ -152,12 +152,13 @@
 ## NB: can have multiline strings just indent following lines
 # ckan.template_footer_end = 
 
+# These three settings (ckan.log_dir, ckan.dump_dir and ckan.backup_dir) are
+# all used in cron jobs, not in CKAN itself. CKAN logging is configured 
+# in the logging configuration below
 # Directory for logs (produced by cron scripts associated with ckan)
 ckan.log_dir = %(here)s/log
-
 # Directory for JSON/CSV dumps (must match setting in apache config)
 ckan.dump_dir = %(here)s/dump
-
 # Directory for SQL database backups
 ckan.backup_dir = %(here)s/backup
 
@@ -203,7 +204,7 @@
 class = logging.handlers.RotatingFileHandler
 formatter = generic
 level = NOTSET
-args = ('%(here)s/ckan.log', 'a', 2e7, 9)
+args = ("ckan.log", "a", 20000000, 9)
 
 [formatter_generic]
 format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s


--- a/ckan/controllers/group.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/controllers/group.py	Thu Jun 23 10:24:38 2011 +0100
@@ -52,7 +52,6 @@
         query = authz.Authorizer().authorized_query(c.user, model.Group)
         query = query.order_by(model.Group.name.asc())
         query = query.order_by(model.Group.title.asc())
-        query = query.options(eagerload_all('packages'))
         c.page = Page(
             collection=query,
             page=request.params.get('page', 1),
@@ -72,7 +71,11 @@
         import ckan.misc
         format = ckan.misc.MarkdownFormat()
         desc_formatted = format.to_html(c.group.description)
-        desc_formatted = genshi.HTML(desc_formatted)
+        try: 
+            desc_formatted = genshi.HTML(desc_formatted)
+        except genshi.ParseError, e:
+            log.error('Could not print group description: %r Error: %r', c.group.description, e)
+            desc_formatted = 'Error: Could not parse group description'
         c.group_description_formatted = desc_formatted
         c.group_admins = self.authorizer.get_admins(c.group)
 


--- a/ckan/controllers/package.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/controllers/package.py	Thu Jun 23 10:24:38 2011 +0100
@@ -303,7 +303,7 @@
         if (context['save'] or context['preview']) and not data:
             return self._save_new(context)
 
-        data = data or {}
+        data = data or dict(request.params) 
         errors = errors or {}
         error_summary = error_summary or {}
         vars = {'data': data, 'errors': errors, 'error_summary': error_summary}
@@ -323,7 +323,6 @@
 
         if (context['save'] or context['preview']) and not data:
             return self._save_edit(id, context)
-
         try:
             old_data = get.package_show(context)
             schema = self._db_to_form_schema()
@@ -332,6 +331,8 @@
             data = data or old_data
         except NotAuthorized:
             abort(401, _('Unauthorized to read package %s') % '')
+        except NotFound:
+            abort(404, _('Package not found'))
 
         c.pkg = context.get("package")
 
@@ -349,7 +350,7 @@
     def _save_new(self, context):
         try:
             data_dict = clean_dict(unflatten(
-                tuplize_dict(parse_params(request.params))))
+                tuplize_dict(parse_params(request.POST))))
             self._check_data_dict(data_dict)
             context['message'] = data_dict.get('log_message', '')
             pkg = create.package_create(data_dict, context)
@@ -375,7 +376,7 @@
     def _save_edit(self, id, context):
         try:
             data_dict = clean_dict(unflatten(
-                tuplize_dict(parse_params(request.params))))
+                tuplize_dict(parse_params(request.POST))))
             self._check_data_dict(data_dict)
             context['message'] = data_dict.get('log_message', '')
             pkg = update.package_update(data_dict, context)
@@ -655,7 +656,7 @@
         package_name = id
         package = model.Package.get(package_name)
         if package is None:
-            abort(404, gettext('404 Package Not Found'))
+            abort(404, gettext('Package Not Found'))
         self._clear_pkg_cache(package)
         rating = request.params.get('rating', '')
         if rating:


--- a/ckan/controllers/user.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/controllers/user.py	Thu Jun 23 10:24:38 2011 +0100
@@ -1,11 +1,15 @@
 import re
+import logging
 
 import genshi
 from sqlalchemy import or_, func, desc
+from urllib import quote
 
 import ckan.misc
 from ckan.lib.base import *
 
+log = logging.getLogger(__name__)
+
 def login_form():
     return render('user/login_form.html').replace('FORM_ACTION', '%s')
 
@@ -73,13 +77,25 @@
     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': 
-            c.login = request.params.getone('login')
-            c.fullname = request.params.getone('fullname')
-            c.email = request.params.getone('email')
+        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 username is not available."))
+                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:
@@ -90,7 +106,8 @@
             model.Session.add(user)
             model.Session.commit() 
             model.Session.remove()
-            h.redirect_to(str('/login_generic?login=%s&password=%s' % (c.login, password.encode('utf-8'))))
+            h.redirect_to('/login_generic?login=%s&password=%s' % (str(c.login), quote(password.encode('utf-8'))))
+
         return render('user/register.html')
 
     def login(self):
@@ -139,9 +156,12 @@
         elif 'save' in request.params:
             try:
                 about = request.params.getone('about')
-                if 'http://' in about:
-                    msg = 'Edit not allowed as looks like spam. Please avoid links in your description'
+                if 'http://' in about or 'https://' in about:
+                    msg = _('Edit not allowed as it looks like spam. Please avoid links in your description.')
                     h.flash_error(msg)
+                    c.user_about = about
+                    c.user_fullname = request.params.getone('fullname')
+                    c.user_email = request.params.getone('email')
                     return render('user/edit.html')
                 user.about = about
                 user.fullname = request.params.getone('fullname')
@@ -166,8 +186,13 @@
         
     def _format_about(self, about):
         about_formatted = ckan.misc.MarkdownFormat().to_html(about)
-        return genshi.HTML(about_formatted) 
-
+        try:
+            html = genshi.HTML(about_formatted)
+        except genshi.ParseError, e:
+            log.error('Could not print "about" field Field: %r Error: %r', about, e)
+            html = 'Error: Could not parse About text'
+        return html
+    
     def _get_form_password(self):
         password1 = request.params.getone('password1')
         password2 = request.params.getone('password2')


--- a/ckan/lib/authenticator.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/lib/authenticator.py	Thu Jun 23 10:24:38 2011 +0100
@@ -14,10 +14,10 @@
                 # TODO: Implement a mask to ask for an alternative user 
                 # name instead of just using the OpenID identifier. 
                 name = identity.get('repoze.who.plugins.openid.nickname')
+                if not User.check_name_valid(name):
+                    name = openid
                 if not User.check_name_available(name):
                     name = openid
-                if User.by_name(name):
-                    name = openid
                 user = User(openid=openid, name=name,
                         fullname=identity.get('repoze.who.plugins.openid.fullname'),
                         email=identity.get('repoze.who.plugins.openid.email'))


--- a/ckan/lib/search/sql.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/lib/search/sql.py	Thu Jun 23 10:24:38 2011 +0100
@@ -142,7 +142,7 @@
                 if isinstance(terms, basestring):
                     terms = terms.split()
                    
-                if hasattr(model.Package, field):
+                if field in model.package_table.c:
                     model_attr = getattr(model.Package, field)
                     for term in terms:
                         q = q.filter(make_like(model_attr, term))
@@ -192,7 +192,7 @@
         group = model.Group.by_name(unicode(term), autoflush=False)
         if group:
             # need to keep joining for each filter
-            q = q.join('groups', aliased=True).filter(
+            q = q.join('package_group_all', 'group', aliased=True).filter(
                 model.Group.id==group.id)
         else:
             # unknown group, so torpedo search


--- a/ckan/lib/stats.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/lib/stats.py	Thu Jun 23 10:24:38 2011 +0100
@@ -49,6 +49,7 @@
         package_group = table('package_group')
         s = select([package_group.c.group_id, func.count(package_group.c.package_id)]).\
             group_by(package_group.c.group_id).\
+            where(package_group.c.group_id!=None).\
             order_by(func.count(package_group.c.package_id).desc()).\
             limit(limit)
         res_ids = model.Session.execute(s).fetchall()        


--- a/ckan/logic/action/get.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/logic/action/get.py	Thu Jun 23 10:24:38 2011 +0100
@@ -58,7 +58,7 @@
 
     query = ckan.authz.Authorizer().authorized_query(user, model.Group, model.Action.EDIT)
     groups = set(query.all())
-    return set([group.id for group in groups])
+    return dict((group.id, group.name) for group in groups)
 
 def group_list_availible(context):
     model = context['model']


--- a/ckan/logic/action/update.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/logic/action/update.py	Thu Jun 23 10:24:38 2011 +0100
@@ -56,7 +56,10 @@
     group_dicts = data_dict.get("groups", [])
     groups = set()
     for group_dict in group_dicts:
-        grp = model.Group.get(group_dict['id'])
+        id = group_dict.get('id')
+        if not id:
+            continue
+        grp = model.Group.get(id)
         if grp is None:
             raise NotFound(_('Group was not found.'))
         groups.add(grp)


--- a/ckan/logic/schema.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/logic/schema.py	Thu Jun 23 10:24:38 2011 +0100
@@ -106,8 +106,9 @@
     ##new
     schema['log_message'] = [unicode, no_http]
     schema['groups'] = {
-            'id': [not_empty, unicode],
+            'id': [ignore_missing, unicode],
             '__extras': [empty],
+            'name': [ignore, unicode],
     }
     schema['tag_string'] = [ignore_missing, tag_string_convert]
     schema['extras_validation'] = [duplicate_extras_key, ignore]


--- a/ckan/misc.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/misc.py	Thu Jun 23 10:24:38 2011 +0100
@@ -16,18 +16,27 @@
     internal_link = re.compile('(package|tag|group):([a-z0-9\-_]+)')
     normal_link = re.compile('<(http:[^>]+)>')
 
-    html_whitelist = 'a b center li ol p table td tr ul'.split(' ')
+    html_whitelist = 'b center li ol p table td tr ul'.split(' ')
     whitelist_elem = re.compile(r'<(\/?(%s)[^>]*)>' % "|".join(html_whitelist), re.IGNORECASE)
     whitelist_escp = re.compile(r'\\xfc\\xfd(\/?(%s)[^>]*?)\\xfd\\xfc' % "|".join(html_whitelist), re.IGNORECASE)
-    html_link = re.compile(r'<a href="([^"]*)">')
+    normal_link = re.compile(r'<a[^>]*?href="([^"]*?)"[^>]*?>', re.IGNORECASE)
+    abbrev_link = re.compile(r'<(http://[^>]*)>', re.IGNORECASE)
+    any_link = re.compile(r'<a[^>]*?>', re.IGNORECASE)
+    close_link = re.compile(r'<(\/a[^>]*)>', re.IGNORECASE)
+    link_escp = re.compile(r'\\xfc\\xfd(\/?(%s)[^>]*?)\\xfd\\xfc' % "|".join(['a']), re.IGNORECASE)
     
     def to_html(self, text):
         if text is None:
             return ''
-
         # Encode whitelist elements.
         text = self.whitelist_elem.sub(r'\\\\xfc\\\\xfd\1\\\\xfd\\\\xfc', text)
 
+        # Encode links only in an acceptable format (guard against spammers)
+        text = self.normal_link.sub(r'\\\\xfc\\\\xfda href="\1" target="_blank" rel="nofollow"\\\\xfd\\\\xfc', text)
+        text = self.abbrev_link.sub(r'\\\\xfc\\\\xfda href="\1" target="_blank" rel="nofollow"\\\\xfd\\\\xfc\1</a>', text)
+        text = self.any_link.sub(r'\\\\xfc\\\\xfda href="TAG MALFORMED" target="_blank" rel="nofollow"\\\\xfd\\\\xfc', text)
+        text = self.close_link.sub(r'\\\\xfc\\\\xfd\1\\\\xfd\\\\xfc', text)
+
         # Convert internal links.
         text = self.internal_link.sub(r'[\1:\2] (/\1/\2)', text)
 
@@ -42,8 +51,6 @@
 
         # Decode whitelist elements.
         text = self.whitelist_escp.sub(r'<\1>', text)
-
-        # Make links safer.
-        text = self.html_link.sub(r'<a href="\1" target="_blank" rel="nofollow">', text)
+        text = self.link_escp.sub(r'<\1>', text)
 
         return text


--- a/ckan/model/group.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/model/group.py	Thu Jun 23 10:24:38 2011 +0100
@@ -8,6 +8,7 @@
 from types import make_uuid
 import vdm.sqlalchemy
 from ckan.model import extension
+from sqlalchemy.ext.associationproxy import association_proxy
 
 __all__ = ['group_table', 'Group', 'package_revision_table',
            'PackageGroup', 'GroupRevision', 'PackageGroupRevision']
@@ -57,7 +58,6 @@
     def get(cls, reference):
         '''Returns a group object referenced by its id or name.'''
         query = Session.query(cls).filter(cls.id==reference)
-        query = query.options(eagerload_all('packages'))
         group = query.first()
         if group == None:
             group = cls.by_name(reference)
@@ -67,7 +67,7 @@
     def active_packages(self, load_eager=True):
         query = Session.query(Package).\
                filter_by(state=vdm.sqlalchemy.State.ACTIVE).\
-               join('groups').filter_by(id=self.id)
+               join('package_group_all', 'group').filter_by(id=self.id)
         if load_eager:
             query = query.options(eagerload_all('package_tags.tag'))
             query = query.options(eagerload_all('resource_groups_all.resources_all'))
@@ -119,12 +119,8 @@
         return '<Group %s>' % self.name
 
 
-mapper(Group, group_table, properties={
-    'packages': relation(Package, secondary=package_group_table,
-        backref='groups',
-        order_by=package_table.c.name
-    )},
-    extension=[vdm.sqlalchemy.Revisioner(group_revision_table),],
+mapper(Group, group_table, 
+       extension=[vdm.sqlalchemy.Revisioner(group_revision_table),],
 )
 
 
@@ -132,11 +128,26 @@
 GroupRevision = vdm.sqlalchemy.create_object_version(mapper, Group,
         group_revision_table)
 
-
-mapper(PackageGroup, package_group_table,
+mapper(PackageGroup, package_group_table, properties={
+    'group': relation(Group,
+        backref=backref('package_group_all', cascade='all, delete-orphan'),
+    ),
+    'package': relation(Package,
+        backref=backref('package_group_all', cascade='all, delete-orphan'),
+    ),
+},
     extension=[vdm.sqlalchemy.Revisioner(package_group_revision_table),],
 )
 
+def _create_group(group):
+    return PackageGroup(group=group)
+
+def _create_package(package):
+    return PackageGroup(package=package)
+
+Package.groups = association_proxy('package_group_all', 'group', creator=_create_group)
+Group.packages = association_proxy('package_group_all', 'package', creator=_create_package)
+
 
 vdm.sqlalchemy.modify_base_object_mapper(PackageGroup, Revision, State)
 PackageGroupRevision = vdm.sqlalchemy.create_object_version(mapper, PackageGroup,


--- a/ckan/model/package.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/model/package.py	Thu Jun 23 10:24:38 2011 +0100
@@ -74,8 +74,10 @@
 
     @property
     def resources(self):
+        if len(self.resource_groups_all) == 0:
+            return []
+
         assert len(self.resource_groups_all) == 1, "can only use resources on packages if there is only one resource_group"
-
         return self.resource_groups_all[0].resources
     
     def update_resources(self, res_dicts, autoflush=True):
@@ -526,4 +528,3 @@
 
         return fields
 
-


--- a/ckan/model/user.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/model/user.py	Thu Jun 23 10:24:38 2011 +0100
@@ -107,12 +107,16 @@
     password = property(_get_password, _set_password)
     
     @classmethod
+    def check_name_valid(cls, name):
+        if not name \
+            or not len(name.strip()) \
+            or not cls.VALID_NAME.match(name):
+            return False
+        return True
+
+    @classmethod
     def check_name_available(cls, name):
-        if not name \
-           or not len(name.strip()) \
-           or not cls.VALID_NAME.match(name):
-           return False
-        return cls.by_name(name)==None
+        return cls.by_name(name) == None
 
     def as_dict(self):
         _dict = DomainObject.as_dict(self)


--- a/ckan/public/css/forms.css	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/public/css/forms.css	Thu Jun 23 10:24:38 2011 +0100
@@ -46,7 +46,7 @@
 input.title {
   font-size: 1.5em; }
 input.short {
-  width: 15em; }
+  width: 10em; }
 input.medium-width {
   width: 25em; }
 input.long {


--- a/ckan/templates/package/new_package_form.html	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/templates/package/new_package_form.html	Thu Jun 23 10:24:38 2011 +0100
@@ -98,10 +98,11 @@
   <legend>Groups</legend><dl><py:for each="num, group in enumerate(data.get('groups', []))">
-      <dt>
+      <dt py:if="'id' in group"><input type="${'checkbox' if group['id'] in c.groups_authz else 'hidden'}" name="groups__${num}__id" checked="checked" value="${group['id']}" />
+      <input type="hidden" name="groups__${num}__name" value="${group.get('name', c.groups_authz.get(group['id']))}" /></dt>     
-      <dd><label for="groups__${num}__checked">${group['name']}</label></dd>
+      <dd py:if="'id' in group"><label for="groups__${num}__checked">${group.get('name', c.groups_authz.get(group['id']))}</label></dd></py:for><dt>Group</dt>


--- a/ckan/templates/user/register.html	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/templates/user/register.html	Thu Jun 23 10:24:38 2011 +0100
@@ -41,7 +41,7 @@
         <input type="password" name="password2" value="" /><br/></fieldset>
-      ${h.submit('s', _('Sign up'))}
+      ${h.submit('signup', _('Sign up'))}
     </form></div><xi:include href="layout.html" />


--- a/ckan/tests/functional/test_package.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/tests/functional/test_package.py	Thu Jun 23 10:24:38 2011 +0100
@@ -6,6 +6,7 @@
 from genshi.core import escape as genshi_escape
 from difflib import unified_diff
 from nose.plugins.skip import SkipTest
+from nose.tools import assert_equal
 
 from ckan.tests import *
 from ckan.tests import search_related
@@ -869,6 +870,8 @@
             assert field_name in res
             fv = res.forms['package-edit']
             fv[prefix + 'groups__0__id'] = grp.id
+            res = fv.submit('preview', extra_environ={'REMOTE_USER':'russianfan'})
+            assert not 'error' in res
             res = fv.submit('save', extra_environ={'REMOTE_USER':'russianfan'})
             res = res.follow()
             pkg = model.Package.by_name(u'editpkgtest')
@@ -899,6 +902,11 @@
         finally:
             self._reset_data()
 
+    def test_edit_404(self):
+        self.offset = url_for(controller='package', action='edit', id='random_name')
+        self.res = self.app.get(self.offset, status=404)
+
+
 class TestNew(TestPackageForm):
     pkg_names = []
 
@@ -913,18 +921,11 @@
 
     def test_new_with_params_1(self):
         offset = url_for(controller='package', action='new',
-                url='http://xxx.org')
+                url='http://xxx.org', name='xxx.org')
         res = self.app.get(offset)
         form = res.forms['package-edit']
-        form['url'].value == 'http://xxx.org/'
-        form['name'].value == 'xxx.org'
-
-    def test_new_with_params_2(self):
-        offset = url_for(controller='package', action='new',
-                url='http://www.xxx.org')
-        res = self.app.get(offset)
-        form = res.forms['package-edit']
-        form['name'].value == 'xxx.org'
+        assert_equal(form['url'].value, 'http://xxx.org')
+        assert_equal(form['name'].value, 'xxx.org')
 
     def test_new_without_resource(self):
         # new package
@@ -1383,7 +1384,7 @@
         self.body = str(self.res)
         self.assert_fragment('<table width="100%" border="1">')
         self.assert_fragment('<td rowspan="2"><b>Description</b></td>')
-        self.assert_fragment('<a href="http://www.nber.org/patents/subcategories.txt">subcategory.txt</a>')
+        self.assert_fragment('<a href="http://www.nber.org/patents/subcategories.txt" target="_blank" rel="nofollow">subcategory.txt</a>')
         self.assert_fragment('<td colspan="2"><center>--</center></td>')
         self.fail_if_fragment('<script>')
 


--- a/ckan/tests/functional/test_user.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/tests/functional/test_user.py	Thu Jun 23 10:24:38 2011 +0100
@@ -1,4 +1,5 @@
 from routes import url_for
+from nose.tools import assert_equal
 
 from ckan.tests import search_related, CreateTestData
 from ckan.tests.html_check import HtmlCheckMethods
@@ -8,9 +9,6 @@
 class TestUserController(FunctionalTestCase, HtmlCheckMethods):
     @classmethod
     def setup_class(self):
-        model.repo.init_db()
-        model.repo.rebuild_db()
-        model.repo.init_db()
         CreateTestData.create()
 
         # make 3 changes, authored by annafan
@@ -21,10 +19,19 @@
             rev.author = u'annafan'
             model.repo.commit_and_remove()
 
+        CreateTestData.create_user('unfinisher', about='<a href="http://unfinished.tag')
+        CreateTestData.create_user('uncloser', about='<a href="http://unclosed.tag">')
+        CreateTestData.create_user('spammer', about=u'<a href="http://mysite">mysite</a><a href=\u201dhttp://test2\u201d>test2</a>')
+        CreateTestData.create_user('spammer2', about=u'<a href="http://spamsite1.com\u201d>spamsite1</a>\r\n<a href="http://www.spamsite2.com\u201d>spamsite2</a>\r\n')
+        
     @classmethod
     def teardown_class(self):
         model.repo.rebuild_db()
 
+    def teardown(self):
+        # just ensure we're not logged in
+        self.app.get('/user/logout')
+
     def test_user_read(self):
         user = model.User.by_name(u'annafan')
         offset = '/user/%s' % user.id
@@ -65,6 +72,48 @@
         assert 'My Account' in main_res, main_res
         assert 'Edit' in main_res, main_res
 
+    def test_user_read_about_unfinished(self):
+        user = model.User.by_name(u'unfinisher')
+        offset = '/user/%s' % user.id
+        res = self.app.get(offset, status=200)
+        main_res = self.main_div(res)
+        assert 'unfinisher' in res, res
+        assert '<a href="http://unfinished.tag' in main_res, main_res
+
+    def test_user_read_about_unclosed(self):
+        user = model.User.by_name(u'uncloser')
+        offset = '/user/%s' % user.id
+        res = self.app.get(offset, status=200)
+        main_res = self.main_div(res)
+        assert 'unclosed' in res, res
+        # tag gets closed by genshi
+        assert '<a href="http://unclosed.tag" target="_blank" rel="nofollow">\n</a>' in main_res, main_res
+
+    def test_user_read_about_spam(self):
+        user = model.User.by_name(u'spammer')
+        offset = '/user/%s' % user.id
+        res = self.app.get(offset, status=200)
+        main_res = self.main_div(res)
+        assert 'spammer' in res, res
+        self.check_named_element(res, 'a',
+                                 'href="http://mysite"',
+                                 'target="_blank"',
+                                 'rel="nofollow"')
+
+        self.check_named_element(res, 'a',
+                                 'href="TAG MALFORMED"',
+                                 'target="_blank"',
+                                 'rel="nofollow"')
+
+    def test_user_read_about_spam2(self):
+        user = model.User.by_name(u'spammer2')
+        offset = '/user/%s' % user.id
+        res = self.app.get(offset, status=200)
+        main_res = self.main_div(res)
+        assert 'spammer2' in res, res
+        assert 'spamsite2' not in res, res
+        assert 'Error: Could not parse About text' in res, res
+        
     def test_user_login(self):
         offset = url_for(controller='user', action='login', id=None)
         res = self.app.get(offset, status=200)
@@ -125,6 +174,154 @@
         res = self.app.get(offset, extra_environ={'REMOTE_USER': 'okfntest'})
         assert 'Your API key is: %s' % user.apikey in res, res
 
+    def test_user_create(self):
+        # create/register user
+        username = 'testcreate'
+        fullname = u'Test Create'
+        password = u'testpassword'
+        assert not 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['register_form']
+        fv['login'] = username
+        fv['fullname'] = fullname
+        fv['password1'] = password
+        fv['password2'] = password
+        res = fv.submit('signup')
+        
+        # view user
+        assert res.status == 302, self.main_div(res).encode('utf8')
+        res = res.follow()
+        if res.status == 302:
+            res = res.follow()
+        if res.status == 302:
+            res = res.follow()
+        if res.status == 302:
+            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))
+        assert user
+        assert_equal(user.name, username)
+        assert_equal(user.fullname, fullname)
+        assert user.password
+
+    def test_user_create_unicode(self):
+        # create/register user
+        username = u'testcreate4'
+        fullname = u'Test Create\xc2\xa0'
+        password = u'testpassword\xc2\xa0'
+        assert not model.User.by_name(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['register_form']
+        fv['login'] = username
+        fv['fullname'] = fullname.encode('utf8')
+        fv['password1'] = password.encode('utf8')
+        fv['password2'] = password.encode('utf8')
+        res = fv.submit('signup')
+        
+        # view user
+        assert res.status == 302, self.main_div(res).encode('utf8')
+        res = res.follow()
+        if res.status == 302:
+            res = res.follow()
+        if res.status == 302:
+            res = res.follow()
+        if res.status == 302:
+            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))
+        assert user
+        assert_equal(user.name, username)
+        assert_equal(user.fullname, fullname)
+        assert user.password
+
+    def test_user_create_no_name(self):
+        # create/register user
+        password = u'testpassword'
+
+        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['register_form']
+        fv['password1'] = password
+        fv['password2'] = password
+        res = fv.submit('signup')
+        assert res.status == 200, res
+        main_res = self.main_div(res)
+        assert 'Please enter a login name' in main_res, main_res
+
+    def test_user_create_bad_name(self):
+        # create/register user
+        username = u'%%%%%%' # characters not allowed
+        password = 'testpass'
+
+        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['register_form']
+        fv['login'] = username
+        fv['password1'] = password
+        fv['password2'] = password
+        res = fv.submit('signup')
+        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)
+
+    def test_user_create_bad_password(self):
+        # create/register user
+        username = 'testcreate2'
+        password = u'a' # too short
+
+        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['register_form']
+        fv['login'] = username
+        fv['password1'] = password
+        fv['password2'] = password
+        res = fv.submit('signup')
+        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)
+
+    def test_user_create_without_password(self):
+        # create/register user
+        username = 'testcreate3'
+        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['register_form']
+        fv['login'] = username
+        # no password
+        res = fv.submit('signup')
+        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)
+
     def test_user_edit(self):
         # create user
         username = 'testedit'
@@ -172,6 +369,32 @@
         main_res = self.main_div(res)
         assert new_about in main_res, main_res
 
+    def test_edit_spammer(self):
+        # create user
+        username = 'testeditspam'
+        about = u'Test About <a href="http://spamsite.net">spamsite</a>'
+        user = model.User.by_name(unicode(username))
+        if not user:
+            model.Session.add(model.User(name=unicode(username), about=about,
+                                         password='letmein'))
+            model.repo.commit_and_remove()
+            user = model.User.by_name(unicode(username))
+
+        # edit
+        offset = url_for(controller='user', action='edit', id=user.id)
+        res = self.app.get(offset, status=200, extra_environ={'REMOTE_USER':username})
+        main_res = self.main_div(res)
+        assert 'Edit User: ' in main_res, main_res
+        assert 'Test About <a href="http://spamsite.net">spamsite</a>' in main_res, main_res
+        fv = res.forms['user-edit']
+        res = fv.submit('preview', extra_environ={'REMOTE_USER':username})
+        # commit
+        res = fv.submit('save', extra_environ={'REMOTE_USER':username})      
+        assert res.status == 200, res.status
+        main_res = self.main_div(res)
+        assert 'looks like spam' in main_res, main_res
+        assert 'Edit User: ' in main_res, main_res
+
 
     ############
     # Disabled


--- a/ckan/tests/html_check.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/tests/html_check.py	Thu Jun 23 10:24:38 2011 +0100
@@ -41,7 +41,7 @@
         '''Searches in the html and returns True if it can find a particular
         tag and all its subtags & data which contains all the of the
         html_to_find'''
-        named_element_re = re.compile('(<(%(tag)s\w*).*?>.*?</%(tag)s>)' % {'tag':tag_name}) 
+        named_element_re = re.compile('(<(%(tag)s\w*).*?(>.*?</%(tag)s)?>)' % {'tag':tag_name}) 
         html_str = self._get_html_from_res(html)
         self._check_html(named_element_re, html_str.replace('\n', ''), html_to_find)
 
@@ -91,7 +91,11 @@
             if found_all:
                 return # found it
         # didn't find it
-        assert 0, "Couldn't find %s in html. Closest matches were:\n%s" % (', '.join(["'%s'" % html.encode('utf8') for html in html_to_find]), '\n'.join([tag.encode('utf8') for tag in partly_matching_tags]))
+        if partly_matching_tags:
+            assert 0, "Couldn't find %s in html. Closest matches were:\n%s" % (', '.join(["'%s'" % html.encode('utf8') for html in html_to_find]), '\n'.join([tag.encode('utf8') for tag in partly_matching_tags]))
+        else:
+            assert 0, "Couldn't find %s in html. Tags matched were:\n%s" % (', '.join(["'%s'" % html.encode('utf8') for html in html_to_find]), '\n'.join([tag.encode('utf8') for tag in regex_compiled.finditer(html_str)]))
+
 
 
 class Stripper(sgmllib.SGMLParser):


--- a/ckan/tests/lib/test_dictization_schema.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/tests/lib/test_dictization_schema.py	Thu Jun 23 10:24:38 2011 +0100
@@ -132,14 +132,15 @@
         data = group_dictize(group, context)
 
         converted_data, errors = validate(data, default_group_schema(), context)
-        group.packages.sort(key=lambda x:x.id)
+        group_pack = sorted(group.packages, key=lambda x:x.id)
+
         converted_data["packages"] = sorted(converted_data["packages"], key=lambda x:x["id"])
 
         expected = {'description': u'These are books that David likes.',
                                  'id': group.id,
                                  'name': u'david',
-                                 'packages': sorted([{'id': group.packages[0].id},
-                                              {'id': group.packages[1].id,
+                                 'packages': sorted([{'id': group_pack[0].id},
+                                              {'id': group_pack[1].id,
                                                }], key=lambda x:x["id"]),
                                  'title': u"Dave's books"}
 


--- a/ckan/tests/lib/test_package_search.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/tests/lib/test_package_search.py	Thu Jun 23 10:24:38 2011 +0100
@@ -99,6 +99,11 @@
         result = self.backend.query_for(model.Package).run(query=u'Expenditure Government China')
         assert len(result['results']) == 0, self._pkg_names(result)
 
+    def test_3_licence(self):
+        ## this should result, but it is here to check that at least it does not error
+        result = self.backend.query_for(model.Package).run(query=u'license:"OKD::Other (PublicsDomain)"')
+        assert result['count'] == 0, result
+
 # Quotation not supported now
 ##        # multiple words quoted
 ##        result = Search().search(u'"Government Expenditure"')


--- a/ckan/tests/misc/test_format_text.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/tests/misc/test_format_text.py	Thu Jun 23 10:24:38 2011 +0100
@@ -34,15 +34,37 @@
         
     def test_internal_link(self):
         instr = 'package:test-_pkg'
-        exp = '<a href="/package/test-_pkg" target="_blank" rel="nofollow">package:test-_pkg</a>'
+        exp = '<a href="/package/test-_pkg">package:test-_pkg</a>'
         format = MarkdownFormat()
         out = format.to_html(instr)
         assert exp in out, '\nGot: %s\nWanted: %s' % (out, exp)
 
     def test_normal_link(self):
-        instr = '<http:/somelink/>'
-        exp = '<a href="http:/somelink/" target="_blank" rel="nofollow">http:/somelink/</a>'
+        instr = '<http://somelink/>'
+        exp = '<a href="http://somelink/" target="_blank" rel="nofollow">http://somelink/</a>'
         format = MarkdownFormat()
         out = format.to_html(instr)
         assert exp in out, '\nGot: %s\nWanted: %s' % (out, exp)
 
+    def test_malformed_link_1(self):
+        instr = u'<a href=\u201dsomelink\u201d>somelink</a>'
+        exp = '<a href="TAG MALFORMED" target="_blank" rel="nofollow">somelink</a>'
+        format = MarkdownFormat()
+        out = format.to_html(instr)
+        assert exp in out, '\nGot: %s\nWanted: %s' % (out, exp)
+
+    def test_malformed_link_2(self):
+        instr = u'<a href="http://url.com> url >'
+        exp = '<a href="TAG MALFORMED" target="_blank" rel="nofollow"> url >'
+        format = MarkdownFormat()
+        out = format.to_html(instr)
+        assert exp in out, '\nGot: %s\nWanted: %s' % (out, exp)
+
+    def test_malformed_link_3(self):
+        instr = u'<a href="http://url.com"> url'
+        exp = '<a href="http://url.com" target="_blank" rel="nofollow"> url'
+        # NB when this is put into Genshi, it will close the tag for you.
+        format = MarkdownFormat()
+        out = format.to_html(instr)
+        assert exp in out, '\nGot: %s\nWanted: %s' % (out, exp)
+


--- a/ckan/tests/models/test_package.py	Thu Jun 23 08:42:20 2011 +0100
+++ b/ckan/tests/models/test_package.py	Thu Jun 23 10:24:38 2011 +0100
@@ -26,6 +26,7 @@
     @classmethod
     def teardown_class(self):
         pkg1 = model.Session.query(model.Package).filter_by(name=self.name).one()
+        
         pkg1.purge()
         model.Session.commit()
         model.repo.rebuild_db()
@@ -87,7 +88,7 @@
         assert out['metadata_modified'] == pkg.metadata_modified.isoformat()
         assert out['metadata_created'] == pkg.metadata_created.isoformat()
         assert_equal(out['notes'], pkg.notes)
-        assert_equal(out['notes_rendered'], '<p>A <b>great</b> package [HTML_REMOVED] like <a href="/package/pollution_stats" target="_blank" rel="nofollow">package:pollution_stats</a>\n</p>')
+        assert_equal(out['notes_rendered'], '<p>A <b>great</b> package [HTML_REMOVED] like <a href="/package/pollution_stats">package:pollution_stats</a>\n</p>')
 
 
 class TestPackageWithTags:
@@ -373,3 +374,17 @@
         test_res(diff, self.res1, 'hash', 'abc123')
         test_res(diff, self.res1, 'state', 'active')
         test_res(diff, self.res2, 'url', 'http://url2.com')
+
+class TestPackagePurge:
+    @classmethod
+    def setup_class(self):
+        CreateTestData.create()
+    def test_purge(self):
+        pkgs = model.Session.query(model.Package).all()
+        for p in pkgs:
+           p.purge()
+        model.Session.commit()
+        pkgs = model.Session.query(model.Package).all()
+        assert len(pkgs) == 0
+
+


--- a/requires/lucid_missing.txt	Thu Jun 23 08:42:20 2011 +0100
+++ b/requires/lucid_missing.txt	Thu Jun 23 10:24:38 2011 +0100
@@ -15,6 +15,8 @@
 -e hg+http://hg.saddi.com/flup@301a58656bfb#egg=flup
 # All the conflicting dependencies from the lucid_conflict.txt file
 -e hg+https://bitbucket.org/okfn/ckan-deps@6287665a1965#egg=ckan-deps
+# FormAlchemy
+-e git+https://github.com/FormAlchemy/formalchemy.git@1.3.9#egg=formalchemy
 
 # NOTE: Developers, our build script for the Debian packages relies on the 
 #       requirements above being specified as editable resources with their


--- a/requires/lucid_present.txt	Thu Jun 23 08:42:20 2011 +0100
+++ b/requires/lucid_present.txt	Thu Jun 23 10:24:38 2011 +0100
@@ -11,8 +11,6 @@
 lxml==2.2.4
 sphinx==0.6.4
 Pylons==0.9.7
-# We use 1.3.6 but that isn't available, any of these should be fine
-FormAlchemy==1.3.5
 repoze.who==1.0.18
 tempita==0.4
 zope.interface==3.5.3

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