- 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:
fpeters 2003-10-14 08:38:37 +00:00
parent 20a17031c5
commit 324136cee9
36 changed files with 1219 additions and 618 deletions

View File

@ -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
-----------------------------------

View File

@ -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

View File

@ -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)

View File

@ -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`

View File

@ -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):

View File

@ -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'

View File

@ -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_(

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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()

View File

@ -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')

View File

@ -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()

View File

@ -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')

View File

@ -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')

View File

@ -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 = {}

View File

@ -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

View File

@ -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()

View File

@ -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(

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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}}}

View File

@ -147,7 +147,11 @@ hr {
div.error .label {
color: #800;
}
}
div.error input {
border: 1px solid #800;
}
span.error-message {
background: #ffce7b;

View File

@ -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 {

View File

@ -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

336
tests/WebTests.py Normal file
View File

@ -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('&amp;', '&') # 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('&amp;', '&') # 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)

44
tests/httpd.conf Normal file
View File

@ -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

357
tests/httpsession.py Normal file
View File

@ -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

View File

@ -15,6 +15,7 @@ testSuites = (
'AuthenticationLoginPasswordTests',
'ArticlesTests',
'SpipTests',
'WebTests',
)
for testSuite in testSuites: