diff --git a/gen/__init__.py b/gen/__init__.py
index a431967..b4c4711 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
-import re, time, copy, sys, types, os, os.path, mimetypes, string, StringIO
+import re, time, copy, sys, types, os, os.path, mimetypes, string, StringIO, \
+ random
from appy import Object
from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts
@@ -1105,6 +1106,7 @@ class String(Type):
TEXT = 1
XHTML = 2
PASSWORD = 3
+ CAPTCHA = 4
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, format=LINE,
show=True, page='main', group=None, layouts=None, move=0,
@@ -1115,7 +1117,9 @@ class String(Type):
transform='none', styles=('p','h1','h2','h3','h4'),
allowImageUpload=True):
# According to format, the widget will be different: input field,
- # textarea, inline editor...
+ # textarea, inline editor... Note that there can be only one String
+ # field of format CAPTCHA by page, because the captcha challenge is
+ # stored in the session at some global key.
self.format = format
# When format is XHTML, the list of styles that the user will be able to
# select in the styles dropdown is defined hereafter.
@@ -1285,18 +1289,25 @@ class String(Type):
return res
def validateValue(self, obj, value):
- if not self.isSelect: return
- # Check that the value is among possible values
- possibleValues = self.getPossibleValues(obj)
- if isinstance(value, basestring):
- error = value not in possibleValues
- else:
- error = False
- for v in value:
- if v not in possibleValues:
- error = True
- break
- if error: return obj.translate('bad_select_value')
+ if self.format == String.CAPTCHA:
+ challenge = obj.REQUEST.SESSION.get('captcha', None)
+ # Compute the challenge minus the char to remove
+ i = challenge['number']-1
+ text = challenge['text'][:i] + challenge['text'][i+1:]
+ if value != text:
+ return obj.translate('bad_captcha')
+ elif self.isSelect:
+ # Check that the value is among possible values
+ possibleValues = self.getPossibleValues(obj)
+ if isinstance(value, basestring):
+ error = value not in possibleValues
+ else:
+ error = False
+ for v in value:
+ if v not in possibleValues:
+ error = True
+ break
+ if error: return obj.translate('bad_select_value')
accents = {'é':'e','è':'e','ê':'e','ë':'e','à':'a','â':'a','ä':'a',
'ù':'u','û':'u','ü':'u','î':'i','ï':'i','ô':'o','ö':'o',
@@ -1348,6 +1359,26 @@ class String(Type):
if (layoutType == 'edit') and (self.format == String.XHTML):
return ('ckeditor/ckeditor.js',)
+ def getCaptchaChallenge(self, session):
+ '''Returns a Captcha challenge in the form of a dict. At key "text",
+ value is a string that the user will be required to re-type, but
+ without 1 character whose position is at key "number". The challenge
+ is stored in the p_session, for the server-side subsequent check.'''
+ length = random.randint(5, 9) # The length of the challenge to encode
+ number = random.randint(1, length) # The position of the char to remove
+ text = '' # The challenge the user needs to type (minus one char)
+ for i in range(length):
+ j = random.randint(0, 1)
+ if j == 0:
+ chars = string.digits
+ else:
+ chars = string.letters
+ # Choose a char
+ text += chars[random.randint(0,len(chars)-1)]
+ res = {'text': text, 'number': number}
+ session['captcha'] = res
+ return res
+
class Boolean(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show=True,
@@ -2636,14 +2667,17 @@ class No:
class WorkflowAnonymous:
'''One-state workflow allowing anyone to consult and Manager to edit.'''
mgr = 'Manager'
- active = State({r:(mgr, 'Anonymous'), w:mgr, d:mgr}, initial=True)
+ o = 'Owner'
+ active = State({r:(mgr, 'Anonymous'), w:(mgr,o), d:(mgr,o)}, initial=True)
WorkflowAnonymous.__instance__ = WorkflowAnonymous()
class WorkflowAuthenticated:
'''One-state workflow allowing authenticated users to consult and Manager
to edit.'''
mgr = 'Manager'
- active = State({r:(mgr, 'Authenticated'), w:mgr, d:mgr}, initial=True)
+ o = 'Owner'
+ active = State({r:(mgr, 'Authenticated'), w:(mgr,o), d:(mgr,o)},
+ initial=True)
WorkflowAuthenticated.__instance__ = WorkflowAuthenticated()
# ------------------------------------------------------------------------------
diff --git a/gen/generator.py b/gen/generator.py
index 970df46..04568ee 100644
--- a/gen/generator.py
+++ b/gen/generator.py
@@ -458,6 +458,8 @@ class ZopeGenerator(Generator):
msg('doc', '', msg.FORMAT_DOC),
msg('rtf', '', msg.FORMAT_RTF),
msg('front_page_text', '', msg.FRONT_PAGE_TEXT),
+ msg('captcha_text', '', msg.CAPTCHA_TEXT),
+ msg('bad_captcha', '', msg.BAD_CAPTCHA),
]
# Create a label for every role added by this application
for role in self.getAllUsedRoles():
diff --git a/gen/installer.py b/gen/installer.py
index ef43db8..a7530eb 100644
--- a/gen/installer.py
+++ b/gen/installer.py
@@ -249,9 +249,11 @@ class ZopeInstaller:
except:
# When Plone has installed PAS in acl_users this may fail. Plone
# may still be in the way for migration purposes.
- users = ('admin') # We suppose there is at least a user.
+ users = ('admin',) # We suppose there is at least a user.
if not users:
- self.app.acl_users._doAddUser('admin', 'admin', ['Manager'], ())
+ appyTool.create('users', login='admin', firstName='admin',
+ name='admin', password1='admin', password2='admin',
+ email='admin@appyframework.org', roles=['Manager'])
appyTool.log('Admin user "admin" created.')
# Create group "admins" if it does not exist
diff --git a/gen/layout.py b/gen/layout.py
index 51f8d6e..f5a8af0 100644
--- a/gen/layout.py
+++ b/gen/layout.py
@@ -216,7 +216,7 @@ class Table(LayoutElement):
# ------------------------------------------------------------------------------
defaultPageLayouts = {
- 'view': Table('s|-n!-w|-b|', align="center"),
+ 'view': Table('n!-w|-b|', align="center"),
'edit': Table('w|-b|', width=None)}
defaultFieldLayouts = {'edit': 'lrv-f'}
# ------------------------------------------------------------------------------
diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py
index 266e350..6c28d3b 100644
--- a/gen/mixins/ToolMixin.py
+++ b/gen/mixins/ToolMixin.py
@@ -938,14 +938,17 @@ class ToolMixin(BaseMixin):
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
if (f.type == 'Pod') and (f.show == 'result')]
- def getUserLine(self, user):
- '''Returns a one-line user info as shown on every page.'''
- res = [user.getId()]
- rolesToShow = [r for r in user.getRoles() \
+ def getUserLine(self):
+ '''Returns a info about the currently logged user as a 2-tuple: first
+ elem is the one-line user info as shown on every page; second line is
+ the URL to edit user info.'''
+ appyUser = self.appy().appyUser
+ res = [appyUser.title, appyUser.login]
+ rolesToShow = [r for r in appyUser.roles \
if r not in ('Authenticated', 'Member')]
if rolesToShow:
res.append(', '.join([self.translate(r) for r in rolesToShow]))
- return ' | '.join(res)
+ return (' | '.join(res), appyUser.o.getUrl(mode='edit'))
def generateUid(self, className):
'''Generates a UID for an instance of p_className.'''
diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py
index 04f0fe9..6049ae8 100644
--- a/gen/mixins/__init__.py
+++ b/gen/mixins/__init__.py
@@ -418,7 +418,7 @@ class BaseMixin:
return the result.'''
page = self.REQUEST.get('page', 'main')
for field in self.getAppyTypes('edit', page):
- if (field.type == 'String') and (field.format == 3):
+ if (field.type == 'String') and (field.format in (3,4)):
self.REQUEST.set(field.name, '')
return self.ui.edit(self)
@@ -1073,6 +1073,9 @@ class BaseMixin:
obj = self
return appyType.getPossibleValues(obj, withTranslations, withBlankValue)
+ def getCaptchaChallenge(self, name):
+ return self.getAppyType(name).getCaptchaChallenge(self.REQUEST.SESSION)
+
def appy(self):
'''Returns a wrapper object allowing to manipulate p_self the Appy
way.'''
diff --git a/gen/po.py b/gen/po.py
index 037086d..a0493a0 100644
--- a/gen/po.py
+++ b/gen/po.py
@@ -114,6 +114,10 @@ class PoMessage:
FORMAT_PDF = 'PDF'
FORMAT_DOC = 'DOC'
FORMAT_RTF = 'RTF'
+ CAPTCHA_TEXT = 'Please type "${text}" (without the double quotes) in the ' \
+ 'field besides, but without the character at position ' \
+ '${number}.'
+ BAD_CAPTCHA = 'The code was not correct. Please try again.'
def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False):
diff --git a/gen/ui/portlet.pt b/gen/ui/portlet.pt
index 5784d18..26db2e1 100644
--- a/gen/ui/portlet.pt
+++ b/gen/ui/portlet.pt
@@ -30,24 +30,24 @@
userMayAdd python: user.has_permission(addPermission, appFolder);
createMeans python: tool.getCreateMeans(rootClass)">
- | + | + + + + + |