[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