- suite de la newSubmitBranch mais sans plus utiliser les slots pour y stocker
les erreurs web (pas tout testé, notamment il y a un truc cassé dans les nouveaux tests web, je vais regarder, je commit pour que la branche refuznik remerge) - re-fonctionnement quand le serveur de traductions n'est pas présent - suppression de compte ou 'compte et utilisateur' dans l'authentification - détails de style dans les templates - début de tests pour l'interface web - ... (?)
This commit is contained in:
parent
20a17031c5
commit
324136cee9
|
@ -107,6 +107,7 @@ labor-liber.net - labor-liber.net
|
|||
---------------------------------
|
||||
|
||||
- http://www.labor-liber.net
|
||||
- Bad coding style in CSS files
|
||||
|
||||
libre-entreprise - Libre-entreprise
|
||||
-----------------------------------
|
||||
|
|
|
@ -92,8 +92,8 @@ ServerPort = %(port)s + 12
|
|||
#experimental# [ContactsServer]
|
||||
#experimental# ServerPort = %(port)s + 15
|
||||
|
||||
[DataflowsServer]
|
||||
ServerPort = %(port)s + 16
|
||||
#[DataflowsServer]
|
||||
#ServerPort = %(port)s + 16
|
||||
|
||||
#experimental# [DirectoriesServer]
|
||||
#experimental# ServerPort = %(port)s + 17
|
||||
|
|
|
@ -496,7 +496,6 @@ class Application(applications.Application):
|
|||
raise apache.SERVER_RETURN, apache.HTTP_NOT_ACCEPTABLE
|
||||
|
||||
# TODO: http auth
|
||||
raise apache.SERVER_RETURN, apache.HTTP_NOT_IMPLEMENTED
|
||||
|
||||
if args.has_key('keywords'):
|
||||
del args['keywords']
|
||||
|
@ -525,11 +524,12 @@ class Application(applications.Application):
|
|||
args[nameBase] = value
|
||||
|
||||
# Handle preferences
|
||||
preferences = None
|
||||
if userToken:
|
||||
preferences = getWebForServerRole('preferences').getPreference()
|
||||
else:
|
||||
preferences = None
|
||||
context.setVar('preferences', preferences)
|
||||
try:
|
||||
preferences = getWebForServerRole('preferences').getPreference()
|
||||
except faults.UnknownServerId:
|
||||
pass
|
||||
|
||||
# Handle languages.
|
||||
languages = []
|
||||
|
@ -548,11 +548,13 @@ class Application(applications.Application):
|
|||
translationsProxy = getProxyForServerRole('translations')
|
||||
languages = [language == 'C' and 'en' or language
|
||||
for language in languages]
|
||||
if translationsProxy:
|
||||
try:
|
||||
possibleLanguages = translationsProxy.getPossibleLanguages()
|
||||
if not virtualHost.language in possibleLanguages:
|
||||
possibleLanguages.append(virtualHost.language)
|
||||
languages = [x for x in languages if x in possibleLanguages]
|
||||
except faults.UnknownServerId:
|
||||
possibleLanguages = []
|
||||
if not virtualHost.language in possibleLanguages:
|
||||
possibleLanguages.append(virtualHost.language)
|
||||
languages = [x for x in languages if x in possibleLanguages]
|
||||
if not languages:
|
||||
languages = [virtualHost.language]
|
||||
context.setVar('readLanguages', languages)
|
||||
|
|
16
make-tests
16
make-tests
|
@ -17,6 +17,16 @@ rm config && make config config-tests install \
|
|||
SERVER_USER=`id -u` SERVER_GROUP=`id -u` \
|
||||
WEB_USER=`id -u` WEB_GROUP=`id -u` &> /dev/null
|
||||
|
||||
mkdir -p root-tests/etc/apache
|
||||
mkdir -p root-tests/var/lock
|
||||
mkdir -p root-tests/var/run
|
||||
mkdir -p root-tests/var/log/apache
|
||||
PREFIX=`pwd`/root-tests \
|
||||
python -c "import os, sys; \
|
||||
print sys.stdin.read().replace( \
|
||||
'PREFIX', os.environ['PREFIX'])" < tests/httpd.conf \
|
||||
> root-tests/etc/apache/httpd.conf
|
||||
|
||||
SERVERS="Dispatcher ArticlesServer AtomsServer AuthenticationServer \
|
||||
AuthenticationLoginPasswordServer CardsServer DataflowsServer \
|
||||
GroupsServer PeopleServer VirtualHostsServer"
|
||||
|
@ -26,8 +36,10 @@ do
|
|||
root-tests/usr/local/sbin/glasnost-tests-ctl start-one $SERVER
|
||||
done
|
||||
|
||||
(cd tmp-tests && python ./tests.py)
|
||||
(cd tmp-tests && python ./tests.py)
|
||||
echo "Starting Apache server..."
|
||||
apache -f `pwd`/root-tests/etc/apache/httpd.conf
|
||||
|
||||
(cd tmp-tests && python ./tests.py)
|
||||
root-tests/usr/local/sbin/glasnost-tests-ctl stop
|
||||
kill `cat root-tests/var/run/apache.pid`
|
||||
|
||||
|
|
|
@ -430,7 +430,6 @@ class TranslationsServer(TranslationsCommonMixin, AdministrableServerMixin,
|
|||
localization.isFuzzy = (
|
||||
translation.fuzzyLocalizations is not None
|
||||
and destinationLanguage in translation.fuzzyLocalizations)
|
||||
print ' returning good translation'
|
||||
return localization.exportToXmlRpc()
|
||||
|
||||
# not translated
|
||||
|
@ -448,11 +447,9 @@ class TranslationsServer(TranslationsCommonMixin, AdministrableServerMixin,
|
|||
possibleTranslations.append(
|
||||
virtualServer.translations[sign])
|
||||
|
||||
print [x.__dict__ for x in possibleTranslations]
|
||||
oldResults = []
|
||||
for trans in possibleTranslations:
|
||||
if not trans.getDestinationString(destinationLanguage):
|
||||
print trans.getDestinationString(destinationLanguage)
|
||||
continue
|
||||
if not trans.creationTimes or \
|
||||
not trans.creationTimes.has_key(destinationLanguage):
|
||||
|
@ -470,7 +467,6 @@ class TranslationsServer(TranslationsCommonMixin, AdministrableServerMixin,
|
|||
localization.similarString = translation.sourceString
|
||||
localization.destinationString = \
|
||||
translation.destinationStrings[destinationLanguage]
|
||||
print ' returning possibly previous translation'
|
||||
return localization.exportToXmlRpc()
|
||||
|
||||
# we try to return a translation matching what's asked
|
||||
|
@ -520,7 +516,6 @@ class TranslationsServer(TranslationsCommonMixin, AdministrableServerMixin,
|
|||
similarTranslation.destinationStrings[
|
||||
destinationLanguage]
|
||||
localization.isFuzzy = 1
|
||||
print ' returning really fuzzy translation'
|
||||
return localization.exportToXmlRpc()
|
||||
|
||||
def getPossibleLanguages(self):
|
||||
|
|
|
@ -55,11 +55,16 @@ class AdminAuthenticationCommon(AdminWithoutWritersCommon):
|
|||
authenticationMethods_kind_isRequired = 1
|
||||
authenticationMethods_kind_itemKind_valueName = 'Choice'
|
||||
authenticationMethods_kind_itemKind_value_values = [
|
||||
None,
|
||||
'login-password',
|
||||
'liberty-alliance',
|
||||
'ldap',
|
||||
]
|
||||
authenticationMethods_kind_itemKind_value_labels = {
|
||||
'login-password': N_('Login & Password'),
|
||||
'x509-cert': N_('X509 Certificate'),
|
||||
'liberty-alliance': N_('Liberty Alliance Server'),
|
||||
'ldap': N_('LDAP'),
|
||||
}
|
||||
authenticationMethods_kind_requiredCount = 1
|
||||
authenticationMethods_kindName = 'Sequence'
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ class VirtualHostCommon(ObjectCommon):
|
|||
serverRole = 'virtualhosts'
|
||||
|
||||
# TODO: everything so that it is editable by user
|
||||
showTooltips = 1 # FIXME: default should be true
|
||||
showTooltips = 0 # FIXME: default should be true
|
||||
|
||||
templateDirectoryName = 'glasnost2'
|
||||
templateDirectoryName_kind_balloonHelp = N_(
|
||||
|
|
|
@ -218,6 +218,7 @@ class MissingMainRubric(BaseFault):
|
|||
|
||||
class BadEmailAddress(BaseFault):
|
||||
faultCode = faultCodeBadEmailAddress
|
||||
uiFaultString = N_('Invalid email address')
|
||||
|
||||
def makeFaultString(self, email):
|
||||
return 'Bad email address = %s' % email
|
||||
|
|
|
@ -52,6 +52,8 @@ import base64
|
|||
import copy
|
||||
import locale
|
||||
import marshal
|
||||
import re
|
||||
import string
|
||||
import time
|
||||
import types
|
||||
|
||||
|
@ -523,7 +525,7 @@ class BaseKind(things.BaseThing):
|
|||
|
||||
def checkModelValue(self, slot, value):
|
||||
assert self.isPractical
|
||||
if self.isRequired and value is None:
|
||||
if self.isRequired and (value is None or value == ''):
|
||||
raise faults.MissingSlotValue(slot)
|
||||
|
||||
def checkModelValueHolder(self, slot, valueHolder):
|
||||
|
@ -910,6 +912,13 @@ class Alias(BaseKind):
|
|||
thingPublicCategory = None # N_('Glasnost')
|
||||
|
||||
thingPublicName = N_('Alias')
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
if ' ' in value or '/' in value:
|
||||
raise faults.BadValue()
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
register(Alias)
|
||||
|
||||
|
||||
|
@ -994,6 +1003,13 @@ class Boolean(BaseKind):
|
|||
if value and value not in self.values:
|
||||
raise faults.BadSlotValue(slot, value)
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
if value in ('0', '1'):
|
||||
return int(value)
|
||||
raise faults.BadValue()
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
|
||||
def getGroupedValues(self, slot, fields):
|
||||
return (None, None) # groupNames, groupedValues
|
||||
|
||||
|
@ -1105,6 +1121,11 @@ class Choice(BaseKind):
|
|||
if value and self.values and value not in self.values:
|
||||
raise faults.BadSlotValue(slot, value)
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
|
||||
def getGroupedValues(self, slot, fields):
|
||||
if self.groupedValuesGetterName:
|
||||
currentSlot = slot
|
||||
|
@ -1209,6 +1230,11 @@ class Data(BaseKind):
|
|||
|
||||
thingPublicCategory = None # N_('Data')
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
|
||||
def exportValueToXmlRpc(self, slot, value):
|
||||
if value:
|
||||
value = base64.encodestring(value)
|
||||
|
@ -1241,6 +1267,30 @@ class Time(BaseKind):
|
|||
BaseKind.checkModelValue(self, slot, value)
|
||||
if value and type(value) not in [types.IntType, types.FloatType]:
|
||||
raise faults.BadSlotValue(slot, value)
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
if value == '':
|
||||
return None
|
||||
|
||||
formatDate = ( '%Y-%m-%d', '%y-%m-%d', '%d/%m/%Y', '%d/%m/%y' )
|
||||
formatTime = ( '%H:%M:%S', '%H:%M', '' )
|
||||
for format in [x + ' ' + y
|
||||
for x in formatDate for y in formatTime]:
|
||||
try:
|
||||
t = time.strptime(value, format)
|
||||
except ValueError:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise faults.BadValue()
|
||||
# Set Daylight Saving Time to -1, so that it is handled
|
||||
# correctly by mktime.
|
||||
t = tuple(list(t[0:-1]) + [-1])
|
||||
value = time.mktime(t)
|
||||
return value
|
||||
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
register(Time)
|
||||
|
||||
|
||||
|
@ -1282,6 +1332,11 @@ class DispatcherId(BaseKind):
|
|||
if value and type(value) is not types.StringType:
|
||||
raise faults.BadSlotValue(slot, value)
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
|
||||
def convertValueIds(
|
||||
self, slot, value, sourceDispatcherId, destinationDispatcherId):
|
||||
return value.replace(sourceDispatcherId, destinationDispatcherId)
|
||||
|
@ -1324,6 +1379,13 @@ class Email(BaseKind):
|
|||
if value and len(value) > 100:
|
||||
raise faults.BadSlotValue(slot, value)
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
if not re.match(r'^\S+@\S+$', value):
|
||||
raise faults.BadEmailAddress(value)
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
|
||||
def newJail(self, slot):
|
||||
return jails.String(slot)
|
||||
register(Email)
|
||||
|
@ -1403,6 +1465,20 @@ class Fingerprint(BaseKind):
|
|||
if value and type(value) is not types.StringType:
|
||||
raise faults.BadSlotValue(slot, value)
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
value = value.replace(' ', '')
|
||||
if len(value) != 40:
|
||||
raise faults.BadValue()
|
||||
for char in value:
|
||||
if char not in string.hexdigits:
|
||||
raise faults.BadValue()
|
||||
return '%s %s %s %s %s %s %s %s %s %s' % (
|
||||
value[ 0: 4], value[ 4: 8], value[ 8:12], value[12:16],
|
||||
value[16:20], value[20:24], value[24:28], value[28:32],
|
||||
value[32:36], value[36:40])
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
|
||||
def newJail(self, slot):
|
||||
return jails.String(slot)
|
||||
register(Fingerprint)
|
||||
|
@ -1512,8 +1588,8 @@ class Id(BaseKind):
|
|||
def convertValueIds(
|
||||
self, slot, value, sourceDispatcherId, destinationDispatcherId):
|
||||
if value is None:
|
||||
return None
|
||||
return value.replace(sourceDispatcherId, destinationDispatcherId)
|
||||
return None
|
||||
return value.replace(sourceDispatcherId, destinationDispatcherId)
|
||||
|
||||
def equals(self, kind):
|
||||
if not BaseKind.equals(self, kind):
|
||||
|
@ -1530,6 +1606,13 @@ class Id(BaseKind):
|
|||
kindServerRoles.sort()
|
||||
return serverRoles == kindServerRoles
|
||||
|
||||
def getLabels(self, slot, fields):
|
||||
values = self.getValues(slot, fields)
|
||||
from glasnost.proxy.tools import getObjectLabelsTranslated
|
||||
labels = getObjectLabelsTranslated(values,
|
||||
context.getVar('readLanguages'))
|
||||
return labels
|
||||
|
||||
def getServerRoles(self):
|
||||
return self.serverRoles
|
||||
|
||||
|
@ -2262,6 +2345,11 @@ class Password(BaseKind):
|
|||
BaseKind.checkModelValue(self, slot, value)
|
||||
if value and type(value) is not types.StringType:
|
||||
raise faults.BadSlotValue(slot, value)
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(String()):
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
register(Password)
|
||||
|
||||
|
||||
|
@ -2477,6 +2565,12 @@ class Sequence(AbstractSequence):
|
|||
itemKind.buildOptions(itemKindOptions)
|
||||
self.itemKind = itemKind
|
||||
|
||||
def checkModelValue(self, slot, value):
|
||||
AbstractSequence.checkModelValue(self, slot, value)
|
||||
### FIXME: UsersSet sets requiredCount to 1 while it's not true
|
||||
### if len(value or []) < self.requiredCount:
|
||||
### raise faults.MissingSlotValue(slot)
|
||||
|
||||
def getItemKind(self, index):
|
||||
return self.itemKind
|
||||
|
||||
|
@ -2544,6 +2638,12 @@ class AcceptedRoles(Sequence):
|
|||
thingPublicCategory = None # N_('Data')
|
||||
|
||||
thingPublicName = N_('Accepted Roles')
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(Sequence()):
|
||||
# TODO: (here?) check sequence items
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
register(AcceptedRoles)
|
||||
|
||||
|
||||
|
@ -2615,6 +2715,12 @@ class UsersSet(Sequence):
|
|||
thingPublicCategory = N_('People Set')
|
||||
|
||||
thingPublicName = N_('Users')
|
||||
|
||||
def convertValue(self, value, otherKind):
|
||||
if otherKind.equals(Sequence()):
|
||||
# TODO: (here?) check sequence items
|
||||
return value
|
||||
return BaseKind.convertValue(self, value, otherKind)
|
||||
register(UsersSet)
|
||||
|
||||
|
||||
|
|
|
@ -94,7 +94,6 @@ class BaseSlot:
|
|||
"""
|
||||
|
||||
container = None
|
||||
error = None
|
||||
parent = None
|
||||
value = None
|
||||
|
||||
|
@ -181,8 +180,8 @@ class BaseSlot:
|
|||
raise NotImplementedError
|
||||
|
||||
def getObject(self):
|
||||
if self.container is not None \
|
||||
and self.container.thingCategory == 'object':
|
||||
if self.container is not None and \
|
||||
self.container.thingCategory in ('object', 'authentication'):
|
||||
return self.container
|
||||
elif self.parent is not None:
|
||||
return self.parent.getObject()
|
||||
|
@ -190,8 +189,8 @@ class BaseSlot:
|
|||
return None
|
||||
|
||||
def getObjectSlot(self):
|
||||
if self.container is not None \
|
||||
and self.container.thingCategory == 'object':
|
||||
if self.container is not None and \
|
||||
self.container.thingCategory in ('object', 'authentication'):
|
||||
return self.parent
|
||||
elif self.parent is not None:
|
||||
return self.parent.getObjectSlot()
|
||||
|
@ -235,12 +234,6 @@ class BaseSlot:
|
|||
def newJail(self):
|
||||
return self.getKind().newJail(self)
|
||||
|
||||
def setError(self, error):
|
||||
self.error = error
|
||||
|
||||
def delError(self):
|
||||
self.error = None
|
||||
|
||||
def setField(self, fields, value):
|
||||
fieldName = self.getFieldName()
|
||||
fields[fieldName] = value
|
||||
|
|
|
@ -366,12 +366,8 @@ class BaseThing:
|
|||
container = self
|
||||
else:
|
||||
container = None
|
||||
import glasnost
|
||||
if glasnost.newSubmitBranch and self.slots.has_key(attributeName):
|
||||
return self.slots[attributeName]
|
||||
slot = slots.Attribute(
|
||||
attributeName, container = container, parent = parentSlot)
|
||||
self.slots[attributeName] = slot
|
||||
return slot
|
||||
|
||||
def getSlotByPath(self, path, parentSlot = None):
|
||||
|
|
|
@ -293,10 +293,14 @@ def sendMail(mailFrom, mailTo, mailSubject, mailMessage, mailPerson = None,
|
|||
"""
|
||||
|
||||
if type(mailTo) in [types.StringType, types.UnicodeType]:
|
||||
mailToStr = mailTo
|
||||
mailTo = [mailTo]
|
||||
else:
|
||||
mailToStr = ', '.join(mailTo)
|
||||
|
||||
mailTo = [x for x in mailTo if not x.endswith('@example.com')]
|
||||
if len(mailTo) == 0:
|
||||
return
|
||||
|
||||
mailToStr = ', '.join(mailTo)
|
||||
|
||||
mailHeader = "From: %s\nTo: %s\nSubject: %s\nMime-Type: 1.0\n" % (
|
||||
mailFrom, mailToStr, mailSubject)
|
||||
if moreHeaders:
|
||||
|
|
|
@ -534,7 +534,10 @@ class idUrl(rootUrl):
|
|||
id = '%s/%s' % (applicationId, self.localId)
|
||||
else:
|
||||
id = applicationId
|
||||
alias = pageNamesProxy.getNameByMappedId(id)
|
||||
try:
|
||||
alias = pageNamesProxy.getNameByMappedId(id)
|
||||
except faults.UnknownServerId:
|
||||
alias = None
|
||||
if alias:
|
||||
return aliasUrl(alias, self.action).getPath()
|
||||
|
||||
|
@ -562,7 +565,10 @@ class idUrl(rootUrl):
|
|||
id = '%s/%s' % (applicationId, self.localId)
|
||||
else:
|
||||
id = applicationId
|
||||
alias = pageNamesProxy.getNameByMappedId(id)
|
||||
try:
|
||||
alias = pageNamesProxy.getNameByMappedId(id)
|
||||
except faults.UnknownServerId:
|
||||
alias = None
|
||||
if alias:
|
||||
return aliasUrl(alias, self.action).getPath()
|
||||
|
||||
|
|
|
@ -73,11 +73,12 @@ class SessionsProxy(Proxy):
|
|||
def getSession(self, objectToken, ipAddress, serverId = None):
|
||||
userToken = context.getVar('userToken')
|
||||
serverId = self.getServerId(serverId = serverId)
|
||||
return callServer(
|
||||
object = callServer(
|
||||
serverId,
|
||||
'getObject',
|
||||
[serverId, getApplicationToken(), userToken, objectToken,
|
||||
ipAddress])
|
||||
return object
|
||||
|
||||
def newSession(self, ipAddress, serverId = None):
|
||||
userToken = context.getVar('userToken')
|
||||
|
|
|
@ -92,6 +92,14 @@ class Article(ObjectWebMixin, Article):
|
|||
title_kind_widget_size = 40
|
||||
title_kind_widgetName = 'InputText'
|
||||
|
||||
# this is all it takes to get a list of checkbox to select authors
|
||||
#
|
||||
#authorsSet_kind_widgetName = 'MultiCheck'
|
||||
#authorsSet_kind_itemKind_value_valuesGetterName = 'authorsSetValues'
|
||||
#
|
||||
#def authorsSetValues(self, slot, fields):
|
||||
# return getProxyForServerRole('people').getObjectIds()
|
||||
|
||||
def getEditLayout(self, fields, parentSlot = None):
|
||||
formatSlot = self.getSlot('format', parentSlot = parentSlot)
|
||||
formatValue = formatSlot.getField(fields, default = 'spip')
|
||||
|
@ -318,13 +326,6 @@ class ArticlesWeb(ObjectsWebMixin, ArticlesProxy):
|
|||
'source', X.idUrl(object.id, 'source'))
|
||||
return layout
|
||||
|
||||
def getViewOtherActionButtonsBarLayout(self, object, fields):
|
||||
layout = X.array()
|
||||
layout += ObjectsWebMixin.getViewOtherActionButtonsBarLayout(
|
||||
self, object, fields)
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
return layout
|
||||
|
||||
def history(self, id):
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
|
|
|
@ -71,8 +71,7 @@ class AtomsWeb(ObjectsWebMixin, AtomsProxy):
|
|||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
if glasnost.newSubmitBranch:
|
||||
nameSlot = object.getSlot('name')
|
||||
nameSlot.setError(f)
|
||||
object.setError('name', f)
|
||||
return self.edit(object = object, **keywords)
|
||||
keywords['name_error'] = 'valueAlreadyUsed'
|
||||
uri = X.actionUrl('edit')
|
||||
|
@ -86,8 +85,7 @@ class AtomsWeb(ObjectsWebMixin, AtomsProxy):
|
|||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
if glasnost.newSubmitBranch:
|
||||
nameSlot = object.getSlot('name')
|
||||
nameSlot.setError(f)
|
||||
object.setError('name', f)
|
||||
return self.edit(object = object, **keywords)
|
||||
keywords['name_error'] = 'valueAlreadyUsed'
|
||||
uri = X.actionUrl('edit')
|
||||
|
|
|
@ -54,7 +54,8 @@ import glasnost.common.tools_new as commonTools
|
|||
|
||||
from glasnost.proxy.AuthenticationLdapProxy import *
|
||||
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, WebMixin, BaseObjectWebMixin
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, \
|
||||
AdministrableWebMixin, BaseObjectWebMixin
|
||||
from tools import *
|
||||
|
||||
class AdminAuthenticationLdap(AdminWithoutWritersMixin,
|
||||
|
@ -74,112 +75,7 @@ class AccountLdap(BaseObjectWebMixin, AccountLdap):
|
|||
return slotNames
|
||||
register(AccountLdap)
|
||||
|
||||
class AuthenticationLdapWeb(WebMixin, AuthenticationLdapProxy):
|
||||
# FIXME: admin*() should be in a AdministrableWebMixin class
|
||||
|
||||
def admin(self):
|
||||
context.push(_level = 'admin',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if not self.canGetAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
keywords = {}
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getViewLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
layout += buttonsBar
|
||||
if self.canModifyAdmin():
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonStandalone(
|
||||
'edit', X.actionUrl('adminEdit'))
|
||||
finally:
|
||||
context.pull(_level = 'admin')
|
||||
return writePageLayout(layout, _('Authentication (LDAP) Settings'))
|
||||
admin.isPublicForWeb = 1
|
||||
|
||||
def adminEdit(self, again = '', error = '', **keywords):
|
||||
context.push(_level = 'adminEdit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'),
|
||||
layoutMode = 'edit')
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if not again:
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getErrorLayout(error, keywords)
|
||||
form = X.form(
|
||||
action = X.actionUrl('adminSubmit'),
|
||||
enctype= 'multipart/form-data', method = 'post')
|
||||
layout += form
|
||||
form += admin.getEditLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
form += buttonsBar
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
||||
finally:
|
||||
context.pull(_level = 'adminEdit')
|
||||
return writePageLayout(layout, _('Editing Authentication Settings'))
|
||||
adminEdit.isPublicForWeb = 1
|
||||
|
||||
def adminSubmit(self, **keywords):
|
||||
uri = None
|
||||
context.push(_level = 'adminSubmit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if isButtonSelected('applyButton', keywords):
|
||||
keywords['again'] = '1'
|
||||
keywords['hideErrors'] = '1'
|
||||
admin = self.newAdmin(keywords)
|
||||
admin.submitFields(keywords)
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
try:
|
||||
self.modifyAdmin(admin)
|
||||
except faults.WrongVersion:
|
||||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
keywords['versionError'] = '1'
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
except:
|
||||
if context.getVar('debug'):
|
||||
raise
|
||||
return accessForbidden()
|
||||
uri = X.actionUrl('admin')
|
||||
# The redirect(uri) will be returned by the finally instruction.
|
||||
finally:
|
||||
context.pull(_level = 'adminSubmit')
|
||||
if uri:
|
||||
return redirect(uri)
|
||||
adminSubmit.isPublicForWeb = 1
|
||||
|
||||
class AuthenticationLdapWeb(AdministrableWebMixin, AuthenticationLdapProxy):
|
||||
def login(self, nextUri = '', access = '', again = '', error = '', **keywords):
|
||||
return failure(
|
||||
X.asIs('LDAP is not enabled in this release'), X.rootUrl())
|
||||
|
@ -255,8 +151,7 @@ class AuthenticationLdapWeb(WebMixin, AuthenticationLdapProxy):
|
|||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
if glasnost.newSubmitBranch:
|
||||
loginSlot = authObject.getSlot('login')
|
||||
loginSlot.setError(f)
|
||||
authObject.setError('login', f)
|
||||
return self.login(object = authObject, **keywords)
|
||||
keywords['login_error'] = 'wrongValue'
|
||||
uri = X.roleUrl(self.serverRole, 'login')
|
||||
|
@ -267,8 +162,7 @@ class AuthenticationLdapWeb(WebMixin, AuthenticationLdapProxy):
|
|||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
if glasnost.newSubmitBranch:
|
||||
passwordSlot = authObject.getSlot('password')
|
||||
passwordSlot.setError(f)
|
||||
authObject.setError('password', f)
|
||||
return self.login(object = authObject, **keywords)
|
||||
keywords['password_error'] = 'wrongValue'
|
||||
uri = X.roleUrl(self.serverRole, 'login')
|
||||
|
|
|
@ -56,7 +56,8 @@ import glasnost.common.tools_new as commonTools
|
|||
|
||||
from glasnost.proxy.AuthenticationLibertyAllianceProxy import *
|
||||
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, WebMixin, BaseObjectWebMixin
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, \
|
||||
AdministrableWebMixin, BaseObjectWebMixin
|
||||
from tools import *
|
||||
|
||||
class AdminAuthenticationLibertyAlliance(AdminWithoutWritersMixin,
|
||||
|
@ -69,113 +70,8 @@ class AccountLibertyAlliance(BaseObjectWebMixin, AccountLibertyAlliance):
|
|||
pass
|
||||
register(AccountLibertyAlliance)
|
||||
|
||||
class AuthenticationLibertyAllianceWeb(WebMixin,
|
||||
class AuthenticationLibertyAllianceWeb(AdministrableWebMixin,
|
||||
AuthenticationLibertyAllianceProxy):
|
||||
|
||||
def admin(self):
|
||||
context.push(_level = 'admin',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if not self.canGetAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
keywords = {}
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getViewLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
layout += buttonsBar
|
||||
if self.canModifyAdmin():
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonStandalone(
|
||||
'edit', X.actionUrl('adminEdit'))
|
||||
finally:
|
||||
context.pull(_level = 'admin')
|
||||
return writePageLayout(layout,
|
||||
_('Authentication (Liberty Alliance) Settings'))
|
||||
admin.isPublicForWeb = 1
|
||||
|
||||
def adminEdit(self, again = '', error = '', **keywords):
|
||||
context.push(_level = 'adminEdit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'),
|
||||
layoutMode = 'edit')
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if not again:
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getErrorLayout(error, keywords)
|
||||
form = X.form(
|
||||
action = X.actionUrl('adminSubmit'),
|
||||
enctype= 'multipart/form-data', method = 'post')
|
||||
layout += form
|
||||
form += admin.getEditLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
form += buttonsBar
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
||||
finally:
|
||||
context.pull(_level = 'adminEdit')
|
||||
return writePageLayout(layout, _('Editing Authentication Settings'))
|
||||
adminEdit.isPublicForWeb = 1
|
||||
|
||||
def adminSubmit(self, **keywords):
|
||||
uri = None
|
||||
context.push(_level = 'adminSubmit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if isButtonSelected('applyButton', keywords):
|
||||
keywords['again'] = '1'
|
||||
keywords['hideErrors'] = '1'
|
||||
admin = self.newAdmin(keywords)
|
||||
admin.submitFields(keywords)
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
try:
|
||||
self.modifyAdmin(admin)
|
||||
except faults.WrongVersion:
|
||||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
keywords['versionError'] = '1'
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
except:
|
||||
if context.getVar('debug'):
|
||||
raise
|
||||
return accessForbidden()
|
||||
uri = X.actionUrl('admin')
|
||||
# The redirect(uri) will be returned by the finally instruction.
|
||||
finally:
|
||||
context.pull(_level = 'adminSubmit')
|
||||
if uri:
|
||||
return redirect(uri)
|
||||
adminSubmit.isPublicForWeb = 1
|
||||
|
||||
def idpAnswer(self, **keywords):
|
||||
if not keywords:
|
||||
keywords = {}
|
||||
|
|
|
@ -54,7 +54,8 @@ import glasnost.common.tools_new as commonTools
|
|||
|
||||
from glasnost.proxy.AuthenticationLoginPasswordProxy import *
|
||||
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, WebMixin, BaseObjectWebMixin
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, \
|
||||
AdministrableWebMixin, BaseObjectWebMixin
|
||||
from tools import *
|
||||
|
||||
class AdminAuthenticationLoginPassword(AdminWithoutWritersMixin,
|
||||
|
@ -76,12 +77,13 @@ register(AdminAuthenticationLoginPassword)
|
|||
class AccountLoginPassword(BaseObjectWebMixin, AccountLoginPassword):
|
||||
login_kind_balloonHelp = N_('Enter the username you use on this site.')
|
||||
password_kind_balloonHelp = N_('Enter your password.')
|
||||
login = 0
|
||||
|
||||
skipPassword = 1
|
||||
|
||||
def getEditLayoutSlotNames(self, fields, parentSlot = None):
|
||||
slotNames = BaseObjectWebMixin.getEditLayoutSlotNames(self,
|
||||
fields, parentSlot = parentSlot)
|
||||
if not self.login:
|
||||
if self.skipPassword:
|
||||
slotNames.remove('password')
|
||||
return slotNames
|
||||
register(AccountLoginPassword)
|
||||
|
@ -153,112 +155,8 @@ class ChangingUserPassword(BaseObjectWebMixin, ObjectCommon):
|
|||
|
||||
|
||||
|
||||
class AuthenticationLoginPasswordWeb(WebMixin,
|
||||
class AuthenticationLoginPasswordWeb(AdministrableWebMixin,
|
||||
AuthenticationLoginPasswordProxy):
|
||||
def admin(self):
|
||||
context.push(_level = 'admin',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if not self.canGetAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
keywords = {}
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getViewLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
layout += buttonsBar
|
||||
if self.canModifyAdmin():
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonStandalone(
|
||||
'edit', X.actionUrl('adminEdit'))
|
||||
finally:
|
||||
context.pull(_level = 'admin')
|
||||
return writePageLayout(layout,
|
||||
_('Authentication (Login/Password) Settings'))
|
||||
admin.isPublicForWeb = 1
|
||||
|
||||
def adminEdit(self, again = '', error = '', **keywords):
|
||||
context.push(_level = 'adminEdit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'),
|
||||
layoutMode = 'edit')
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if not again:
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getErrorLayout(error, keywords)
|
||||
form = X.form(
|
||||
action = X.actionUrl('adminSubmit'),
|
||||
enctype= 'multipart/form-data', method = 'post')
|
||||
layout += form
|
||||
form += admin.getEditLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
form += buttonsBar
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
||||
finally:
|
||||
context.pull(_level = 'adminEdit')
|
||||
return writePageLayout(layout, _('Editing Authentication Settings'))
|
||||
adminEdit.isPublicForWeb = 1
|
||||
|
||||
def adminSubmit(self, **keywords):
|
||||
uri = None
|
||||
context.push(_level = 'adminSubmit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if isButtonSelected('applyButton', keywords):
|
||||
keywords['again'] = '1'
|
||||
keywords['hideErrors'] = '1'
|
||||
admin = self.newAdmin(keywords)
|
||||
admin.submitFields(keywords)
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
try:
|
||||
self.modifyAdmin(admin)
|
||||
except faults.WrongVersion:
|
||||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
keywords['versionError'] = '1'
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
except:
|
||||
if context.getVar('debug'):
|
||||
raise
|
||||
return accessForbidden()
|
||||
uri = X.actionUrl('admin')
|
||||
# The redirect(uri) will be returned by the finally instruction.
|
||||
finally:
|
||||
context.pull(_level = 'adminSubmit')
|
||||
if uri:
|
||||
return redirect(uri)
|
||||
adminSubmit.isPublicForWeb = 1
|
||||
|
||||
def changePassword(self, again = '', error = '', **keywords):
|
||||
if not self.getAdmin().userCanChoosePassword:
|
||||
return accessForbidden()
|
||||
|
@ -268,8 +166,7 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
passwordChange.initFields(keywords)
|
||||
passwordChange.repairFields(keywords)
|
||||
|
||||
context.push(_level = 'index', layoutMode = 'edit',
|
||||
authMode = 'login')
|
||||
context.push(_level = 'index', layoutMode = 'edit')
|
||||
try:
|
||||
layout = X.array()
|
||||
layout += passwordChange.getErrorLayout(error, keywords)
|
||||
|
@ -345,8 +242,7 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
passwordChange.initFields(keywords)
|
||||
passwordChange.repairFields(keywords)
|
||||
|
||||
context.push(_level = 'index', layoutMode = 'edit',
|
||||
authMode = 'login')
|
||||
context.push(_level = 'index', layoutMode = 'edit')
|
||||
try:
|
||||
layout = X.array()
|
||||
layout += passwordChange.getErrorLayout(error, keywords)
|
||||
|
@ -397,24 +293,62 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
X.roleUrl('authentication'))
|
||||
changeUserPasswordSubmit.isPublicForWeb = 1
|
||||
|
||||
def deleteUser(self, account, **keywords):
|
||||
def delete(self, account):
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
|
||||
layout = X.array(
|
||||
X.p()(_("""Remove both the account and the user card ?""")),
|
||||
# TODO: note about the use of an user card without account
|
||||
X.div(_class = 'buttons-bar')(
|
||||
X.buttonStandalone(_('Remove Both'),
|
||||
X.actionUrl('deleteAccountAndUser/%s' % account)),
|
||||
X.buttonStandalone(_('Keep the user card'),
|
||||
X.actionUrl('deleteAccount/%s' % account)),
|
||||
X.buttonStandalone(_('Keep both'),
|
||||
X.roleUrl('authentication'))
|
||||
)
|
||||
)
|
||||
return writePageLayout(layout, _('Delete Account'), canCache = 0)
|
||||
delete.isPublicForWeb = 1
|
||||
|
||||
def deleteAccount(self, account):
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
if not keywords:
|
||||
keywords = {}
|
||||
|
||||
authObject = self.newAuthenticationObject()
|
||||
authObject.login = account
|
||||
userId = self.getAccountUserId(authObject)
|
||||
self.deleteAccount(authObject)
|
||||
|
||||
# TODO: propose to remove userId
|
||||
try:
|
||||
AuthenticationLoginPasswordProxy.deleteAccount(self, authObject)
|
||||
except faults.BaseFault: # FIXME: be more precise
|
||||
return failure(_('Failed to remove the account.'),
|
||||
X.roleUrl('authentication'))
|
||||
|
||||
return success(
|
||||
_('The account has been removed successfully.'),
|
||||
X.roleUrl('authentication'))
|
||||
deleteUser.isPublicForWeb = 1
|
||||
deleteAccount.isPublicForWeb = 1
|
||||
|
||||
def deleteAccountAndUser(self, account):
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
|
||||
authObject = self.newAuthenticationObject()
|
||||
authObject.login = account
|
||||
|
||||
try:
|
||||
userId = self.getAccountUserId(authObject)
|
||||
AuthenticationLoginPasswordProxy.deleteAccount(self, authObject)
|
||||
userWeb = getWeb(userId)
|
||||
userWeb.deleteObject(userId)
|
||||
except faults.BaseFault: # be more precise
|
||||
return failure(_('Failed to remove the account or the user card.'),
|
||||
X.roleUrl('authentication'))
|
||||
|
||||
return success(
|
||||
_('The account and the user card have been removed successfully.'),
|
||||
X.roleUrl('authentication'))
|
||||
deleteAccountAndUser.isPublicForWeb = 1
|
||||
|
||||
def getMenuCommands(self):
|
||||
userToken = context.getVar('userToken')
|
||||
|
@ -451,9 +385,9 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
options.append(
|
||||
X.form(
|
||||
action = X.roleUrl(self.serverRole,
|
||||
'deleteUser'),
|
||||
'delete'),
|
||||
method = 'post', enctype = 'multipart/form-data')(
|
||||
X.asIs(_('Delete user:')),
|
||||
X.asIs(_('Delete Account:')),
|
||||
accountsMenu,
|
||||
X.buttonInForm('ok', 'ok')))
|
||||
|
||||
|
@ -479,7 +413,7 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
oldAuthObject = keywords['object']
|
||||
if isinstance(oldAuthObject, authObject.__class__):
|
||||
authObject = oldAuthObject
|
||||
authObject.login = 1
|
||||
authObject.skipPassword = 0
|
||||
if not again:
|
||||
authObject.initFields(keywords)
|
||||
authObject.repairFields(keywords)
|
||||
|
@ -552,8 +486,7 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
if glasnost.newSubmitBranch:
|
||||
loginSlot = authObject.getSlot('login')
|
||||
loginSlot.setError(f)
|
||||
authObject.setError('login', f)
|
||||
return self.login(object = authObject, **keywords)
|
||||
keywords['login_error'] = 'wrongValue'
|
||||
uri = X.roleUrl(self.serverRole, 'login')
|
||||
|
@ -564,8 +497,7 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
if glasnost.newSubmitBranch:
|
||||
passwordSlot = authObject.getSlot('password')
|
||||
passwordSlot.setError(f)
|
||||
authObject.setError('password', f)
|
||||
return self.login(object = authObject, **keywords)
|
||||
keywords['password_error'] = 'wrongValue'
|
||||
uri = X.roleUrl(self.serverRole, 'login')
|
||||
|
@ -579,7 +511,6 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
loginSubmit.isPublicForWeb = 1
|
||||
|
||||
|
||||
|
||||
def newAccount(self, again = '', error = '', **keywords):
|
||||
usercardWeb = getProxyForServerRole('people')
|
||||
if not usercardWeb.canAddObject():
|
||||
|
@ -588,21 +519,31 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
if keywords is None:
|
||||
keywords = {}
|
||||
userCardObject = usercardWeb.newObject(None)
|
||||
if glasnost.newSubmitBranch and \
|
||||
keywords.has_key('userCardObject') and \
|
||||
isinstance(keywords['userCardObject'], userCardObject.__class__):
|
||||
userCardObject = keywords['userCardObject']
|
||||
del keywords['userCardObject']
|
||||
userCardSlot = slots.Root(userCardObject, name = 'userCard')
|
||||
if not again:
|
||||
userCardObject.initFields(keywords, parentSlot = userCardSlot)
|
||||
userCardObject.repairFields(keywords, parentSlot = userCardSlot)
|
||||
|
||||
authObject = self.newAuthenticationObject()
|
||||
if glasnost.newSubmitBranch and \
|
||||
keywords.has_key('authObject') and \
|
||||
isinstance(keywords['authObject'], authObject.__class__):
|
||||
authObject = keywords['authObject']
|
||||
del keywords['authObject']
|
||||
if not again:
|
||||
authObject.initFields(keywords)
|
||||
authObject.repairFields(keywords)
|
||||
|
||||
|
||||
context.push(_level = 'index', layoutMode = 'edit')
|
||||
|
||||
try:
|
||||
layout = X.array()
|
||||
layout += userCardObject.getErrorLayout(error, keywords)
|
||||
form = X.form(action = X.actionUrl('newAccountSubmit'),
|
||||
method = 'post')
|
||||
layout += form
|
||||
|
@ -636,12 +577,23 @@ class AuthenticationLoginPasswordWeb(WebMixin,
|
|||
authObject.submitFields(keywords)
|
||||
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
if glasnost.newSubmitBranch:
|
||||
return self.newAccount(
|
||||
userCardObject = userCardObject,
|
||||
authObject = authObject,
|
||||
**keywords)
|
||||
uri = X.actionUrl('newAccount')
|
||||
uri.addKeywords(keywords)
|
||||
return redirect(uri)
|
||||
|
||||
userId = usercardWeb.addObject(userCardObject)
|
||||
self.addAccount(userId, authObject)
|
||||
try:
|
||||
self.addAccount(userId, authObject)
|
||||
except faults.SmtpError:
|
||||
return failure(_("""\
|
||||
The account has successfully been created, but a SMTP error has occurred.
|
||||
Maybe the Mail Transport Agent on the Glasnost server is badly configured.\
|
||||
"""), X.rootUrl())
|
||||
|
||||
return redirect(X.idUrl(userId))
|
||||
newAccountSubmit.isPublicForWeb = 1
|
||||
|
|
|
@ -54,7 +54,7 @@ import glasnost.common.tools_new as commonTools
|
|||
|
||||
from glasnost.proxy.AuthenticationProxy import *
|
||||
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, WebMixin
|
||||
from ObjectsWeb import register, AdminWithoutWritersMixin, AdministrableWebMixin
|
||||
from tools import *
|
||||
|
||||
import widgets
|
||||
|
@ -70,12 +70,6 @@ class AuthAdminWidget(widgets.Select):
|
|||
register(AuthAdminWidget)
|
||||
|
||||
class AdminAuthentication(AdminWithoutWritersMixin, AdminAuthentication):
|
||||
authenticationMethods_kind_itemKind_value_widget_labels = {
|
||||
'login-password': N_('Login & Password'),
|
||||
'x509-cert': N_('X509 Certificate'),
|
||||
'liberty-alliance': N_('Liberty Alliance Server'),
|
||||
'ldap': N_('LDAP'),
|
||||
}
|
||||
authenticationMethods_kind_itemKind_value_widgetName = 'AuthAdminWidget'
|
||||
authenticationMethods_kind_widget_fieldLabel = N_('Authentication Methods')
|
||||
#authenticationMethods_kind_widgetName = 'MultiCheck'
|
||||
|
@ -99,110 +93,7 @@ class NewAccountPerson:
|
|||
return slotNames
|
||||
|
||||
|
||||
class AuthenticationWeb(WebMixin, AuthenticationProxy):
|
||||
def admin(self):
|
||||
context.push(_level = 'admin',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if not self.canGetAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
keywords = {}
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getViewLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
layout += buttonsBar
|
||||
if self.canModifyAdmin():
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonStandalone(
|
||||
'edit', X.actionUrl('adminEdit'))
|
||||
finally:
|
||||
context.pull(_level = 'admin')
|
||||
return writePageLayout(layout, _('Authentication Settings'))
|
||||
admin.isPublicForWeb = 1
|
||||
|
||||
def adminEdit(self, again = '', error = '', **keywords):
|
||||
context.push(_level = 'adminEdit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'),
|
||||
layoutMode = 'edit')
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if not again:
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
||||
layout = X.array()
|
||||
layout += admin.getErrorLayout(error, keywords)
|
||||
form = X.form(
|
||||
action = X.actionUrl('adminSubmit'),
|
||||
enctype= 'multipart/form-data', method = 'post')
|
||||
layout += form
|
||||
form += admin.getEditLayout(keywords)
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
form += buttonsBar
|
||||
actionButtonsBar = X.span(_class = 'action-buttons-bar')
|
||||
buttonsBar += actionButtonsBar
|
||||
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
||||
finally:
|
||||
context.pull(_level = 'adminEdit')
|
||||
return writePageLayout(layout, _('Editing Authentication Settings'))
|
||||
adminEdit.isPublicForWeb = 1
|
||||
|
||||
def adminSubmit(self, **keywords):
|
||||
uri = None
|
||||
context.push(_level = 'adminSubmit',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
try:
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
if not self.isAdmin():
|
||||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if isButtonSelected('applyButton', keywords):
|
||||
keywords['again'] = '1'
|
||||
keywords['hideErrors'] = '1'
|
||||
admin = self.newAdmin(keywords)
|
||||
admin.submitFields(keywords)
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
try:
|
||||
self.modifyAdmin(admin)
|
||||
except faults.WrongVersion:
|
||||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
keywords['versionError'] = '1'
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
# instruction.
|
||||
except:
|
||||
if context.getVar('debug'):
|
||||
raise
|
||||
return accessForbidden()
|
||||
uri = X.actionUrl('admin')
|
||||
# The redirect(uri) will be returned by the finally instruction.
|
||||
finally:
|
||||
context.pull(_level = 'adminSubmit')
|
||||
if uri:
|
||||
return redirect(uri)
|
||||
adminSubmit.isPublicForWeb = 1
|
||||
|
||||
class AuthenticationWeb(AdministrableWebMixin, AuthenticationProxy):
|
||||
def canViewAll(self, serverId = None):
|
||||
return 1
|
||||
|
||||
|
@ -293,7 +184,7 @@ class AuthenticationWeb(WebMixin, AuthenticationProxy):
|
|||
|
||||
def viewAll(self):
|
||||
admin = self.getAdmin()
|
||||
labels = admin.authenticationMethods_kind_itemKind_value_widget_labels
|
||||
labels = admin.authenticationMethods_kind_itemKind_value_labels
|
||||
|
||||
layout = X.array()
|
||||
if not context.getVar('userId'):
|
||||
|
@ -308,11 +199,13 @@ class AuthenticationWeb(WebMixin, AuthenticationProxy):
|
|||
authMethod)
|
||||
if not authenticationMethodProxy:
|
||||
continue
|
||||
layout += X.h3(_(labels[authMethod]))
|
||||
try:
|
||||
accounts = authenticationMethodProxy.getAccounts()
|
||||
except faults.UserAccessDenied:
|
||||
accounts = None
|
||||
except faults.UnknownServerId:
|
||||
continue
|
||||
layout += X.h3(_(labels[authMethod]))
|
||||
if accounts:
|
||||
table = X.table(_class = 'objects-table')
|
||||
layout += table
|
||||
|
@ -320,20 +213,11 @@ class AuthenticationWeb(WebMixin, AuthenticationProxy):
|
|||
table += tr
|
||||
tr += X.th(_('User'))
|
||||
tr += X.th(_('Account Info'))
|
||||
#if self.isAdmin():
|
||||
# tr += X.th()
|
||||
for userId, account in accounts.items():
|
||||
tr = X.tr()
|
||||
table += tr
|
||||
tr += X.td(X.objectHypertextLabel(userId))
|
||||
tr += X.td(X.asIs(account.getLabel()))
|
||||
#if self.isAdmin():
|
||||
# action = 'deleteAccount/%s/%s' % (
|
||||
# authMethod,
|
||||
# account.getUniquePart())
|
||||
# deleteUrl = X.actionUrl(action)
|
||||
# tr += X.td(X.buttonStandalone(
|
||||
# 'delete', X.actionUrl(action)))
|
||||
|
||||
authWeb = getWebForServerRole(authenticationMethodProxy.serverRole)
|
||||
authCommands = authWeb.getMenuCommands()
|
||||
|
|
|
@ -184,7 +184,7 @@ class WebMixin(things.ThingMixin):
|
|||
thingCategory = 'web'
|
||||
|
||||
|
||||
class ObjectsWebMixin(WebMixin):
|
||||
class AdministrableWebMixin(WebMixin):
|
||||
def admin(self):
|
||||
context.push(_level = 'admin',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
|
@ -228,6 +228,12 @@ class ObjectsWebMixin(WebMixin):
|
|||
return accessForbidden()
|
||||
admin = self.getAdmin()
|
||||
|
||||
if glasnost.newSubmitBranch and \
|
||||
keywords.has_key('adminObject') and \
|
||||
isinstance(keywords['adminObject'], admin.__class__):
|
||||
admin = keywords['adminObject']
|
||||
del keywords['adminObject']
|
||||
|
||||
if not again:
|
||||
admin.makeFieldsFromInstance(keywords)
|
||||
admin.repairFields(keywords)
|
||||
|
@ -268,6 +274,8 @@ class ObjectsWebMixin(WebMixin):
|
|||
admin = self.newAdmin(keywords)
|
||||
admin.submitFields(keywords)
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
if glasnost.newSubmitBranch:
|
||||
return self.adminEdit(adminObject = admin, **keywords)
|
||||
uri = X.actionUrl('adminEdit')
|
||||
uri.addKeywords(keywords)
|
||||
return # The redirect(uri) will be returned by the finally
|
||||
|
@ -294,6 +302,8 @@ class ObjectsWebMixin(WebMixin):
|
|||
return redirect(uri)
|
||||
adminSubmit.isPublicForWeb = 1
|
||||
|
||||
|
||||
class ObjectsWebMixin(AdministrableWebMixin):
|
||||
def canViewAll(self, serverId = None):
|
||||
return hasattr(self, 'canGetObjects') \
|
||||
and self.canGetObjects(serverId = serverId)
|
||||
|
@ -392,8 +402,12 @@ class ObjectsWebMixin(WebMixin):
|
|||
if not self.canAddObject():
|
||||
return accessForbidden()
|
||||
object = self.newObject(keywords)
|
||||
if keywords.has_key('object'):
|
||||
object = keywords['object']
|
||||
|
||||
if glasnost.newSubmitBranch and \
|
||||
keywords.has_key('object') and \
|
||||
isinstance(keywords['object'], object.__class__):
|
||||
object = keywords['object']
|
||||
del keywords['object']
|
||||
|
||||
if id and not again:
|
||||
object.makeFieldsFromInstance(keywords)
|
||||
|
@ -443,8 +457,9 @@ class ObjectsWebMixin(WebMixin):
|
|||
if self.canDeleteObject(id):
|
||||
# TODO: what's the logic behind having this button here ?
|
||||
# (isn't it enough to have it in view mode ?)
|
||||
actionButtonsBar += X.buttonStandalone(
|
||||
'delete', X.idUrl(id, 'confirmDelete'))
|
||||
pass
|
||||
#actionButtonsBar += X.buttonStandalone(
|
||||
# 'delete', X.idUrl(id, 'confirmDelete'))
|
||||
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
||||
|
||||
return writePageLayout(layout, headerTitle)
|
||||
|
@ -674,12 +689,17 @@ class ObjectsWebMixin(WebMixin):
|
|||
|
||||
def getViewButtonsBarLayout(self, object, fields):
|
||||
layout = X.div(_class = 'buttons-bar')
|
||||
layout += X.span(_class = 'navigation-buttons-bar')(
|
||||
self.getViewNavigationButtonsBarLayout(object, fields))
|
||||
layout += X.span(_class = 'other-action-buttons-bar')(
|
||||
self.getViewOtherActionButtonsBarLayout(object, fields))
|
||||
layout += X.span(_class = 'action-buttons-bar')(
|
||||
self.getViewActionButtonsBarLayout(object, fields))
|
||||
buttons = self.getViewNavigationButtonsBarLayout(object, fields)
|
||||
if buttons:
|
||||
layout += X.span(_class = 'navigation-buttons-bar')(buttons)
|
||||
buttons = self.getViewOtherActionButtonsBarLayout(object, fields)
|
||||
if buttons:
|
||||
layout += X.span(_class = 'other-action-buttons-bar')(buttons)
|
||||
buttons = self.getViewActionButtonsBarLayout(object, fields)
|
||||
if buttons:
|
||||
layout += X.span(_class = 'action-buttons-bar')(buttons)
|
||||
if not layout.children:
|
||||
return None
|
||||
return layout
|
||||
|
||||
def getViewLeadIn(self, object, fields):
|
||||
|
@ -1019,8 +1039,7 @@ class ObjectsWebMixin(WebMixin):
|
|||
layout += widget.getModelPageBodyLayout(slot, keywords)
|
||||
pageTitle = context.getVar('pageTitle', default = None)
|
||||
layout += self.getViewAboveButtonsBarLayout(object, keywords)
|
||||
if context.getVar('userId'):
|
||||
layout += self.getViewButtonsBarLayout(object, keywords)
|
||||
layout += self.getViewButtonsBarLayout(object, keywords)
|
||||
layout += self.getViewBelowButtonsBarLayout(object, keywords)
|
||||
|
||||
context.push(
|
||||
|
|
|
@ -77,8 +77,7 @@ class PageNamesWeb(ObjectsWebMixin, PageNamesProxy):
|
|||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
if glasnost.newSubmitBranch:
|
||||
nameSlot = object.getSlot('name')
|
||||
nameSlot.setError(f)
|
||||
object.setError('name', f)
|
||||
return self.edit(object = object, **keywords)
|
||||
keywords['name_error'] = 'valueAlreadyUsed'
|
||||
uri = X.actionUrl('edit')
|
||||
|
@ -91,7 +90,9 @@ class PageNamesWeb(ObjectsWebMixin, PageNamesProxy):
|
|||
except faults.DuplicateValue:
|
||||
keywords['again'] = '1'
|
||||
keywords['error'] = '1'
|
||||
keywords['name_error'] = 'valueAlreadyUsed'
|
||||
if glasnost.newSubmitBranch:
|
||||
object.setError('name', f)
|
||||
return self.edit(object = object, **keywords)
|
||||
uri = X.actionUrl('edit')
|
||||
uri.addKeywords(keywords)
|
||||
return redirect(uri)
|
||||
|
|
|
@ -244,6 +244,8 @@ class PeopleWeb(ObjectsWebMixin, PeopleProxy):
|
|||
object.submitFields(keywords)
|
||||
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
if glasnost.newSubmitBranch:
|
||||
return self.edit(object = object, **keywords)
|
||||
uri = X.idUrl(id, 'edit')
|
||||
uri.addKeywords(keywords)
|
||||
return redirect(uri)
|
||||
|
@ -259,12 +261,6 @@ class PeopleWeb(ObjectsWebMixin, PeopleProxy):
|
|||
except faults.DuplicateFingerprint:
|
||||
error = 1
|
||||
object.getSlot('fingerprint').error = f
|
||||
except faults.SmtpError:
|
||||
return failure(_("""\
|
||||
The account has successfully been created, but a SMTP error has occurred.
|
||||
Maybe the Mail Transport Agent on the Glasnost server is badly configured.\
|
||||
"""),
|
||||
X.rootUrl())
|
||||
except:
|
||||
if context.getVar('debug'):
|
||||
raise
|
||||
|
@ -279,12 +275,12 @@ Maybe the Mail Transport Agent on the Glasnost server is badly configured.\
|
|||
return self.edit(object = object, **keywords)
|
||||
uri.addKeywords(keywords)
|
||||
return redirect(uri)
|
||||
if object.email:
|
||||
return success(
|
||||
_("""\
|
||||
The account has successfully been created.
|
||||
An email containing the password has been sent to %s.\
|
||||
""") % object.email, X.idUrl(id))
|
||||
### if object.email:
|
||||
### return success(
|
||||
### _("""\
|
||||
### The account has successfully been created.
|
||||
### An email containing the password has been sent to %s.\
|
||||
### """) % object.email, X.idUrl(id))
|
||||
else:
|
||||
try:
|
||||
self.modifyPartialObject(object, object.getSlotToModifyNames())
|
||||
|
@ -326,6 +322,10 @@ An email containing the password has been sent to %s.\
|
|||
partialObjects = self.getObjects(
|
||||
['firstName', 'lastName', 'nickname'])
|
||||
layout = X.array()
|
||||
layout += X.asIs(_("""<p>
|
||||
Note that user accounts should now be created from the
|
||||
<a href="%s">authentication page</a>.
|
||||
</p>""") % X.roleUrl('authentication'))
|
||||
ids = self.getSortedIds(partialObjects)
|
||||
layout += self.getObjectsLayout(partialObjects, ids, [])
|
||||
layout += self.getViewAllButtonsBarLayout()
|
||||
|
|
|
@ -516,7 +516,7 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
if context.getVar('debug'):
|
||||
raise
|
||||
return accessForbidden()
|
||||
return redirect(X.actionUrl().add('localizationKey', localizationKey))
|
||||
return redirect(X.actionUrl('viewAll/%s' % localizationKey))
|
||||
delete.isPublicForWeb = 1
|
||||
|
||||
def edit(self, localizationKey, sourceStringDigest, again = '',
|
||||
|
@ -531,6 +531,9 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
return accessForbidden()
|
||||
localization = self.getLocalization(localizationKey,
|
||||
sourceStringDigest)
|
||||
if keywords.has_key('localization'):
|
||||
if isinstance(keywords['localization'], localization.__class__):
|
||||
localization = keywords['localization']
|
||||
|
||||
if not again:
|
||||
localization.makeFieldsFromInstance(keywords)
|
||||
|
@ -582,9 +585,8 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
ul = X.ul()
|
||||
layout += ul
|
||||
for digest, label, wordsCount in digestsAndLabels:
|
||||
link = X.a(href = X.actionUrl('edit').add(
|
||||
'localizationKey', key).add(
|
||||
'sourceStringDigest', digest)) (label)
|
||||
link = X.a(href = X.actionUrl(
|
||||
'edit/%s/%s' % (key, digest)))(label)
|
||||
li = X.li()
|
||||
li += link
|
||||
if label[-3:] == '...':
|
||||
|
@ -615,7 +617,12 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
else:
|
||||
localization.submitFields(keywords)
|
||||
if keywords.has_key('again') and keywords['again']:
|
||||
uri = X.actionUrl('edit')
|
||||
if glasnost.newSubmitBranch:
|
||||
return self.edit(localization = localization, **keywords)
|
||||
uri = X.actionUrl(
|
||||
'edit/%s/%s' % (localizationKey, sourceStringDigest))
|
||||
del(keywords['localizationKey'])
|
||||
del(keywords['sourceStringDigest'])
|
||||
uri.addKeywords(keywords)
|
||||
return redirect(uri)
|
||||
try:
|
||||
|
@ -633,7 +640,7 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
return accessForbidden()
|
||||
if keywords.has_key('nextUri') and keywords['nextUri']:
|
||||
return redirect(keywords['nextUri'])
|
||||
return redirect(X.actionUrl().add('localizationKey', localizationKey))
|
||||
return redirect(X.actionUrl('viewAll/%s' % localizationKey))
|
||||
submit.isPublicForWeb = 1
|
||||
|
||||
def viewAll(self, localizationKey = ''):
|
||||
|
@ -651,9 +658,10 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
if localizationKey and not localizationKey in localizationKeys:
|
||||
return accessForbidden()
|
||||
if localizationKeys:
|
||||
if not localizationKey \
|
||||
if not localizationKey \
|
||||
or not localizationKey in localizationKeys:
|
||||
localizationKey = localizationKeys[0]
|
||||
localizationKey = localizationKeys[0]
|
||||
return redirect(X.actionUrl('viewAll/%s' % localizationKey))
|
||||
else:
|
||||
localizationKey = ''
|
||||
if localizationKey:
|
||||
|
@ -681,8 +689,8 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
}
|
||||
otherLocalizations.append({
|
||||
'label': labelLocalized,
|
||||
'link': X.actionUrl().add(
|
||||
'localizationKey', otherLocalizationKey),
|
||||
'link': X.actionUrl(
|
||||
'viewAll/%s' % otherLocalizationKey),
|
||||
})
|
||||
|
||||
layout = X.array()
|
||||
|
|
|
@ -195,6 +195,10 @@ class UploadFilesWeb(ObjectsWebMixin, UploadFilesProxy):
|
|||
if not self.canAddObject():
|
||||
return accessForbidden()
|
||||
object = self.newObject(keywords)
|
||||
if keywords.has_key('object') and \
|
||||
isinstance(keywords['object'], object.__class__):
|
||||
object = keywords['object']
|
||||
del keywords['object']
|
||||
|
||||
if id and not again:
|
||||
object.makeFieldsFromInstance(keywords)
|
||||
|
@ -204,8 +208,8 @@ class UploadFilesWeb(ObjectsWebMixin, UploadFilesProxy):
|
|||
if again:
|
||||
if keywords.has_key('data') and keywords['data']:
|
||||
data = keywords['data']
|
||||
if keywords.has_key('dataType') \
|
||||
and isTypeOfMimeType(keywords['dataType'], 'image'):
|
||||
if keywords.has_key('dataType') and \
|
||||
isTypeOfMimeType(keywords['dataType'], 'image'):
|
||||
uploadFileFile = cStringIO.StringIO(data)
|
||||
uploadFileObject = PILImage.open(uploadFileFile)
|
||||
width, height = uploadFileObject.size
|
||||
|
@ -251,8 +255,10 @@ class UploadFilesWeb(ObjectsWebMixin, UploadFilesProxy):
|
|||
actionButtonsBar += X.buttonInForm('create', 'createButton')
|
||||
else:
|
||||
if self.canDeleteObject(id):
|
||||
actionButtonsBar += X.buttonStandalone(
|
||||
'delete', X.idUrl(id, 'confirmDelete'))
|
||||
### see TODO note in ObjectsWeb.py
|
||||
#actionButtonsBar += X.buttonStandalone(
|
||||
# 'delete', X.idUrl(id, 'confirmDelete'))
|
||||
pass
|
||||
actionButtonsBar += X.buttonInForm('modify', 'modifyButton')
|
||||
|
||||
return writePageLayout(layout, headerTitle)
|
||||
|
|
|
@ -72,6 +72,8 @@ def register(thingClass):
|
|||
|
||||
|
||||
class ThingMixin:
|
||||
errors = None
|
||||
|
||||
def getCompactEditLayout(self, fields, parentSlot = None):
|
||||
if parentSlot is None:
|
||||
level = 'getCompactEditLayout'
|
||||
|
@ -324,7 +326,22 @@ Please backup your changes and redo the edition.\
|
|||
if kind.hasToRepairField:
|
||||
kind.repairField(slot, fields)
|
||||
|
||||
def getError(self, slotName):
|
||||
if not self.errors or not self.errors.has_key(slotName):
|
||||
return None
|
||||
return self.errors[slotName]
|
||||
|
||||
def setError(self, slotName, error):
|
||||
if not self.errors:
|
||||
self.errors = {}
|
||||
self.errors[slotName] = error
|
||||
|
||||
def delErrors(self):
|
||||
self.errors = {}
|
||||
|
||||
def submitFields(self, fields, parentSlot = None):
|
||||
self.delErrors()
|
||||
|
||||
# Change the class of the thing if needed.
|
||||
for slotName in ['thingCategory', 'thingName']:
|
||||
slot = self.getSlot(slotName, parentSlot = parentSlot)
|
||||
|
@ -355,19 +372,21 @@ Please backup your changes and redo the edition.\
|
|||
if (kind.isRequiredInEditMode or kind.isExportable()) \
|
||||
and kind.hasToSubmitField:
|
||||
import glasnost
|
||||
slot.delError()
|
||||
if glasnost.newSubmitBranch and \
|
||||
hasattr(widget, 'submit') and \
|
||||
hasattr(kind, 'convertValue'):
|
||||
if glasnost.newSubmitBranch:
|
||||
try:
|
||||
valueKind, value = widget.submit(slot, fields)
|
||||
value = kind.convertValue(value, valueKind)
|
||||
if valueKind is not None:
|
||||
value = kind.convertValue(value, valueKind)
|
||||
else:
|
||||
value = kind.defaultValue
|
||||
kind.checkModelValue(slot, value)
|
||||
except faults.BaseFault, f:
|
||||
slot.setError(f)
|
||||
self.setError(slotName, f)
|
||||
fields['again'] = '1'
|
||||
fields['error'] = '1'
|
||||
continue
|
||||
#except NotImplementedError:
|
||||
# kind.submitField(slot, fields)
|
||||
|
||||
if not slot.hasValue() or value != slot.getValue():
|
||||
slot.setValue(value)
|
||||
|
|
|
@ -217,28 +217,29 @@ def cleanUpUri(uri, trashList):
|
|||
def confirmDelete(deleteUrl, id = None, objectName = None):
|
||||
backUrl = copy.copy(deleteUrl)
|
||||
backUrl.action = None # Remove the 'delete' action.
|
||||
|
||||
if objectName is None:
|
||||
objectName = N_('entry')
|
||||
layout = X.array()
|
||||
|
||||
if not id:
|
||||
layout += X.div(_class = 'alert')(
|
||||
_('Are you sure you want to delete the %s?') % _(objectName))
|
||||
message = _('Are you sure you want to delete the %s?') % _(objectName)
|
||||
else:
|
||||
layout += X.div(_class = 'alert')(
|
||||
_('Are you sure you want to delete the %s "%s" ?') % (
|
||||
message = _('Are you sure you want to delete the %s "%s" ?') % (
|
||||
_(objectName),
|
||||
getObjectLabelTranslated(id,
|
||||
context.getVar('readLanguages')) ))
|
||||
layout += X.br()
|
||||
context.getVar('readLanguages')) )
|
||||
|
||||
layout = X.array()
|
||||
layout += X.p(_class = 'alert')(message)
|
||||
layout += X.buttonStandalone('delete', deleteUrl)
|
||||
layout += X.buttonStandalone('cancel', backUrl)
|
||||
|
||||
return writePageLayout(layout, _('Confirm Deletion'), canCache = 0)
|
||||
|
||||
|
||||
def failure(message, url):
|
||||
layout = X.array()
|
||||
layout += X.div(_class = 'alert')(message)
|
||||
layout += X.br()
|
||||
layout += X.p(_class = 'alert')(message)
|
||||
layout += X.buttonStandalone('ok', url)
|
||||
return writePageLayout(layout, _('Failure'), canCache = 0)
|
||||
|
||||
|
@ -385,6 +386,8 @@ def redirectPermanently(url):
|
|||
|
||||
def rememberObject(id):
|
||||
preferencesWeb = getWebForServerRole('preferences')
|
||||
if not preferencesWeb:
|
||||
return
|
||||
preferencesWeb.rememberId(id)
|
||||
|
||||
|
||||
|
@ -587,8 +590,7 @@ def setHttpCookie():
|
|||
|
||||
def success(message, url):
|
||||
layout = X.array()
|
||||
layout += X.div(_class = 'alert')(message)
|
||||
layout += X.br()
|
||||
layout += X.p(_class = 'alert')(message)
|
||||
layout += X.buttonStandalone('ok', url)
|
||||
return writePageLayout(layout, _('Success'), canCache = 0)
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ class WidgetMixin(things.ThingMixin):
|
|||
layout += label
|
||||
balloonHelp = slot.getKind().balloonHelp
|
||||
showToolTip = context.getVar('virtualHost').useBalloonHelp \
|
||||
and balloonHelp and self.isReadOnly(slot)
|
||||
and balloonHelp and not self.isReadOnly(slot)
|
||||
if showToolTip:
|
||||
tooltipId = 'for-tooltip-%s' % slot.getFieldName()
|
||||
layout += X.span(_class = 'field-description tooltip',
|
||||
|
@ -100,8 +100,9 @@ class WidgetMixin(things.ThingMixin):
|
|||
label.setAttribute('class', ' '.join(
|
||||
[label.getAttribute('class'), 'fullwidth']))
|
||||
|
||||
if (slot.error or slot.getFieldOption(fields, 'error')) \
|
||||
and not fields.has_key('hideErrors'):
|
||||
if slot.getObject().getError(slot.name) or (
|
||||
slot.getFieldOption(fields, 'error')) and \
|
||||
not fields.has_key('hideErrors'):
|
||||
layout.setAttribute('class', ' '.join(
|
||||
[layout.getAttribute('class'), 'error']))
|
||||
layout += cell
|
||||
|
@ -198,9 +199,10 @@ class WidgetMixin(things.ThingMixin):
|
|||
if fields.has_key('hideErrors'):
|
||||
return None
|
||||
|
||||
if slot.error:
|
||||
error = slot.getObject().getError(slot.name)
|
||||
if error:
|
||||
return X.span(_class = 'error-message')(
|
||||
_(slot.error.uiFaultString))
|
||||
_(error.uiFaultString))
|
||||
|
||||
errorCode = slot.getFieldOption(fields, 'error')
|
||||
if errorCode:
|
||||
|
@ -253,6 +255,13 @@ class WidgetMixin(things.ThingMixin):
|
|||
layout = self.getHtmlFormValue(slot, fields)
|
||||
return layout
|
||||
|
||||
def submit(self, slot, fields):
|
||||
fieldName = slot.getFieldName()
|
||||
fieldValue = str(slot.getField(fields, default = ''))
|
||||
if not fieldValue:
|
||||
return None, None
|
||||
return kinds.String(), fieldValue
|
||||
|
||||
|
||||
class InputTextMixin(WidgetMixin):
|
||||
def getHtmlViewValue(self, slot, fields, **keywords):
|
||||
|
@ -335,6 +344,8 @@ class InputTextMixin(WidgetMixin):
|
|||
def submit(self, slot, fields):
|
||||
fieldName = slot.getFieldName()
|
||||
fieldValue = str(slot.getField(fields, default = ''))
|
||||
if not fieldValue:
|
||||
return None, None
|
||||
fieldValue = fieldValue.replace('\r\n', '\n')
|
||||
fieldValue = fieldValue.replace(
|
||||
'\x91', "'").replace('\x92', "'").replace(
|
||||
|
@ -584,7 +595,6 @@ class Date(TimeMixin, proxyWidgets.Date):
|
|||
[0] * 5 + [time.localtime()[-1]] )
|
||||
except ValueError:
|
||||
raise faults.BadValue()
|
||||
|
||||
register(Date)
|
||||
|
||||
|
||||
|
@ -713,16 +723,6 @@ class Email(WidgetMixin, proxyWidgets.Email):
|
|||
X.input(name = fieldName, type = 'hidden',
|
||||
value = fieldValue),
|
||||
)
|
||||
|
||||
def submit(self, slot, fields):
|
||||
fieldName = slot.getFieldName()
|
||||
fieldValue = str(slot.getField(fields, default = ''))
|
||||
if fieldValue.count('@') != 1:
|
||||
raise faults.BadEmailAddress()
|
||||
if fieldValue.count(' ') > 0:
|
||||
raise faults.BadEmailAddress()
|
||||
return kinds.Email(), fieldValue
|
||||
|
||||
register(Email)
|
||||
|
||||
|
||||
|
@ -771,15 +771,6 @@ class InputCheckBox(WidgetMixin, proxyWidgets.InputCheckBox):
|
|||
if labels and labels.has_key(valueAsString):
|
||||
return _(labels[valueAsString])
|
||||
return None
|
||||
|
||||
def submit(self, slot, fields):
|
||||
fieldName = slot.getFieldName()
|
||||
fieldValue = str(slot.getField(fields, default = ''))
|
||||
if fieldValue in ('0', '1'):
|
||||
return kinds.Boolean(), int(fieldValue)
|
||||
if fieldValue == '':
|
||||
return None
|
||||
raise faults.BadValue()
|
||||
register(InputCheckBox)
|
||||
|
||||
|
||||
|
@ -1011,21 +1002,86 @@ class Multi(WidgetMixin, proxyWidgets.Multi):
|
|||
slot.getPath(), str(itemSlot.getKind().__dict__)))
|
||||
layout += itemWidget.getModelHiddenLayout(itemSlot, fields)
|
||||
return layout
|
||||
|
||||
def submit(self, slot, fields):
|
||||
count = 1 # FIXME: was self.fieldsCountMin
|
||||
try:
|
||||
count = max(count,
|
||||
int(slot.getFieldOption(fields, 'count', default = '0')))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
value = []
|
||||
kind = slot.getKind()
|
||||
for i in range(count):
|
||||
itemSlot = kind.getItemSlot(slot, i)
|
||||
itemWidget = itemSlot.getWidget()
|
||||
itemKind = itemSlot.getKind()
|
||||
try:
|
||||
valueKind, itemValue = itemWidget.submit(itemSlot, fields)
|
||||
if valueKind is not None:
|
||||
itemValue = itemKind.convertValue(itemValue, valueKind)
|
||||
else:
|
||||
# nothing selected; nothing added
|
||||
continue
|
||||
itemKind.checkModelValue(itemSlot, itemValue)
|
||||
except faults.BaseFault, f:
|
||||
itemSlot.getObject().setError(slot.name, f)
|
||||
fields['again'] = '1'
|
||||
fields['error'] = '1'
|
||||
continue
|
||||
value.append(itemValue)
|
||||
|
||||
addButtonName = slot.getFieldOptionName('addButton')
|
||||
if isButtonSelected(addButtonName, fields):
|
||||
count = count + 1
|
||||
slot.setFieldOption(fields, 'count', str(count))
|
||||
fields['again'] = '1'
|
||||
fields['hideErrors'] = '1'
|
||||
|
||||
return kinds.Sequence(), value
|
||||
register(Multi)
|
||||
|
||||
|
||||
class MultiCheck(WidgetMixin, proxyWidgets.MultiCheck):
|
||||
### this widget only works within newSubmitBranch
|
||||
def getHtmlViewValue(self, slot, fields, **keywords):
|
||||
kind = slot.getKind()
|
||||
layout = X.ul()
|
||||
value = slot.getValue()
|
||||
for i, v in zip(range(len(value)), value):
|
||||
itemSlot = kind.getItemSlot(slot, i)
|
||||
itemWidget = itemSlot.getWidget()
|
||||
layout += X.li(itemWidget.getHtmlValue(itemSlot, fields))
|
||||
return layout
|
||||
|
||||
def getHtmlFormValue(self, slot, fields, **keywords):
|
||||
kind = slot.getKind()
|
||||
layout = X.ul()
|
||||
for i in range(len(kind.itemKind.values)):
|
||||
values = kind.itemKind.getValues(slot, fields)
|
||||
labels = kind.itemKind.getLabels(slot, fields)
|
||||
for i in range(len(values)):
|
||||
itemSlot = kind.getItemSlot(slot, i)
|
||||
itemKind = itemSlot.getKind()
|
||||
itemWidget = itemSlot.getWidget()
|
||||
fieldName = slot.getFieldOptionName(values[i])
|
||||
attrs = {}
|
||||
if values[i] in (slot.getValue() or []):
|
||||
attrs = {'checked': 'yes'}
|
||||
layout += X.li(
|
||||
X.input(type = 'checkbox', name = kind.itemKind.values[i]),
|
||||
itemWidget.labels[kind.itemKind.values[i]])
|
||||
X.input(type = 'checkbox', name = fieldName, **attrs),
|
||||
labels[values[i]])
|
||||
return layout
|
||||
|
||||
def submit(self, slot, fields):
|
||||
kind = slot.getKind()
|
||||
value = []
|
||||
values = kind.itemKind.getValues(slot, fields)
|
||||
for i in range(len(values)):
|
||||
fieldName = slot.getFieldOptionName(values[i])
|
||||
fieldValue = slot.getFieldOption(fields, values[i])
|
||||
if fieldValue == 'on':
|
||||
value.append(values[i])
|
||||
return kinds.Sequence(), value
|
||||
register(MultiCheck)
|
||||
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ documentation est d'ailleurs bas
|
|||
|
||||
Glasnost ne respecte pas automatiquement la typographie française. C'est le
|
||||
travail du rédacteur. Par contre, il apporte une aide précieuse sur certains
|
||||
petits détails. Ainsi il remplace automatiquement les espaces situés avant les
|
||||
« ((:)) », « ((;)) », « ((!)) » , « ((?)) » & « ((»)) » par des espaces
|
||||
insécables. Il procède de même pour les espaces situé après les « ((«)) ».
|
||||
petits détails. Ainsi il remplace automatiquement les espaces situées avant les
|
||||
« ((:)) », « ((;)) », « ((!)) » , « ((?)) » et « ((»)) » par des espaces
|
||||
insécables. Il procède de même pour les espaces situées après les « ((«)) ».
|
||||
|
||||
|
||||
{{{Paragraphes}}}
|
||||
|
|
|
@ -147,7 +147,11 @@ hr {
|
|||
|
||||
div.error .label {
|
||||
color: #800;
|
||||
}
|
||||
}
|
||||
|
||||
div.error input {
|
||||
border: 1px solid #800;
|
||||
}
|
||||
|
||||
span.error-message {
|
||||
background: #ffce7b;
|
||||
|
|
|
@ -178,6 +178,7 @@ a.button, input.button {
|
|||
padding: 1px;
|
||||
text-decoration: none;
|
||||
font-size: 90%;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
a.button:hover, input.button:hover {
|
||||
|
|
|
@ -5,7 +5,7 @@ import os
|
|||
import sys
|
||||
import unittest
|
||||
|
||||
glasnostPythonDir = "/home/fred/src/glasnost/glasnost-cvs-head/root-tests/usr/local/lib/glasnost-tests"
|
||||
glasnostPythonDir = '/usr/local/lib/glasnost-tests'
|
||||
sys.path.insert(0, glasnostPythonDir)
|
||||
|
||||
import glasnost.common.applications as applications
|
||||
|
|
|
@ -0,0 +1,336 @@
|
|||
# -*- coding: iso-8859-15 -*-
|
||||
|
||||
import httpsession
|
||||
import re
|
||||
import sgmllib
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
glasnostPythonDir = '/usr/local/lib/glasnost-tests'
|
||||
sys.path.insert(0, glasnostPythonDir)
|
||||
|
||||
from GlasnostTestCase import MinimalTestCase
|
||||
|
||||
httpsession.PostRequest.follow_redirects = 1
|
||||
|
||||
# The dict function is new in Python 2.2.
|
||||
try:
|
||||
dict
|
||||
except NameError:
|
||||
def dict(sequence):
|
||||
mapping = {}
|
||||
for key, value in sequence:
|
||||
mapping[key] = value
|
||||
return mapping
|
||||
|
||||
class Form:
|
||||
def __init__(self, action = '', method = '', formId = '', **keywords):
|
||||
self.actionUrl = action
|
||||
self.method = method
|
||||
self.id = formId
|
||||
self.values = {}
|
||||
self.buttons = []
|
||||
self.selects = {}
|
||||
|
||||
def set(self, k, v):
|
||||
if not self.values.has_key(k):
|
||||
raise 'unknown key'
|
||||
self.values[k] = v
|
||||
|
||||
def setOption(self, k, v):
|
||||
if not k in self.selects.keys():
|
||||
raise 'unknown key'
|
||||
self.values[k] = self.selects[k][v]
|
||||
|
||||
def getKeywords(self, buttonClicked = None):
|
||||
if not buttonClicked:
|
||||
buttonClicked = self.buttons[0]
|
||||
else:
|
||||
buttonClicked = [x for x in self.buttons if x[0] == buttonClicked
|
||||
or x[1] == buttonClicked][0]
|
||||
self.values[buttonClicked[0]] = buttonClicked[1]
|
||||
return self.values
|
||||
|
||||
|
||||
class ParserForForms(sgmllib.SGMLParser):
|
||||
inTextArea = None
|
||||
inOption = None
|
||||
|
||||
def __init__(self, session, body):
|
||||
sgmllib.SGMLParser.__init__(self)
|
||||
self.forms = []
|
||||
self.currentForm = None
|
||||
self.session = session
|
||||
self.feed(body)
|
||||
|
||||
def start_form(self, attrs):
|
||||
attrs = dict(attrs) # require python >= 2.2
|
||||
self.currentForm = Form(**attrs)
|
||||
self.currentForm.session = self.session
|
||||
|
||||
def end_form(self):
|
||||
if not self.currentForm:
|
||||
raise 'end form while it never opened!'
|
||||
self.forms.append(self.currentForm)
|
||||
self.currentForm = None
|
||||
|
||||
def start_input(self, attrs):
|
||||
attrs = dict(attrs)
|
||||
if not self.currentForm:
|
||||
return
|
||||
if not attrs.has_key('name'):
|
||||
attrs['name'] = 'undefined'
|
||||
if attrs.has_key('type') and attrs['type'] == 'submit':
|
||||
self.currentForm.buttons.append( (attrs['name'], attrs['value']) )
|
||||
return
|
||||
self.currentForm.values[attrs['name']] = None
|
||||
if attrs.has_key('value'):
|
||||
self.currentForm.values[attrs['name']] = attrs['value']
|
||||
|
||||
def start_select(self, attrs):
|
||||
attrs = dict(attrs)
|
||||
if not self.currentForm:
|
||||
return
|
||||
self.currentForm.values[attrs['name']] = None
|
||||
self.inSelect = attrs['name']
|
||||
self.currentForm.selects[self.inSelect] = {}
|
||||
|
||||
def end_select(self):
|
||||
self.inSelect = None
|
||||
|
||||
def start_textarea(self, attrs):
|
||||
attrs = dict(attrs)
|
||||
if not self.currentForm:
|
||||
return
|
||||
if not attrs.has_key('name'):
|
||||
return
|
||||
self.currentForm.values[attrs['name']] = None
|
||||
self.inTextArea = attrs['name']
|
||||
|
||||
def end_textarea(self):
|
||||
self.inTextArea = None
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.inTextArea is not None:
|
||||
self.currentForm.values[self.inTextArea] = data
|
||||
elif self.inOption is not None:
|
||||
select = self.currentForm.selects[self.inSelect]
|
||||
current = ''
|
||||
if select.has_key(self.inOption):
|
||||
current = select[self.inOption]
|
||||
select[self.inOption] = current + ' ' + data.strip()
|
||||
|
||||
def start_option(self, attrs):
|
||||
if self.inSelect is None:
|
||||
return
|
||||
attrs = dict(attrs)
|
||||
if attrs.has_key('selected'):
|
||||
self.currentForm.values[self.inSelect] = attrs['value']
|
||||
self.inOption = attrs['value']
|
||||
|
||||
def end_option(self):
|
||||
select = self.currentForm.selects[self.inSelect]
|
||||
select[self.inOption] = select[self.inOption].strip()
|
||||
select[ select[self.inOption] ] = self.inOption
|
||||
del select[self.inOption]
|
||||
self.inOption = None
|
||||
|
||||
class ParserForLinks(sgmllib.SGMLParser):
|
||||
def __init__(self, body):
|
||||
sgmllib.SGMLParser.__init__(self)
|
||||
self.links = []
|
||||
self.inLink = 0
|
||||
self.feed(body)
|
||||
|
||||
def start_a(self, attrs):
|
||||
attrs = dict(attrs) # require python >= 2.2
|
||||
if not attrs.has_key('href'):
|
||||
return
|
||||
self.inLink = 1
|
||||
self.links.append(attrs)
|
||||
self.links[-1]['data'] = ''
|
||||
|
||||
def handle_data(self, data):
|
||||
if not self.inLink:
|
||||
return
|
||||
self.links[-1]['data'] += data
|
||||
|
||||
def end_a(self):
|
||||
try:
|
||||
self.links[-1]['data'] = self.links[-1]['data'].strip()
|
||||
except IndexError:
|
||||
# when first link has not content
|
||||
pass
|
||||
self.inLink = 0
|
||||
|
||||
|
||||
class WebTestCase(MinimalTestCase, httpsession.HTTPSession):
|
||||
pageContent = None
|
||||
path = None
|
||||
replyCode = None
|
||||
|
||||
links = None
|
||||
forms = None
|
||||
|
||||
def setUp(self):
|
||||
MinimalTestCase.setUp(self)
|
||||
httpsession.HTTPSession.__init__(self, use_cookies = 1)
|
||||
self.add_header('User-Agent', 'Glasnost/0.0 (WebTesting)')
|
||||
self.add_header('Accept-Language', 'fr')
|
||||
self.setHost('localhost:9000')
|
||||
|
||||
def setHost(self, host):
|
||||
self.protocol = 'http'
|
||||
self.hostName = host
|
||||
self.add_header('Host', self.hostName)
|
||||
|
||||
|
||||
def get(self, path):
|
||||
if path.startswith('http'):
|
||||
url = path
|
||||
else:
|
||||
url = '%s://%s%s' % (self.protocol, self.hostName, path)
|
||||
url = url.replace('&', '&') # TODO: everything
|
||||
req = httpsession.HTTPSession.get(self, url)
|
||||
self.handle_req(req)
|
||||
|
||||
def getLinkByLabel(self, label):
|
||||
try:
|
||||
return [x for x in self.links if x['data'] == label][0]['href']
|
||||
except IndexError:
|
||||
self.fail('No link with that label')
|
||||
|
||||
def post(self, path, keywords):
|
||||
if path.startswith('http'):
|
||||
url = path
|
||||
else:
|
||||
url = '%s://%s%s' % (self.protocol, self.hostName, path)
|
||||
url = url.replace('&', '&') # TODO: everything
|
||||
req = httpsession.HTTPSession.post(self, url)
|
||||
for k, v in keywords.items():
|
||||
req.add_param(k, v)
|
||||
self.handle_req(req)
|
||||
|
||||
def handle_req(self, req):
|
||||
req.getreply()
|
||||
self.replyCode = int(req.replycode)
|
||||
self.pageContent = req.getfile().read()
|
||||
self.links = ParserForLinks(self.pageContent).links
|
||||
self.forms = ParserForForms(self, self.pageContent).forms
|
||||
self.path = req.path
|
||||
|
||||
|
||||
|
||||
class BasicTestCase(WebTestCase):
|
||||
def test01_getHomepage(self):
|
||||
'''Get homepage'''
|
||||
self.get('/')
|
||||
self.failUnless(self.replyCode == 200)
|
||||
self.failUnless(self.pageContent.find('<h1>Home</h1>') > -1)
|
||||
self.failUnless(self.pageContent.find('Login') > -1)
|
||||
|
||||
def test02_getVirtualHostsPage(self):
|
||||
'''Get virtual hosts page'''
|
||||
self.get('/virtualhosts')
|
||||
self.failUnless(self.replyCode == 200)
|
||||
self.failUnless(self.pageContent.find(
|
||||
'<h1>Virtual Hosts</h1>') > -1)
|
||||
|
||||
def test03_getTheVirtualHostPage(self):
|
||||
'''Get the virtual host page'''
|
||||
self.get('/virtualhosts')
|
||||
try:
|
||||
link = [x for x in self.links if
|
||||
x['data'] == 'Glasnost' and
|
||||
re.match('/virtualhosts/\d+', x['href'])] [0]
|
||||
except IndexError:
|
||||
self.fail('No link to a virtual host object')
|
||||
self.get(link['href'])
|
||||
self.failUnless(self.replyCode == 200)
|
||||
self.failUnless(self.pageContent.find(
|
||||
'<h1>Virtual Host - Glasnost</h1>') > -1)
|
||||
|
||||
def test04_editTheVirtualHost(self):
|
||||
'''Change the language of the virtual host'''
|
||||
self.get('/virtualhosts')
|
||||
link = [x for x in self.links if
|
||||
x['data'] == 'Glasnost' and
|
||||
re.match('/virtualhosts/\d+', x['href'])] [0]
|
||||
self.get(link['href'])
|
||||
|
||||
self.get( self.getLinkByLabel('Edit') )
|
||||
self.failUnless(self.replyCode == 200)
|
||||
self.failUnless(self.pageContent.find(
|
||||
'<h1>Editing Virtual Host - Glasnost</h1>') > -1)
|
||||
|
||||
try:
|
||||
form = [x for x in self.forms if
|
||||
x.actionUrl == '/virtualhosts/submit'][0]
|
||||
except IndexError:
|
||||
self.fail('No form on this page')
|
||||
|
||||
form.setOption('language', 'French')
|
||||
self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Modify'))
|
||||
self.failUnless(self.replyCode == 200)
|
||||
self.failUnless(self.pageContent.find(
|
||||
'<h1>Hôte virtuel - Glasnost</h1>') > -1)
|
||||
|
||||
self.get( self.getLinkByLabel('Supprimer') )
|
||||
self.failUnless(
|
||||
re.match('/virtualhosts/\d+/confirmDelete', self.path) )
|
||||
self.get( self.getLinkByLabel('Supprimer') )
|
||||
|
||||
def test05_applyEveryTemplates(self):
|
||||
'''Apply every templates'''
|
||||
self.get('/virtualhosts')
|
||||
link = [x for x in self.links if
|
||||
x['data'] == 'Glasnost' and
|
||||
re.match('/virtualhosts/\d+', x['href'])] [0]
|
||||
self.get(link['href'])
|
||||
previousPageContent = self.pageContent
|
||||
|
||||
self.get( self.getLinkByLabel('Edit') )
|
||||
form = [x for x in self.forms if
|
||||
x.actionUrl == '/virtualhosts/submit'][0]
|
||||
|
||||
templateNames = form.selects['templateDirectoryName'].keys()
|
||||
for templateName in templateNames + ['Glasnost 2']:
|
||||
self.get(link['href'])
|
||||
self.get( self.getLinkByLabel('Edit') )
|
||||
form = [x for x in self.forms if
|
||||
x.actionUrl == '/virtualhosts/submit'][0]
|
||||
form.setOption('templateDirectoryName', templateName)
|
||||
self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Modify'))
|
||||
self.failUnless(self.replyCode == 200)
|
||||
self.failUnless(self.path == link['href'])
|
||||
self.failUnless(self.pageContent.find(
|
||||
'<h1>Virtual Host - Glasnost</h1>') > -1)
|
||||
self.failUnless(self.pageContent != previousPageContent)
|
||||
previousPageContent = self.pageContent
|
||||
|
||||
|
||||
class AccountManipulationTestCase(WebTestCase):
|
||||
def test01_buttonPresence(self):
|
||||
'''Look for "New Account" button'''
|
||||
self.get('/')
|
||||
self.failUnless(self.replyCode == 200)
|
||||
self.failUnless(self.pageContent.find('New Account') > -1)
|
||||
|
||||
def test02_emptyForm(self):
|
||||
'''Submit empty *new account* form'''
|
||||
self.get('/')
|
||||
self.get( self.getLinkByLabel('New Account') )
|
||||
form = [x for x in self.forms if
|
||||
x.actionUrl.endswith('/newAccountSubmit')][0]
|
||||
self.post(form.actionUrl, form.getKeywords())
|
||||
#self.fail('test not finished')
|
||||
|
||||
|
||||
suite1 = unittest.makeSuite(BasicTestCase, 'test')
|
||||
suite2 = unittest.makeSuite(AccountManipulationTestCase, 'test')
|
||||
|
||||
allTests = unittest.TestSuite((suite1, suite2))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.TextTestRunner(verbosity=2).run(allTests)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
ServerType standalone
|
||||
ServerRoot PREFIX
|
||||
LockFile PREFIX/var/lock/apache.lock
|
||||
PidFile PREFIX/var/run/apache.pid
|
||||
ScoreBoardFile PREFIX/var/run/apache.scoreboard
|
||||
ResourceConfig /dev/null
|
||||
AccessConfig /dev/null
|
||||
|
||||
LoadModule config_log_module /usr/lib/apache/1.3/mod_log_config.so
|
||||
LoadModule mime_magic_module /usr/lib/apache/1.3/mod_mime_magic.so
|
||||
LoadModule mime_module /usr/lib/apache/1.3/mod_mime.so
|
||||
LoadModule dir_module /usr/lib/apache/1.3/mod_dir.so
|
||||
LoadModule python_module /usr/lib/apache/1.3/mod_python.so
|
||||
|
||||
Port 9000
|
||||
|
||||
ServerAdmin root@localhost
|
||||
DocumentRoot PREFIX/usr/local/lib/glasnost-tests/web
|
||||
|
||||
<Directory />
|
||||
Options SymLinksIfOwnerMatch
|
||||
AllowOverride None
|
||||
</Directory>
|
||||
|
||||
AccessFileName .htaccess
|
||||
|
||||
TypesConfig /etc/mime.types
|
||||
DefaultType text/plain
|
||||
MIMEMagicFile /usr/share/misc/magic
|
||||
|
||||
UseCanonicalName On
|
||||
HostnameLookups Off
|
||||
ServerSignature On
|
||||
|
||||
LogLevel warn
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
||||
CustomLog PREFIX/var/log/apache/access.log combined
|
||||
ErrorLog PREFIX/var/log/apache/error.log
|
||||
|
||||
PythonPath "sys.path + [ 'PREFIX/usr/local/lib/glasnost-tests/web/', 'PREFIX/usr/local/lib/glasnost-tests/web/code/']"
|
||||
SetHandler python-program
|
||||
DirectoryIndex index.py index.html
|
||||
PythonHandler webhandler
|
||||
|
|
@ -0,0 +1,357 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
This module provides support for mimicking a client HTTP session that spans
|
||||
a number of requests.
|
||||
|
||||
Instances of the HTTPSession class contain a pool of cookies and standard
|
||||
headers, and allow the creation of HTTP request objects that refer to the
|
||||
session.
|
||||
|
||||
References
|
||||
|
||||
- HTTP Protocol: http://www.w3.org/Protocols/rfc1945/rfc1945
|
||||
- File upload: http://www.magres.nottingham.ac.uk/cgi-bin/rfc/1867
|
||||
"""
|
||||
|
||||
import string, httplib, urlparse, urllib, mimetools, base64
|
||||
|
||||
# If the following module (Timothy O'Malley's Cookie module) is missing, try
|
||||
# http://www.google.com/search?q=cookie.py"
|
||||
# (Python >= 2 has this module in the standard library.)
|
||||
import Cookie
|
||||
|
||||
try: from cStringIO import StringIO
|
||||
except: from StringIO import StringIO
|
||||
|
||||
__author__ = 'Steve Purcell'
|
||||
__version__ = '$Revision$'[11:-2]
|
||||
__email__ = 'stephen_purcell@yahoo.com'
|
||||
|
||||
##############################################################################
|
||||
# Query string/parameter parsing
|
||||
##############################################################################
|
||||
|
||||
def query_string_to_param_list(querystr):
|
||||
"""Take a url-encoded query string and convert to a list of tuples.
|
||||
|
||||
Each tuple is in the form (name, value), where 'value' will be None if
|
||||
the corresponding parameter in the query string was not in the form 'a=b'.
|
||||
"""
|
||||
params = []
|
||||
for param in string.split(querystr, '&'):
|
||||
if not param:
|
||||
continue
|
||||
parts = map(urllib.unquote_plus, string.split(param, '='))
|
||||
if len(parts) == 2:
|
||||
name, value = parts
|
||||
else:
|
||||
name, value = parts[0], None
|
||||
params.append((name, value))
|
||||
return params
|
||||
|
||||
def param_list_to_query_string(params):
|
||||
"""Takes a list of parameter tuples and returns a url-encoded string.
|
||||
|
||||
Each tuple is in the form (name, value), where 'value' can be None if
|
||||
the corresponding parameter in the query string is not to be in
|
||||
the form 'a=b'.
|
||||
"""
|
||||
encoded = []
|
||||
for key, value in params:
|
||||
if value is None:
|
||||
encoded.append(urllib.quote_plus(key))
|
||||
else:
|
||||
encoded.append("%s=%s" % tuple(map(urllib.quote_plus, (key, value))))
|
||||
return string.join(encoded, '&')
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Standard request classes
|
||||
##############################################################################
|
||||
|
||||
class HTTPRequestError(Exception):
|
||||
"""Error thrown in response to misuse of API or internal failure"""
|
||||
pass
|
||||
|
||||
|
||||
class HTTPRequest:
|
||||
"""Base class for HTTP requests that are made in the context of a
|
||||
session instance. Cannot be used directly.
|
||||
"""
|
||||
follow_redirects = 0
|
||||
|
||||
def __init__(self, session, url):
|
||||
self.session = session
|
||||
self.redirects = 0
|
||||
self._extra_headers = []
|
||||
self._init_request(url)
|
||||
|
||||
def _init_request(self, url):
|
||||
self.url = url
|
||||
try:
|
||||
(self.scheme, self.server, self.path, self.params,
|
||||
query, self.fragment) = urlparse.urlparse(url)
|
||||
except TypeError:
|
||||
raise HTTPRequest, 'illegal URL: %s' % url
|
||||
self.query_params = query_string_to_param_list(query)
|
||||
if not self.scheme:
|
||||
raise HTTPRequestError, 'not a full url: %s' % url
|
||||
elif self.scheme == 'http':
|
||||
self._request = httplib.HTTP()
|
||||
elif self.scheme == 'https' and hasattr(httplib, 'HTTPS'):
|
||||
self._request = httplib.HTTPS()
|
||||
else:
|
||||
raise HTTPRequestError, 'unsupported url scheme %s' % self.scheme
|
||||
if self.session.debug_level > 1:
|
||||
self._request.set_debuglevel(1)
|
||||
self._finished = 0
|
||||
|
||||
def get_query_param(self, key):
|
||||
for param, value in self.query_params:
|
||||
if param == key:
|
||||
return value
|
||||
raise KeyError, key
|
||||
|
||||
def _get_path(self):
|
||||
if self.query_params:
|
||||
query_string = param_list_to_query_string(self.query_params)
|
||||
return "%s?%s" % (self.path, query_string)
|
||||
else:
|
||||
return self.path
|
||||
|
||||
def _finish_request(self):
|
||||
if self._finished:
|
||||
return
|
||||
selector = self._get_path()
|
||||
self.session._debug("begin %s request: %s" %
|
||||
(self.request_type, selector))
|
||||
self.url = urlparse.urljoin(self.url, selector)
|
||||
self._request.connect(self.server)
|
||||
self._request.putrequest(self.request_type, selector)
|
||||
self._send_headers(self.session.get_headers_for(self.server, selector))
|
||||
self._send_headers(self._extra_headers)
|
||||
self._send_body()
|
||||
reply = self._request.getreply()
|
||||
if reply[0] == -1:
|
||||
raise HTTPRequestError, \
|
||||
'illegal response from server: %s' % reply[1]
|
||||
self.replycode, self.message, self.replyheaders = reply
|
||||
self.session._debug("got response: %s: %s" %
|
||||
(self.replycode, self.message))
|
||||
self._extract_cookies(self.replyheaders)
|
||||
if self.replycode in (301, 302) and self.follow_redirects:
|
||||
self.redirects = self.redirects + 1
|
||||
if not self.replyheaders.has_key('location'):
|
||||
raise HTTPRequestError, 'redirected, but no location in headers'
|
||||
location = self.replyheaders['location']
|
||||
self.session._debug("redirecting to: %s" % location)
|
||||
self._init_request(self.resolve_href(location))
|
||||
self._finish_request()
|
||||
self._finished = 1
|
||||
|
||||
def _send_headers(self, headers):
|
||||
for header, value in headers:
|
||||
self._send_header(header, value)
|
||||
|
||||
def _send_header(self, header, value):
|
||||
self.session._debug("sending header: %s: %s" % (header, value))
|
||||
self._request.putheader(header, value)
|
||||
|
||||
def _send_body(self):
|
||||
pass
|
||||
|
||||
def query_string(self):
|
||||
return param_list_to_query_string(self.query_params)
|
||||
|
||||
def add_query_param(self, key, value):
|
||||
self.query_params.append((key,value))
|
||||
|
||||
def add_query_params(self, dict):
|
||||
for key, value in dict.items():
|
||||
self.add_query_param(key, value)
|
||||
|
||||
def resolve_href(self, href):
|
||||
return urlparse.urljoin(self.url, href)
|
||||
|
||||
def redirect(self):
|
||||
self._finish_request()
|
||||
if self.replycode in (301, 302):
|
||||
return self.replyheaders['location']
|
||||
else:
|
||||
return None
|
||||
|
||||
add_param = add_query_param
|
||||
|
||||
def add_params(self, dict):
|
||||
for key, value in dict.items():
|
||||
self.add_param(key, value)
|
||||
|
||||
def getfile(self):
|
||||
self._finish_request()
|
||||
return self._request.getfile()
|
||||
|
||||
def _extract_cookies(self, headers):
|
||||
for cookie in headers.getallmatchingheaders('set-cookie'):
|
||||
self.session.add_cookie(self.server, cookie)
|
||||
|
||||
def getreply(self):
|
||||
self._finish_request()
|
||||
return self.replycode, self.message, self.replyheaders
|
||||
|
||||
|
||||
class GetRequest(HTTPRequest):
|
||||
request_type = 'GET'
|
||||
follow_redirects = 1
|
||||
|
||||
def _send_body(self):
|
||||
self._request.endheaders()
|
||||
|
||||
|
||||
class PostRequest(HTTPRequest):
|
||||
request_type = 'POST'
|
||||
follow_redirects = 0
|
||||
|
||||
def _init_request(self, url):
|
||||
self.post_params = []
|
||||
HTTPRequest._init_request(self, url)
|
||||
|
||||
def add_param(self, key, value):
|
||||
self.post_params.append((key, value))
|
||||
|
||||
def _send_body(self):
|
||||
self._send_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
content = param_list_to_query_string(self.post_params)
|
||||
self._send_header('Content-Length', str(len(content)))
|
||||
self._request.endheaders()
|
||||
self._request.send(content)
|
||||
|
||||
|
||||
class PostMultipartRequest(PostRequest):
|
||||
def _init_request(self, url):
|
||||
self.post_files = []
|
||||
self._boundary = '-' * 16 + mimetools.choose_boundary()
|
||||
PostRequest._init_request(self, url)
|
||||
|
||||
def add_file(self, name, filename, content_type, stream):
|
||||
self.post_files.append((name, filename, content_type, stream))
|
||||
|
||||
def _send_body(self):
|
||||
body = StringIO()
|
||||
start_boundary = '--' + self._boundary
|
||||
end_boundary = start_boundary + '--'
|
||||
for key, value in self.post_params:
|
||||
body.write(start_boundary + '\r\n')
|
||||
body.write('Content-Disposition: form-data; name="%s"\r\n' % key)
|
||||
body.write('\r\n')
|
||||
if value is not None:
|
||||
body.write(value)
|
||||
body.write('\r\n')
|
||||
|
||||
for name, filename, content_type, stream in self.post_files:
|
||||
body.write(start_boundary + '\r\n')
|
||||
body.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n'
|
||||
% (name, filename))
|
||||
body.write('Content-Type: %s\r\n' % content_type)
|
||||
#body.write('Content-Transfer-Encoding: binary\r\n')
|
||||
body.write('\r\n')
|
||||
while 1:
|
||||
data = stream.read(512)
|
||||
if not data: break
|
||||
body.write(data)
|
||||
stream.close()
|
||||
body.write('\r\n')
|
||||
body.write(end_boundary + '\r\n')
|
||||
content = body.getvalue()
|
||||
self._send_header('Content-Type', 'multipart/form-data; boundary="%s"' %
|
||||
self._boundary)
|
||||
self._send_header('Content-Length', str(len(content)))
|
||||
self._request.endheaders()
|
||||
self._request.send(content)
|
||||
body.close()
|
||||
|
||||
|
||||
class HTTPSession:
|
||||
def __init__(self, use_cookies=1, debug_level=0):
|
||||
self.cookies = Cookie.SimpleCookie()
|
||||
self.use_cookies = use_cookies
|
||||
self.debug_level = debug_level
|
||||
self.standard_headers = []
|
||||
self.authorisation = None
|
||||
|
||||
def _debug(self, msg, level=1):
|
||||
if self.debug_level >= level:
|
||||
print msg
|
||||
|
||||
def set_basic_auth(self, user, password):
|
||||
if user is None:
|
||||
self.authorisation = None
|
||||
else:
|
||||
self.authorisation = (user, password)
|
||||
|
||||
def add_header(self, header, value):
|
||||
self.standard_headers.append((header, value))
|
||||
|
||||
def add_cookie(self, server, header):
|
||||
header = string.strip(header)
|
||||
new_cookies = Cookie.SimpleCookie()
|
||||
new_cookies.load(header)
|
||||
for cookie in new_cookies.values():
|
||||
if not cookie.get('domain', None):
|
||||
cookie['domain'] = string.lower(server)
|
||||
assert len(cookie['domain']) > 0
|
||||
self.cookies.update(new_cookies)
|
||||
self._debug("added cookie: server=%s, header=%s" % (server, header))
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return apply(GetRequest, (self,) + args, kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return apply(PostRequest, (self,) + args, kwargs)
|
||||
|
||||
def post_multipart(self, *args, **kwargs):
|
||||
return apply(PostMultipartRequest, (self,) + args, kwargs)
|
||||
|
||||
def _cookie_matches(self, cookie, server, path):
|
||||
return self._domains_match(server, cookie['domain']) and \
|
||||
self._paths_match(path, cookie['path'])
|
||||
|
||||
def _domains_match(self, domain, cookie_domain):
|
||||
domain = string.lower(domain)
|
||||
cookie_domain = string.lower(cookie_domain)
|
||||
if domain == cookie_domain:
|
||||
return 1
|
||||
elif cookie_domain[0] == '.':
|
||||
index = string.find(domain, cookie_domain)
|
||||
if index != -1 and (len(domain) - index) == len(cookie_domain):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def _paths_match(self, path, cookie_path):
|
||||
path = string.split(path, '/')
|
||||
cookie_path = string.split(cookie_path, '/')
|
||||
if cookie_path[-1] != '':
|
||||
return 0 ## invalid cookie!
|
||||
cookie_path = cookie_path[:-1]
|
||||
if path == cookie_path:
|
||||
return 1
|
||||
if len(path) < len(cookie_path):
|
||||
return 0
|
||||
if path[:len(cookie_path)] == cookie_path:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def get_headers_for(self, server, path):
|
||||
headers = self.standard_headers[:]
|
||||
if self.authorisation:
|
||||
authstr = base64.encodestring("%s:%s" % self.authorisation)[:-1]
|
||||
headers.append(("Authorization", "Basic " + authstr))
|
||||
if self.use_cookies and self.cookies.values(): # TODO: fix path matching...
|
||||
cookies = []
|
||||
for cookie in self.cookies.values():
|
||||
if self._cookie_matches(cookie, server, path):
|
||||
cookies.append(cookie.output(attrs=(), header=''))
|
||||
headers.append(("Cookie", string.join(cookies,'')))
|
||||
return headers
|
||||
|
||||
|
|
@ -15,6 +15,7 @@ testSuites = (
|
|||
'AuthenticationLoginPasswordTests',
|
||||
'ArticlesTests',
|
||||
'SpipTests',
|
||||
'WebTests',
|
||||
)
|
||||
|
||||
for testSuite in testSuites:
|
||||
|
|
Reference in New Issue