appy.gen: added new format 'captcha' for a String.
This commit is contained in:
parent
0d55abb239
commit
a80ef513ff
|
@ -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,7 +1289,14 @@ class String(Type):
|
|||
return res
|
||||
|
||||
def validateValue(self, obj, value):
|
||||
if not self.isSelect: return
|
||||
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):
|
||||
|
@ -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()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'}
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -30,24 +30,24 @@
|
|||
userMayAdd python: user.has_permission(addPermission, appFolder);
|
||||
createMeans python: tool.getCreateMeans(rootClass)">
|
||||
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
||||
<img style="cursor:pointer"
|
||||
tal:condition="python: ('form' in createMeans) and userMayAdd"
|
||||
tal:attributes="onClick python: 'href: window.location=\'%s/do?action=Create&className=%s\'' % (toolUrl, rootClass);
|
||||
src string: $appUrl/ui/plus.png;
|
||||
title python: _('query_create')"/>
|
||||
<a tal:condition="python: ('form' in createMeans) and userMayAdd"
|
||||
tal:attributes="href python: '%s/do?action=Create&className=%s' % (toolUrl, rootClass);
|
||||
title python: _('query_create')">
|
||||
<img tal:attributes="src string: $appUrl/ui/plus.png"/>
|
||||
</a>
|
||||
<tal:comment replace="nothing">Create (a) new object(s) by importing data</tal:comment>
|
||||
<img style="cursor:pointer"
|
||||
tal:condition="python: ('import' in createMeans) and userMayAdd"
|
||||
tal:attributes="onClick python: 'href: window.location=\'%s/ui/import?className=%s\'' % (toolUrl, rootClass);
|
||||
src string: $appUrl/ui/import.png;
|
||||
title python: _('query_import')"/>
|
||||
<a tal:condition="python: ('import' in createMeans) and userMayAdd"
|
||||
tal:attributes="href python: '%s/ui/import?className=%s' % (toolUrl, rootClass);
|
||||
title python: _('query_import')">
|
||||
<img tal:attributes="src string: $appUrl/ui/import.png"/>
|
||||
</a>
|
||||
<tal:comment replace="nothing">Search objects of this type</tal:comment>
|
||||
<img style="cursor:pointer"
|
||||
tal:define="showSearch python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
||||
<a tal:define="showSearch python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
||||
tal:condition="showSearch"
|
||||
tal:attributes="onClick python: 'href: window.location=\'%s/ui/search?className=%s\'' % (toolUrl, rootClass);
|
||||
src string: $appUrl/ui/search.gif;
|
||||
title python: _('search_objects')"/>
|
||||
tal:attributes="href python: '%s/ui/search?className=%s' % (toolUrl, rootClass);
|
||||
title python: _('search_objects')">
|
||||
<img tal:attributes="src string: $appUrl/ui/search.gif"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -88,7 +88,8 @@
|
|||
<tr>
|
||||
<td>
|
||||
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment>
|
||||
<table align="center" tal:condition="isAnon" class="login">
|
||||
<table align="center" tal:condition="python: isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])"
|
||||
class="login">
|
||||
<tr><td>
|
||||
<form name="loginform" method="post"
|
||||
tal:attributes="action python: tool.absolute_url() + '/performLogin'">
|
||||
|
@ -116,17 +117,23 @@
|
|||
<img tal:attributes="src string: $appUrl/ui/home.gif"/>
|
||||
</a>
|
||||
<!-- Config -->
|
||||
<img style="cursor:pointer" tal:condition="python: user.has_role('Manager')"
|
||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % tool.getUrl(page='main', nav='');
|
||||
title python: _('%sTool' % appName);
|
||||
src string:$appUrl/ui/appyConfig.gif"/>
|
||||
<a tal:condition="python: user.has_role('Manager')"
|
||||
tal:attributes="href python: tool.getUrl(page='main', nav='');
|
||||
title python: _('%sTool' % appName)">
|
||||
<img tal:attributes="src string:$appUrl/ui/appyConfig.gif"/>
|
||||
</a>
|
||||
<!-- Logout -->
|
||||
<a tal:attributes="href python: tool.absolute_url() + '/performLogout';
|
||||
title python: _('logout')">
|
||||
<img tal:attributes="src string: $appUrl/ui/logout.gif"/>
|
||||
</a>
|
||||
</td>
|
||||
<td align="right" tal:content="python: tool.getUserLine(user)"></td>
|
||||
<td align="right" tal:define="userInfo tool/getUserLine">
|
||||
<span tal:content="python: userInfo[0]"></span>
|
||||
<a tal:attributes="href python: userInfo[1]">
|
||||
<img tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
tal:define="fmt widget/format;
|
||||
isSelect widget/isSelect;
|
||||
isMaster widget/slaves;
|
||||
isOneLine python: fmt in (0,3);
|
||||
isOneLine python: fmt in (0,3,4);
|
||||
maxChars python: test(widget['maxChars'], widget['maxChars'], '')">
|
||||
|
||||
<tal:choice condition="isSelect">
|
||||
|
@ -49,6 +49,12 @@
|
|||
value python: test(inRequest, requestValue, value);
|
||||
style python: 'text-transform:%s' % widget['transform'];
|
||||
type python: (widget['format'] == 3) and 'password' or 'text'"/>
|
||||
<tal:comment replace="nothing">Display a captcha if required</tal:comment>
|
||||
<tal:captcha condition="python: widget['format'] == 4">
|
||||
<span tal:define="challenge python: contextObj.getCaptchaChallenge(name)"
|
||||
tal:content="python: _('captcha_text', mapping=challenge)">
|
||||
</span>
|
||||
</tal:captcha>
|
||||
</tal:line>
|
||||
<tal:textarea condition="python: fmt in (1,2)">
|
||||
<textarea tal:attributes="id name; name name;
|
||||
|
|
|
@ -19,10 +19,11 @@ def createObject(folder, id, className, appName, wf=True):
|
|||
obj.id = id
|
||||
obj._at_uid = id
|
||||
userId = obj.getUser().getId()
|
||||
obj.creator = userId
|
||||
# If user is anonymous, userIs is None
|
||||
obj.creator = userId or 'Anonymous User'
|
||||
from DateTime import DateTime
|
||||
obj.created = DateTime()
|
||||
obj.__ac_local_roles__ = { userId: ['Owner'] }
|
||||
obj.__ac_local_roles__ = { userId: ['Owner'] } # userId can be None (anon).
|
||||
if wf: obj.notifyWorkflowCreated()
|
||||
return obj
|
||||
|
||||
|
|
|
@ -70,5 +70,9 @@ class GroupWrapper(AbstractWrapper):
|
|||
self.log('User "%s" added to group "%s".' % \
|
||||
(user.login, self.login))
|
||||
if hasattr(self.o.aq_base, '_oldUsers'): del self.o._oldUsers
|
||||
# If the group was created by an Anonymous, Anonymous can't stay Owner
|
||||
# of the object.
|
||||
if None in self.o.__ac_local_roles__:
|
||||
del self.o.__ac_local_roles__[None]
|
||||
return self._callCustom('onEdit', created)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -67,9 +67,13 @@ class UserWrapper(AbstractWrapper):
|
|||
# Updates roles at the Zope level.
|
||||
zopeUser = aclUsers.getUserById(login)
|
||||
zopeUser.roles = self.roles
|
||||
# "self" must be owned by its Zope user
|
||||
# "self" must be owned by its Zope user.
|
||||
if 'Owner' not in self.o.get_local_roles_for_userid(login):
|
||||
self.o.manage_addLocalRoles(login, ('Owner',))
|
||||
# If the user was created by an Anonymous, Anonymous can't stay Owner
|
||||
# of the object.
|
||||
if None in self.o.__ac_local_roles__:
|
||||
del self.o.__ac_local_roles__[None]
|
||||
return self._callCustom('onEdit', created)
|
||||
|
||||
def getZopeUser(self):
|
||||
|
|
|
@ -56,7 +56,7 @@ class AbstractWrapper(object):
|
|||
elif name == 'user':
|
||||
return self.o.getUser()
|
||||
elif name == 'appyUser':
|
||||
return self.search('User', login=self.o.getUser().getId())[0]
|
||||
return self.search1('User', login=self.o.getUser().getId())
|
||||
elif name == 'fields': return self.o.getAllAppyTypes()
|
||||
# Now, let's try to return a real attribute.
|
||||
res = object.__getattribute__(self, name)
|
||||
|
|
|
@ -198,6 +198,7 @@ class Converter:
|
|||
# Loads the document to convert in a new hidden frame
|
||||
prop = PropertyValue(); prop.Name = 'Hidden'; prop.Value = True
|
||||
if self.inputType == 'csv':
|
||||
# Give some additional params if we need to open a CSV file
|
||||
prop2 = PropertyValue()
|
||||
prop2.Name = 'FilterFlags'
|
||||
prop2.Value = '59,34,76,1'
|
||||
|
@ -206,7 +207,6 @@ class Converter:
|
|||
props = (prop, prop2)
|
||||
else:
|
||||
props = (prop,)
|
||||
# Give some additional params if we need to open a CSV file
|
||||
self.doc = self.oo.loadComponentFromURL(self.docUrl, "_blank", 0,
|
||||
props)
|
||||
if self.inputType == 'odt':
|
||||
|
|
Loading…
Reference in a new issue