Cf le mail sur glasnost-devel
This commit is contained in:
parent
8abdaed5ff
commit
9b5af4b22d
|
@ -116,7 +116,8 @@ class Application(applications.Application):
|
|||
|
||||
#locale.setlocale(locale.LC_ALL, '')
|
||||
domains = context.getVar('gettextDomains')
|
||||
translation = commonTools.translation(domains + ['gnome-libs'])
|
||||
translation = commonTools.translation(domains + ['gnome-libs'],
|
||||
context.getVar('readLanguages'))
|
||||
__builtin__.__dict__['_'] = lambda x: utf8(translation.gettext(x))
|
||||
|
||||
imagesDirectory = glasnost.gtk.MainWindow.__file__
|
||||
|
|
|
@ -178,7 +178,10 @@ class Application(applications.Application):
|
|||
if req.buffered:
|
||||
req.finish()
|
||||
return HTTP_FORBIDDEN
|
||||
except IOError:
|
||||
except IOError, exc:
|
||||
if exc.filename:
|
||||
# TODO: check this doesn't catch IOError to client socket
|
||||
raise
|
||||
# Write failed, client closed connection.
|
||||
if req.caching:
|
||||
req.cancel()
|
||||
|
@ -492,13 +495,22 @@ class Application(applications.Application):
|
|||
context.setVar('session', session)
|
||||
context.setVar('sessionToken', sessionToken)
|
||||
context.setVar('sessionTokenInCookie', sessionTokenInCookie)
|
||||
userId = ''
|
||||
if session and session.has_key('userId'):
|
||||
userId = session['userId']
|
||||
context.setVar('userId', userId)
|
||||
userToken = ''
|
||||
if session and session.has_key('userToken'):
|
||||
userId = None
|
||||
if session and session.has_key('userToken') and session['userToken']:
|
||||
userToken = session['userToken']
|
||||
context.setVar('userToken', userToken)
|
||||
userId = getProxyForServerRole('authentication').getUserId()
|
||||
if userId:
|
||||
userToken = session['userToken']
|
||||
else:
|
||||
userToken = None
|
||||
userId = None
|
||||
del session['userToken']
|
||||
if session.has_key('userId'):
|
||||
del session['userId']
|
||||
session['isDirty'] = 1
|
||||
context.setVar('userId', userId)
|
||||
context.setVar('userToken', userToken)
|
||||
|
||||
if not userToken and req.headers_in.has_key('Authorization'):
|
||||
|
@ -559,17 +571,11 @@ class Application(applications.Application):
|
|||
if not virtualHost.cacheFiles:
|
||||
reqCache.caching = -1
|
||||
modTime = reqCache.checkDepends()
|
||||
reqCache = None
|
||||
else:
|
||||
req.headers_out['Expires'] = reqCache.getExpires()
|
||||
modTime = None
|
||||
if reqCache.isPageCached():
|
||||
# and \
|
||||
# not (req.headers_in.has_key('Cache-Control') and \
|
||||
# req.headers_in['Cache-Control'] == 'max-age=0') and \
|
||||
# not (req.headers_in.has_key('Cache-Control') and \
|
||||
# req.headers_in['Cache-Control'] == 'no-cache') and \
|
||||
# not (req.headers_in.has_key('Pragma') and \
|
||||
# req.headers_in['Pragma'] == 'no-cache'):
|
||||
if reqCache and reqCache.isPageCached():
|
||||
try:
|
||||
cachedPage, contentType = reqCache.getCachePage()
|
||||
except OSError:
|
||||
|
@ -589,7 +595,7 @@ class Application(applications.Application):
|
|||
|
||||
if reqCache is None:
|
||||
# The system caching is not activated when a session is open.
|
||||
reqCache = req
|
||||
reqCache = Request(req)
|
||||
reqCache.caching = 0
|
||||
reqCache.depends = []
|
||||
reqCache.buffered = 0
|
||||
|
@ -1148,7 +1154,12 @@ class BufferedRequest:
|
|||
self.__dict__['_req'] = val
|
||||
except AttributeError:
|
||||
self.__dict__[attr] = val
|
||||
|
||||
|
||||
class Request(apache.Request):
|
||||
def write(self, st):
|
||||
if type(st) is type(u''):
|
||||
st = st.encode('iso-8859-1')
|
||||
return self._req.write(st)
|
||||
|
||||
class RequestCache:
|
||||
buffered = 0
|
||||
|
|
|
@ -58,48 +58,20 @@ input.button:hover {
|
|||
}
|
||||
|
||||
|
||||
/*** diffs ***/
|
||||
|
||||
div.diff-context {
|
||||
}
|
||||
|
||||
div.diff-error {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
div.diff-new {
|
||||
background-color: #80ff80;
|
||||
border-style: none;
|
||||
border-width: thin;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.diff-old {
|
||||
background-color: #ffff80;
|
||||
border-style: none;
|
||||
border-width: thin;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
hr {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
hr.diff {
|
||||
}
|
||||
|
||||
span.diff-equal {
|
||||
white-space: pre;
|
||||
pre.diff {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
span.diff-new {
|
||||
background-color: #80ff80;
|
||||
white-space: pre;
|
||||
background-color: #8f8;
|
||||
}
|
||||
|
||||
span.diff-old {
|
||||
background-color: #ffff80;
|
||||
white-space: pre;
|
||||
background-color: #f88;
|
||||
}
|
||||
|
||||
|
||||
|
@ -250,66 +222,6 @@ div.clear {
|
|||
clear: both;
|
||||
}
|
||||
|
||||
div.row {
|
||||
clear: both;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
div.row span.label,
|
||||
div.row label {
|
||||
float: left;
|
||||
width: 20%;
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.row .formw {
|
||||
float: right;
|
||||
margin-left: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.cell,
|
||||
table.cell,
|
||||
fieldset.cell,
|
||||
span.cell,
|
||||
input.cell,
|
||||
select.cell {
|
||||
text-align: left;
|
||||
float: right;
|
||||
width: 76%;
|
||||
}
|
||||
|
||||
.cell .cell {
|
||||
text-align: left;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.cell ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
div.cell ul.inline li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
fieldset.inline span {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
table.cell {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
left: -3px;
|
||||
}
|
||||
|
||||
div.buttons-bar {
|
||||
padding-top: 1em;
|
||||
clear: both;
|
||||
|
@ -342,21 +254,6 @@ div.comment-block a {
|
|||
font-size: 80%;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset span.cell,
|
||||
fieldset input.cell,
|
||||
fieldset select.cell,
|
||||
fieldset div.cell {
|
||||
width: auto;
|
||||
text-align: left;
|
||||
float: none;
|
||||
}
|
||||
|
||||
span.spellcheck {
|
||||
cursor: help;
|
||||
border-bottom: 1px dashed red;
|
||||
|
@ -400,22 +297,6 @@ h2, h3, h4, h5, h6 {
|
|||
clear: both;
|
||||
}
|
||||
|
||||
div.row div.fullwidth,
|
||||
div.row span.fullwidth {
|
||||
text-align: inherit;
|
||||
width: inherit;
|
||||
clear: both;
|
||||
float: none;
|
||||
}
|
||||
|
||||
ul.multi>li {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
ul.multi>li>div{
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
span.comment-no {
|
||||
display: none;
|
||||
}
|
||||
|
@ -424,3 +305,13 @@ ul.article-meta {
|
|||
list-style: none;
|
||||
}
|
||||
|
||||
table.diff {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
table.diff td {
|
||||
width: 45%;
|
||||
border: 1px solid black;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,9 @@ def index():
|
|||
if object.getLabel() == mainRubric.getLabel():
|
||||
context.setVar('pageTitle', mainRubric.getLabel())
|
||||
object.title = ''
|
||||
context.push(sectionLevel = context.getVar('sectionLevel')+1)
|
||||
layout += object.getEmbeddedViewLayout()
|
||||
context.pull()
|
||||
else:
|
||||
mainRubric = None
|
||||
|
||||
|
|
|
@ -91,126 +91,6 @@ class Article(ObjectServerMixin, ArticleCommon):
|
|||
dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
|
||||
self.loadBody()
|
||||
|
||||
def getBodyDiff(self, editionTime):
|
||||
virtualServerId = self.getServer().computeVirtualServerId(self.id)
|
||||
virtualServer = self.getServer().getVirtualServer(virtualServerId)
|
||||
articlesDirectoryPath = os.path.join(
|
||||
virtualServer.dataDirectoryPath, self.getServer().applicationRole)
|
||||
serverHostNameAndPort, serverRole, localId = \
|
||||
commonTools.splitId(self.id)
|
||||
bodyFilePath = os.path.join(articlesDirectoryPath, localId)
|
||||
if not os.access(bodyFilePath, os.F_OK):
|
||||
return None
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
articlesDirectoryPath, localId + '-history')
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
return None
|
||||
fileNames = os.listdir(bodyHistoryDirectoryPath)
|
||||
if not fileNames:
|
||||
return None
|
||||
editionTimeString = time.strftime('%Y%m%d%H%M%S',
|
||||
time.localtime(editionTime))
|
||||
editionTimeLen = len(editionTimeString)
|
||||
for fileName in fileNames:
|
||||
if fileName[:editionTimeLen] == editionTimeString \
|
||||
and fileName[editionTimeLen] == '-':
|
||||
editionTimeString, editorIdEncoded, format = \
|
||||
fileName.split('-')
|
||||
editorId = base64.decodestring(editorIdEncoded)
|
||||
bodyVersionFilePath = os.path.join(bodyHistoryDirectoryPath,
|
||||
fileName)
|
||||
command = 'diff -u %(backup)s %(current)s' % {
|
||||
'backup': bodyVersionFilePath,
|
||||
'current': bodyFilePath,
|
||||
}
|
||||
diffFile = os.popen(command, 'r')
|
||||
diff = diffFile.readlines()
|
||||
result = diffFile.close()
|
||||
if not diff:
|
||||
return None
|
||||
version = {
|
||||
'diff': diff[2:],
|
||||
'editionTime': editionTime,
|
||||
'format': format,
|
||||
}
|
||||
if editorId:
|
||||
version['editorId'] = editorId
|
||||
return version
|
||||
return None
|
||||
|
||||
def getBodyHistory(self):
|
||||
virtualServerId = self.getServer().computeVirtualServerId(self.id)
|
||||
virtualServer = self.getServer().getVirtualServer(virtualServerId)
|
||||
articlesDirectoryPath = os.path.join(
|
||||
virtualServer.dataDirectoryPath, self.getServer().applicationRole)
|
||||
serverHostNameAndPort, serverRole, localId = \
|
||||
commonTools.splitId(self.id)
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
articlesDirectoryPath, localId + '-history')
|
||||
history = []
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
return history
|
||||
fileNames = os.listdir(bodyHistoryDirectoryPath)
|
||||
if fileNames:
|
||||
fileNames.sort()
|
||||
for fileName in fileNames:
|
||||
editionTimeString, editorIdEncoded, format = \
|
||||
fileName.split('-')
|
||||
t = time.strptime(editionTimeString, '%Y%m%d%H%M%S')
|
||||
# Set Daylight Saving Time to -1, so that it is handled
|
||||
# correctly by mktime.
|
||||
t = tuple(list(t[0:-1]) + [-1])
|
||||
editionTime = time.mktime(t)
|
||||
editorId = base64.decodestring(editorIdEncoded)
|
||||
version = {
|
||||
'editionTime': editionTime,
|
||||
'format': format,
|
||||
}
|
||||
if editorId:
|
||||
version['editorId'] = editorId
|
||||
history.append(version)
|
||||
return history
|
||||
|
||||
def getBodyVersion(self, editionTime):
|
||||
virtualServerId = self.getServer().computeVirtualServerId(self.id)
|
||||
virtualServer = self.getServer().getVirtualServer(virtualServerId)
|
||||
articlesDirectoryPath = os.path.join(
|
||||
virtualServer.dataDirectoryPath, self.getServer().applicationRole)
|
||||
serverHostNameAndPort, serverRole, localId = \
|
||||
commonTools.splitId(self.id)
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
articlesDirectoryPath, localId + '-history')
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
return None
|
||||
fileNames = os.listdir(bodyHistoryDirectoryPath)
|
||||
if not fileNames:
|
||||
return None
|
||||
editionTimeString = time.strftime('%Y%m%d%H%M%S',
|
||||
time.localtime(editionTime))
|
||||
editionTimeLen = len(editionTimeString)
|
||||
for fileName in fileNames:
|
||||
if fileName[:editionTimeLen] == editionTimeString \
|
||||
and fileName[editionTimeLen] == '-':
|
||||
editionTimeString, editorIdEncoded, format = \
|
||||
fileName.split('-')
|
||||
editorId = base64.decodestring(editorIdEncoded)
|
||||
bodyVersionFilePath = os.path.join(bodyHistoryDirectoryPath,
|
||||
fileName)
|
||||
bodyVersionFile = open(bodyVersionFilePath, 'rb')
|
||||
fcntl.lockf(bodyVersionFile, fcntl.LOCK_SH)
|
||||
body = bodyVersionFile.read()
|
||||
fcntl.lockf(bodyVersionFile, fcntl.LOCK_UN)
|
||||
bodyVersionFile.close()
|
||||
version = {
|
||||
'body': body,
|
||||
'editionTime': editionTime,
|
||||
'format': format,
|
||||
}
|
||||
if editorId:
|
||||
version['editorId'] = editorId
|
||||
return version
|
||||
return None
|
||||
|
||||
def getDocBookChapter(self):
|
||||
# TODO: support conversion from every text format to DocBook
|
||||
assert self.format == 'spip'
|
||||
|
@ -273,30 +153,8 @@ class Article(ObjectServerMixin, ArticleCommon):
|
|||
serverHostNameAndPort, serverRole, localId = \
|
||||
commonTools.splitId(self.id)
|
||||
bodyFilePath = os.path.join(articlesDirectoryPath, localId)
|
||||
try:
|
||||
if os.path.exists(bodyFilePath):
|
||||
os.remove(bodyFilePath)
|
||||
except OSError, error:
|
||||
# Ignore 'No such file or directory' error.
|
||||
if error.errno != 2:
|
||||
raise
|
||||
|
||||
def removeBodyHistory(self):
|
||||
virtualServerId = self.getServer().computeVirtualServerId(self.id)
|
||||
virtualServer = self.getServer().getVirtualServer(virtualServerId)
|
||||
articlesDirectoryPath = os.path.join(
|
||||
virtualServer.dataDirectoryPath, self.getServer().applicationRole)
|
||||
serverHostNameAndPort, serverRole, localId = \
|
||||
commonTools.splitId(self.id)
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
articlesDirectoryPath, localId + '-history')
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
return
|
||||
fileNames = os.listdir(bodyHistoryDirectoryPath)
|
||||
if fileNames:
|
||||
for fileName in fileNames:
|
||||
filePath = os.path.join(bodyHistoryDirectoryPath, fileName)
|
||||
os.remove(filePath)
|
||||
os.rmdir(bodyHistoryDirectoryPath)
|
||||
|
||||
def removeNonCore(self, objectDirectoryPath = None,
|
||||
dataDirectoryPath = None, parentSlot = None):
|
||||
|
@ -304,7 +162,6 @@ class Article(ObjectServerMixin, ArticleCommon):
|
|||
self, objectDirectoryPath = objectDirectoryPath,
|
||||
dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
|
||||
self.removeBodyFile()
|
||||
self.removeBodyHistory()
|
||||
|
||||
def saveBody(self):
|
||||
if self.body is None:
|
||||
|
@ -330,37 +187,6 @@ class Article(ObjectServerMixin, ArticleCommon):
|
|||
fcntl.lockf(bodyFile, fcntl.LOCK_UN)
|
||||
bodyFile.close()
|
||||
|
||||
def saveBodyHistory(self, format, body, editorId, editionTime):
|
||||
virtualServerId = self.getServer().computeVirtualServerId(self.id)
|
||||
virtualServer = self.getServer().getVirtualServer(virtualServerId)
|
||||
articlesDirectoryPath = os.path.join(
|
||||
virtualServer.dataDirectoryPath, self.getServer().applicationRole)
|
||||
serverHostNameAndPort, serverRole, localId = \
|
||||
commonTools.splitId(self.id)
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
articlesDirectoryPath, localId + '-history')
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
os.mkdir(bodyHistoryDirectoryPath)
|
||||
os.chmod(bodyHistoryDirectoryPath, 0750)
|
||||
editionTimeString = time.strftime('%Y%m%d%H%M%S',
|
||||
time.localtime(editionTime))
|
||||
if editorId is None:
|
||||
editorId = ''
|
||||
bodyHistoryFileName = \
|
||||
'%(editionTime)s-%(editorIdEncoded)s-%(format)s' % {
|
||||
'editionTime': editionTimeString,
|
||||
'editorIdEncoded': base64.encodestring(editorId).strip(),
|
||||
'format': format,
|
||||
}
|
||||
bodyHistoryFilePath = os.path.join(bodyHistoryDirectoryPath,
|
||||
bodyHistoryFileName)
|
||||
bodyHistoryFile = open(bodyHistoryFilePath, 'wb')
|
||||
os.chmod(bodyHistoryFilePath, 0640)
|
||||
fcntl.lockf(bodyHistoryFile, fcntl.LOCK_EX)
|
||||
bodyHistoryFile.write(body)
|
||||
fcntl.lockf(bodyHistoryFile, fcntl.LOCK_UN)
|
||||
bodyHistoryFile.close()
|
||||
|
||||
def saveNonCore(self, objectDirectoryPath = None, dataDirectoryPath = None,
|
||||
parentSlot = None):
|
||||
ObjectServerMixin.saveNonCore(
|
||||
|
@ -397,27 +223,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
virtualServer.markCoreAsDirty()
|
||||
return object.id
|
||||
|
||||
def canAddObject(self):
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
applicationName = extractApplicationHostName(virtualServerId)
|
||||
nbArticle = int(commonTools.getConfigNoCache(
|
||||
'%s' % applicationName, 'Limit-%s' % applicationRole, '0'))
|
||||
if nbArticle:
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
if len(virtualServer.objects.keys()) >= nbArticle:
|
||||
return 0
|
||||
return ObjectsServer.canAddObject(self)
|
||||
|
||||
def canGetObjectHistory(self, objectId):
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
if not virtualServer.canLoadObjectCore(objectId):
|
||||
return 0
|
||||
object = virtualServer.loadObjectCore(objectId)
|
||||
return self.isAdmin() \
|
||||
or getProxyForServerRole('authentication').setContainsUser(
|
||||
object.writersSet)
|
||||
|
||||
def convertVirtualServersIds(
|
||||
self, sourceDispatcherId, destinationDispatcherId):
|
||||
exitCode = ObjectsServer.convertVirtualServersIds(
|
||||
|
@ -460,42 +265,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
destinationBodyFile.write(sourceBodyFile.read())
|
||||
sourceBodyFile.close()
|
||||
destinationBodyFile.close()
|
||||
|
||||
sourceBodyHistoryDirectoryPath = os.path.join(
|
||||
sourceObjectsDirectoryPath, localId + '-history')
|
||||
destinationBodyHistoryDirectoryPath = os.path.join(
|
||||
destinationObjectsDirectoryPath, localId + '-history')
|
||||
if not os.access(sourceBodyHistoryDirectoryPath, os.F_OK):
|
||||
continue
|
||||
if not os.access(destinationBodyHistoryDirectoryPath, os.F_OK):
|
||||
os.mkdir(destinationBodyHistoryDirectoryPath)
|
||||
os.chmod(destinationBodyHistoryDirectoryPath, 0750)
|
||||
sourceBodyHistoryFileNames = os.listdir(
|
||||
sourceBodyHistoryDirectoryPath)
|
||||
for sourceBodyHistoryFileName in sourceBodyHistoryFileNames:
|
||||
editionTimeString, sourceEditorIdEncoded, format = \
|
||||
sourceBodyHistoryFileName.split('-')
|
||||
sourceEditorId = base64.decodestring(sourceEditorIdEncoded)
|
||||
destinationEditorId = sourceEditorId.replace(
|
||||
sourceDispatcherId, destinationDispatcherId)
|
||||
destinationBodyHistoryFileName = \
|
||||
'%(editionTime)s-%(editorIdEncoded)s-%(format)s' % {
|
||||
'editionTime': editionTimeString,
|
||||
'editorIdEncoded': base64.encodestring(
|
||||
destinationEditorId).strip(),
|
||||
'format': format,
|
||||
}
|
||||
sourceBodyHistoryFilePath = os.path.join(
|
||||
sourceBodyHistoryDirectoryPath, sourceBodyHistoryFileName)
|
||||
destinationBodyHistoryFilePath = os.path.join(
|
||||
destinationBodyHistoryDirectoryPath,
|
||||
destinationBodyHistoryFileName)
|
||||
sourceBodyHistoryFile = open(sourceBodyHistoryFilePath, 'rb')
|
||||
destinationBodyHistoryFile = open(
|
||||
destinationBodyHistoryFilePath, 'wb')
|
||||
destinationBodyHistoryFile.write(sourceBodyHistoryFile.read())
|
||||
sourceBodyHistoryFile.close()
|
||||
destinationBodyHistoryFile.close()
|
||||
return None
|
||||
|
||||
def exportVirtualServer(self, virtualServerId, exportDirectoryPath):
|
||||
|
@ -532,29 +301,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
exportBodyFile.write(sourceBodyFile.read())
|
||||
sourceBodyFile.close()
|
||||
exportBodyFile.close()
|
||||
|
||||
sourceBodyHistoryDirectoryPath = os.path.join(
|
||||
objectsDirectoryPath, localId + '-history')
|
||||
exportBodyHistoryDirectoryPath = os.path.join(
|
||||
exportObjectsDirectoryPath, localId + '-history')
|
||||
if not os.access(sourceBodyHistoryDirectoryPath, os.F_OK):
|
||||
continue
|
||||
if not os.access(exportBodyHistoryDirectoryPath, os.F_OK):
|
||||
os.mkdir(exportBodyHistoryDirectoryPath)
|
||||
os.chmod(exportBodyHistoryDirectoryPath, 0750)
|
||||
bodyHistoryFileNames = os.listdir(
|
||||
sourceBodyHistoryDirectoryPath)
|
||||
for bodyHistoryFileName in bodyHistoryFileNames:
|
||||
sourceBodyHistoryFilePath = os.path.join(
|
||||
sourceBodyHistoryDirectoryPath, bodyHistoryFileName)
|
||||
exportBodyHistoryFilePath = os.path.join(
|
||||
exportBodyHistoryDirectoryPath, bodyHistoryFileName)
|
||||
sourceBodyHistoryFile = open(sourceBodyHistoryFilePath, 'rb')
|
||||
exportBodyHistoryFile = open(exportBodyHistoryFilePath, 'wb')
|
||||
os.chmod(exportBodyHistoryFilePath, 0640)
|
||||
exportBodyHistoryFile.write(sourceBodyHistoryFile.read())
|
||||
sourceBodyHistoryFile.close()
|
||||
exportBodyHistoryFile.close()
|
||||
return None
|
||||
|
||||
def getLastObjectIds(self, objectsCount, possibleAuthorsSet,
|
||||
|
@ -603,20 +349,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
break
|
||||
return result
|
||||
|
||||
def getObjectDiffXmlRpc(self, objectId, editionTime):
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
object = virtualServer.loadObjectCore(objectId)
|
||||
if not self.isAdmin() \
|
||||
and not getProxyForServerRole('authentication'
|
||||
).setContainsUser(object.writersSet):
|
||||
raise faults.UserAccessDenied()
|
||||
result = object.getBodyDiff(editionTime)
|
||||
if result is None:
|
||||
raise faults.MissingItem(objectId)
|
||||
result['diff'] = [ utf8(line) for line in result['diff']]
|
||||
return result
|
||||
|
||||
def getObjectDocBookChapterXmlRpc(self, objectId):
|
||||
object = self.getObjectCore(objectId)
|
||||
object.acquireNonCore()
|
||||
|
@ -626,16 +358,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
object.releaseNonCore()
|
||||
return utf8(result)
|
||||
|
||||
def getObjectHistory(self, objectId):
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
object = virtualServer.loadObjectCore(objectId)
|
||||
if not self.isAdmin() \
|
||||
and not getProxyForServerRole('authentication'
|
||||
).setContainsUser(object.writersSet):
|
||||
raise faults.UserAccessDenied()
|
||||
return object.getBodyHistory()
|
||||
|
||||
def getObjectLatexChapterXmlRpc(self, objectId):
|
||||
object = self.getObjectCore(objectId)
|
||||
object.acquireNonCore()
|
||||
|
@ -645,20 +367,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
object.releaseNonCore()
|
||||
return utf8(result)
|
||||
|
||||
def getObjectVersionXmlRpc(self, objectId, editionTime):
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
object = virtualServer.loadObjectCore(objectId)
|
||||
if not self.isAdmin() \
|
||||
and not getProxyForServerRole('authentication'
|
||||
).setContainsUser(object.writersSet):
|
||||
raise faults.UserAccessDenied()
|
||||
result = object.getBodyVersion(editionTime)
|
||||
if result is None:
|
||||
raise faults.MissingItem(objectId)
|
||||
result['body'] = utf8(result['body'])
|
||||
return result
|
||||
|
||||
def importVirtualServer(self, virtualServerId, importDirectoryPath):
|
||||
virtualServer = ObjectsServer.importVirtualServer(
|
||||
self, virtualServerId, importDirectoryPath)
|
||||
|
@ -692,106 +400,16 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
destinationBodyFile.write(importBodyFile.read())
|
||||
importBodyFile.close()
|
||||
destinationBodyFile.close()
|
||||
|
||||
importBodyHistoryDirectoryPath = os.path.join(
|
||||
importObjectsDirectoryPath, localId + '-history')
|
||||
destinationBodyHistoryDirectoryPath = os.path.join(
|
||||
objectsDirectoryPath, localId + '-history')
|
||||
if not os.access(importBodyHistoryDirectoryPath, os.F_OK):
|
||||
continue
|
||||
if not os.access(destinationBodyHistoryDirectoryPath, os.F_OK):
|
||||
os.mkdir(destinationBodyHistoryDirectoryPath)
|
||||
os.chmod(destinationBodyHistoryDirectoryPath, 0750)
|
||||
bodyHistoryFileNames = os.listdir(
|
||||
importBodyHistoryDirectoryPath)
|
||||
for bodyHistoryFileName in bodyHistoryFileNames:
|
||||
importBodyHistoryFilePath = os.path.join(
|
||||
importBodyHistoryDirectoryPath, bodyHistoryFileName)
|
||||
destinationBodyHistoryFilePath = os.path.join(
|
||||
destinationBodyHistoryDirectoryPath, bodyHistoryFileName)
|
||||
importBodyHistoryFile = open(importBodyHistoryFilePath, 'rb')
|
||||
destinationBodyHistoryFile = open(
|
||||
destinationBodyHistoryFilePath, 'wb')
|
||||
destinationBodyHistoryFile.write(importBodyHistoryFile.read())
|
||||
importBodyHistoryFile.close()
|
||||
destinationBodyHistoryFile.close()
|
||||
return virtualServer
|
||||
|
||||
def modifyObjectXmlRpc(self, objectImport):
|
||||
objectChanges = commonTools.importThing(objectImport)
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
object = virtualServer.loadObjectCore(objectChanges.id)
|
||||
object.acquireNonCore()
|
||||
try:
|
||||
if not object.canBeModified():
|
||||
raise faults.ReadOnlyObject()
|
||||
if not self.canModifyObject(object.id) or not (
|
||||
self.isAdmin()
|
||||
or not objectChanges.hasSlotName('writersSet')
|
||||
or getProxyForServerRole('authentication'
|
||||
).setContainsUser(
|
||||
objectChanges.getSlot('writersSet').getValue())):
|
||||
if not object.canBeModifiedByClient():
|
||||
raise faults.UserAccessDenied()
|
||||
object.checkModifyIsPossible(objectChanges)
|
||||
oldFormat = object.format
|
||||
oldBody = object.body
|
||||
oldLastEditorId = object.lastEditorId
|
||||
oldEditionTime = object.editionTime
|
||||
object.modify(objectChanges)
|
||||
if object.body != oldBody:
|
||||
if oldBody is not None:
|
||||
object.saveBodyHistory(oldFormat, oldBody, oldLastEditorId,
|
||||
oldEditionTime)
|
||||
object.saveNonCore()
|
||||
finally:
|
||||
object.releaseNonCore()
|
||||
virtualServer.markObjectAsDirty(object)
|
||||
invalidateValue(object.id)
|
||||
return object.version
|
||||
|
||||
def registerPublicMethods(self):
|
||||
ObjectsServer.registerPublicMethods(self)
|
||||
self.registerPublicMethod('canGetObjectHistory')
|
||||
self.registerPublicMethod('getObjectDiff',
|
||||
self.getObjectDiffXmlRpc)
|
||||
self.registerPublicMethod('getObjectDocBookChapter',
|
||||
self.getObjectDocBookChapterXmlRpc)
|
||||
self.registerPublicMethod('getObjectHistory')
|
||||
self.registerPublicMethod('getObjectLatexChapter',
|
||||
self.getObjectLatexChapterXmlRpc)
|
||||
self.registerPublicMethod('getObjectVersion',
|
||||
self.getObjectVersionXmlRpc)
|
||||
self.registerPublicMethod('search')
|
||||
|
||||
def removeVirtualServerHistory(self, dispatcherId):
|
||||
exitCode = ObjectsServer.removeVirtualServerHistory(
|
||||
self, dispatcherId)
|
||||
if exitCode is not None:
|
||||
return exitCode
|
||||
virtualServerId = '%s/%s' % (dispatcherId, self.applicationRole)
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
hostName = extractApplicationHostName(dispatcherId)
|
||||
virtualServerDataDirectoryPath = os.path.join(
|
||||
self.dataDirectoryPath, hostName)
|
||||
objectsDirectoryPath = os.path.join(
|
||||
virtualServerDataDirectoryPath, self.applicationRole)
|
||||
for id in virtualServer.objects.keys():
|
||||
serverHostNameAndPortNotUsed, serverRoleNotUsed, localId = \
|
||||
commonTools.splitId(id)
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
objectsDirectoryPath, localId + '-history')
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
continue
|
||||
bodyHistoryFileNames = os.listdir(bodyHistoryDirectoryPath)
|
||||
for bodyHistoryFileName in bodyHistoryFileNames:
|
||||
bodyHistoryFilePath = os.path.join(
|
||||
bodyHistoryDirectoryPath, bodyHistoryFileName)
|
||||
os.remove(bodyHistoryFilePath)
|
||||
os.rmdir(bodyHistoryDirectoryPath)
|
||||
return None
|
||||
|
||||
def repairVirtualServer(self, virtualServer, version):
|
||||
changed = 0
|
||||
if version < 3000:
|
||||
|
@ -827,44 +445,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
if not object.__dict__.has_key('language'):
|
||||
changed = 1
|
||||
object.language = 'fr'
|
||||
if version < 5001:
|
||||
hostName = extractApplicationHostName(
|
||||
virtualServer.virtualServerId)
|
||||
virtualServerDataDirectoryPath = os.path.join(
|
||||
self.dataDirectoryPath, hostName)
|
||||
objectsDirectoryPath = os.path.join(
|
||||
virtualServerDataDirectoryPath, self.applicationRole)
|
||||
for id in virtualServer.objects.keys():
|
||||
serverHostNameAndPort, serverRole, localId = \
|
||||
commonTools.splitId(id)
|
||||
bodyFilePath = os.path.join(objectsDirectoryPath, localId)
|
||||
if not os.access(bodyFilePath, os.F_OK):
|
||||
continue
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
objectsDirectoryPath, localId + '-history')
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
continue
|
||||
fileNames = os.listdir(bodyHistoryDirectoryPath)
|
||||
if not fileNames:
|
||||
continue
|
||||
for fileName in fileNames:
|
||||
editionTimeString, editorLocalId, format = \
|
||||
fileName.split('-')
|
||||
if len(editorLocalId) > 4:
|
||||
# The editorId is already encoded into base64.
|
||||
continue
|
||||
editorId = 'glasnost://%s/people/%s' % (
|
||||
hostName, editorLocalId)
|
||||
newFileName = \
|
||||
'%(editionTime)s-%(editorIdEncoded)s-%(format)s' % {
|
||||
'editionTime': editionTimeString,
|
||||
'editorIdEncoded': base64.encodestring(
|
||||
editorId).strip(),
|
||||
'format': format,
|
||||
}
|
||||
os.rename(
|
||||
os.path.join(bodyHistoryDirectoryPath, fileName),
|
||||
os.path.join(bodyHistoryDirectoryPath, newFileName))
|
||||
if version < 5004:
|
||||
changed = virtualServer.admin.repair(5004) or changed
|
||||
hostName = extractApplicationHostName(
|
||||
|
@ -890,25 +470,6 @@ class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
|
|||
os.chmod(bodyFilePath, 0640)
|
||||
file.write(repairedBody)
|
||||
file.close()
|
||||
bodyHistoryDirectoryPath = os.path.join(
|
||||
objectsDirectoryPath, localId + '-history')
|
||||
if not os.access(bodyHistoryDirectoryPath, os.F_OK):
|
||||
continue
|
||||
fileNames = os.listdir(bodyHistoryDirectoryPath)
|
||||
if not fileNames:
|
||||
continue
|
||||
for fileName in fileNames:
|
||||
filePath = os.path.join(bodyHistoryDirectoryPath, fileName)
|
||||
file = open(filePath, 'rb')
|
||||
body = file.read()
|
||||
file.close()
|
||||
repairedBody = body.replace('\r\n', '\n')
|
||||
repairedBody = repairedBody.replace('\r', '\n')
|
||||
if repairedBody != body:
|
||||
file = open(filePath, 'wb')
|
||||
os.chmod(filePath, 0640)
|
||||
file.write(repairedBody)
|
||||
file.close()
|
||||
if version <= 1021000:
|
||||
admin = virtualServer.admin
|
||||
if admin.id is None:
|
||||
|
|
|
@ -489,7 +489,7 @@ class Dispatcher(Server):
|
|||
rolesToRemove = []
|
||||
for r in currentRolesUniq.keys():
|
||||
if not r in futureRoles:
|
||||
rolestoRemove.append(r)
|
||||
rolesToRemove.append(r)
|
||||
|
||||
for serverInfo in virtualServer.serverInfos.values():
|
||||
serverAccessor = serverInfo['accessor']
|
||||
|
|
|
@ -323,7 +323,7 @@ class PeopleServer(PeopleCommonMixin, ObjectsServer):
|
|||
gpg = Gpg(email = object.email, fingerprint = object.fingerprint)
|
||||
gpg.addKey()
|
||||
except: # raise Exception('GnuPG not configured for the user')
|
||||
# it should be a real fault that could be catched specifically
|
||||
# it should be a real fault that could then be catched specifically
|
||||
pass
|
||||
return object.id
|
||||
|
||||
|
@ -669,3 +669,4 @@ peopleServer = PeopleServer()
|
|||
|
||||
if __name__ == "__main__":
|
||||
peopleServer.launch(applicationName, applicationRole)
|
||||
|
||||
|
|
|
@ -379,6 +379,8 @@ class TranslationsServer(TranslationsCommonMixin, AdministrableServerMixin,
|
|||
[translation.sourceStringDigest,
|
||||
utf8(translation.getLabel()),
|
||||
len(translation.sourceString.split())])
|
||||
if digestsAndLabelsCount == -1:
|
||||
continue
|
||||
if len(digestsAndLabels) >= digestsAndLabelsCount:
|
||||
break
|
||||
return digestsAndLabels
|
||||
|
|
|
@ -94,6 +94,7 @@ class UploadFile(ObjectServerMixin, UploadFileCommon):
|
|||
self, objectDirectoryPath = objectDirectoryPath,
|
||||
dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
|
||||
self.loadData()
|
||||
self.loadProperties()
|
||||
|
||||
def loadData(self):
|
||||
virtualServerId = self.getServer().computeVirtualServerId(self.id)
|
||||
|
@ -108,10 +109,6 @@ class UploadFile(ObjectServerMixin, UploadFileCommon):
|
|||
except IOError:
|
||||
if self.__dict__.has_key('data'):
|
||||
del self.data
|
||||
if self.__dict__.has_key('height'):
|
||||
del self.height
|
||||
if self.__dict__.has_key('width'):
|
||||
del self.width
|
||||
if self.__dict__.has_key('size'):
|
||||
del self.size
|
||||
else:
|
||||
|
@ -121,18 +118,51 @@ class UploadFile(ObjectServerMixin, UploadFileCommon):
|
|||
dataFile.close()
|
||||
# XMLRPC doesn't implements the handling of long integers.
|
||||
self.size = int(os.stat(dataFilePath)[ST_SIZE])
|
||||
if self.isType('image') and PILImage:
|
||||
uploadFileFile = cStringIO.StringIO(self.data)
|
||||
try:
|
||||
uploadFileObject = PILImage.open(uploadFileFile)
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
self.width, self.height = uploadFileObject.size
|
||||
|
||||
def loadProperties(self):
|
||||
self.properties = []
|
||||
self.values = {}
|
||||
|
||||
def newProperty(name, kindName, label, value):
|
||||
property = commonTools.newThing('other', 'Property')
|
||||
property.name = name
|
||||
property.kind = commonTools.newThing('kind', kindName)
|
||||
property.kind.isTranslatable = 0
|
||||
property.kind.label = label
|
||||
self.values[name] = value
|
||||
self.properties.append(property)
|
||||
|
||||
if self.isType('image') and PILImage and self.data:
|
||||
uploadFileFile = cStringIO.StringIO(self.data)
|
||||
try:
|
||||
uploadFileObject = PILImage.open(uploadFileFile)
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
width, height = uploadFileObject.size
|
||||
newProperty('extraWidth', 'Integer', N_('Width'), width)
|
||||
newProperty('extraHeight', 'Integer', N_('Height'), height)
|
||||
|
||||
if hasattr(uploadFileObject, '_getexif'):
|
||||
exifData = uploadFileObject._getexif()
|
||||
exifKeys = { # gotten experimentally from gthumb;
|
||||
# TODO: read libexif
|
||||
271: ('make', N_('Make')),
|
||||
272: ('model', N_('Model')),
|
||||
306: ('datetime', N_('Date')),
|
||||
}
|
||||
for key, value in exifKeys.items():
|
||||
if not exifData.has_key(key):
|
||||
continue
|
||||
newProperty(value[0], 'String', value[1],
|
||||
str(exifData[key]))
|
||||
|
||||
|
||||
def releaseNonCore(self, parentSlot = None):
|
||||
if self.__dict__.has_key('data'):
|
||||
del self.data
|
||||
if self.__dict__.has_key('properties'):
|
||||
del self.properties
|
||||
ObjectServerMixin.releaseNonCore(self, parentSlot)
|
||||
|
||||
def removeDataFile(self):
|
||||
|
@ -182,14 +212,6 @@ class UploadFile(ObjectServerMixin, UploadFileCommon):
|
|||
dataFile.close()
|
||||
# XMLRPC doesn't implements the handling of long integers.
|
||||
self.size = int(os.stat(dataFilePath)[ST_SIZE])
|
||||
if self.isType('image') and PILImage:
|
||||
uploadFileFile = cStringIO.StringIO(self.data)
|
||||
try:
|
||||
uploadFileObject = PILImage.open(uploadFileFile)
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
self.width, self.height = uploadFileObject.size
|
||||
|
||||
def saveNonCore(self, objectDirectoryPath = None, dataDirectoryPath = None,
|
||||
parentSlot = None):
|
||||
|
@ -466,3 +488,4 @@ uploadFilesServer = UploadFilesServer()
|
|||
|
||||
if __name__ == "__main__":
|
||||
uploadFilesServer.launch(applicationName, applicationRole)
|
||||
|
||||
|
|
|
@ -45,7 +45,10 @@ __doc__ = """Glasnost Upload Files Common Models"""
|
|||
__version__ = '$Revision$'[11:-2]
|
||||
|
||||
|
||||
import faults
|
||||
from ObjectsCommon import AdminCommon, ObjectCommon, ObjectsCommonMixin
|
||||
import slots
|
||||
import tools_new as commonTools
|
||||
|
||||
|
||||
class AdminUploadFilesCommon(AdminCommon):
|
||||
|
@ -74,14 +77,16 @@ class UploadFileCommon(ObjectCommon):
|
|||
dataType_kind_isTranslatable = 0
|
||||
dataType_kindName = 'String'
|
||||
|
||||
height = None
|
||||
height_kind_importExport = 'from-server-only'
|
||||
height_kind_useCustomStorage = 1
|
||||
height_kindName = 'Integer'
|
||||
|
||||
modificationTime = None
|
||||
modificationTime_kindName = 'ModificationTime'
|
||||
|
||||
properties = None
|
||||
properties_kind_importExport = 'from-server-only'
|
||||
properties_kind_label = N_('Extra')
|
||||
properties_kindName = 'Properties'
|
||||
|
||||
values = None
|
||||
|
||||
readersSet = None
|
||||
readersSet_kindName = 'ReadersSet'
|
||||
|
||||
|
@ -97,11 +102,6 @@ class UploadFileCommon(ObjectCommon):
|
|||
title_kind_isRequired = 1
|
||||
title_kindName = 'String'
|
||||
|
||||
width = None
|
||||
width_kind_importExport = 'from-server-only'
|
||||
width_kind_useCustomStorage = 1
|
||||
width_kindName = 'Integer'
|
||||
|
||||
writersSet = None
|
||||
writersSet_kindName = 'WritersSet'
|
||||
|
||||
|
@ -126,6 +126,128 @@ class UploadFileCommon(ObjectCommon):
|
|||
def isType(self, type):
|
||||
return self.dataType and self.dataType.startswith(type)
|
||||
|
||||
def importFromXmlRpc(self, dataImport, parentSlot = None):
|
||||
# The slots 'properties' must be imported first,
|
||||
# because it is needed to compute the slotNames list.
|
||||
if dataImport.has_key('__noneSlotNames__'):
|
||||
noneSlotNames = dataImport['__noneSlotNames__']
|
||||
else:
|
||||
noneSlotNames = []
|
||||
for slotName in ['properties']:
|
||||
if dataImport.has_key(slotName):
|
||||
exportedValue = dataImport[slotName]
|
||||
elif slotName in noneSlotNames:
|
||||
exportedValue = None
|
||||
else:
|
||||
continue
|
||||
slot = self.getSlot(slotName, parentSlot = parentSlot)
|
||||
kind = slot.getKind()
|
||||
if not kind.isImportable():
|
||||
continue
|
||||
slot.setValue(kind.importValueFromXmlRpc(slot, exportedValue))
|
||||
return ObjectCommon.importFromXmlRpc(
|
||||
self, dataImport, parentSlot = parentSlot)
|
||||
|
||||
### TODO: those methods could be moved to ObjectsCommon (?)
|
||||
def getDirectPropertyNames(self):
|
||||
if self.properties is None:
|
||||
return []
|
||||
return [property.name for property in self.properties]
|
||||
|
||||
def getDirectPropertyValue(self, propertyName):
|
||||
if not self.hasDirectPropertyValue(propertyName):
|
||||
raise faults.MissingItem(propertyName)
|
||||
return self.values[propertyName]
|
||||
|
||||
def getDirectPropertyValueKind(self, propertyName):
|
||||
if self.properties:
|
||||
for property in self.properties:
|
||||
if property.name == propertyName:
|
||||
return property.kind
|
||||
return None
|
||||
|
||||
def getPropertiesCount(self):
|
||||
return len(self.getPropertyNames())
|
||||
|
||||
def getPropertyNames(self):
|
||||
if self.properties is None:
|
||||
return []
|
||||
return [property.name for property in self.properties]
|
||||
|
||||
def getPropertySlot(self, propertyName, parentSlot = None):
|
||||
propertiesSlot = self.getSlot('properties', parentSlot = parentSlot)
|
||||
return slots.Property(propertyName, parent = propertiesSlot)
|
||||
|
||||
def getPropertyValue(self, propertyName, propertyKind):
|
||||
return self.getDirectPropertyValue(propertyName)
|
||||
|
||||
def getPropertyValueKind(self, propertyName):
|
||||
kind = self.getDirectPropertyValueKind(propertyName)
|
||||
if kind is not None:
|
||||
return kind
|
||||
raise Exception('Property "%s" of %s has no kind' % (
|
||||
propertyName, self))
|
||||
|
||||
def getPropertyValueSlot(self, propertyName, parentSlot = None):
|
||||
if parentSlot is None:
|
||||
container = self
|
||||
else:
|
||||
container = None
|
||||
return slots.PropertyValue(
|
||||
propertyName, container = container, parent = parentSlot)
|
||||
|
||||
def getSlot(self, attributeName, parentSlot = None):
|
||||
if attributeName in ObjectCommon.getSlotNames(
|
||||
self, parentSlot = parentSlot):
|
||||
return ObjectCommon.getSlot(
|
||||
self, attributeName, parentSlot = parentSlot)
|
||||
return self.getPropertyValueSlot(
|
||||
attributeName, parentSlot = parentSlot)
|
||||
|
||||
def getSlotNames(self, parentSlot = None):
|
||||
slotNames = ObjectCommon.getSlotNames(self, parentSlot = parentSlot)
|
||||
return slotNames + self.getPropertyNames()
|
||||
|
||||
def hasDirectPropertyValue(self, propertyName):
|
||||
return self.values and self.values.has_key(propertyName)
|
||||
|
||||
def setDirectPropertyValue(self, propertyName, value):
|
||||
if self.values is None:
|
||||
self.values = {}
|
||||
self.values[propertyName] = value
|
||||
|
||||
def setDirectPropertyValueKind(self, propertyName, propertyKind):
|
||||
if self.properties is None:
|
||||
self.properties = []
|
||||
else:
|
||||
for i in range(len(self.properties)):
|
||||
if self.properties[i].name == propertyName:
|
||||
if propertyKind is None:
|
||||
del self.properties[i]
|
||||
return
|
||||
else:
|
||||
property = self.properties[i]
|
||||
property.kind = propertyKind
|
||||
return
|
||||
if propertyKind is not None:
|
||||
property = commonTools.newThing('other', 'Property')
|
||||
property.kind = propertyKind
|
||||
property.name = propertyName
|
||||
self.properties.append(property)
|
||||
|
||||
def setPropertyValue(self, propertyName, propertyKind, value):
|
||||
if self.values is None:
|
||||
self.values = {}
|
||||
if value == self.getInheritedPropertyValue(propertyName, propertyKind):
|
||||
if self.values.has_key(propertyName):
|
||||
del self.values[propertyName]
|
||||
if not self.values:
|
||||
del self.values
|
||||
else:
|
||||
self.values[propertyName] = value
|
||||
|
||||
|
||||
|
||||
|
||||
class UploadFilesCommonMixin(ObjectsCommonMixin):
|
||||
adminClassName = 'AdminUploadFiles'
|
||||
|
@ -136,3 +258,4 @@ class UploadFilesCommonMixin(ObjectsCommonMixin):
|
|||
objectsName = N_('files')
|
||||
objectsNameCapitalized = N_('Files')
|
||||
serverRole = 'uploadfiles'
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ class Cache:
|
|||
del self.cachedValues[k]
|
||||
except faults.UnknownServerId:
|
||||
self.disabled = 1
|
||||
self.cachedValues = {}
|
||||
|
||||
|
||||
cache = Cache()
|
||||
|
|
|
@ -93,6 +93,7 @@ faultCodeUnknownCommandAction = 40
|
|||
faultCodeValueTooBig = 41
|
||||
faultCodeValueTooSmall = 42
|
||||
faultCodeRoleNotInProfiles = 43
|
||||
faultCodeUnknownObjectVersion = 44
|
||||
|
||||
faultCodeUnknownVoteToken = 1000
|
||||
faultCodeUnknownVoterToken = 1001
|
||||
|
@ -560,6 +561,15 @@ class RoleNotInProfiles(BaseFault):
|
|||
def makeFaultString(self, role):
|
||||
return 'Role (%s) not in profiles' % role
|
||||
|
||||
|
||||
class UnknownObjectVersion(BaseFault):
|
||||
faultCode = faultCodeUnknownObjectVersion
|
||||
uiFaultString = N_('Unknown object revision')
|
||||
|
||||
def makeFaultString(self, objectId, versionNumber):
|
||||
return 'Unknown version (%d) for object %s' % (versionNumber, objectId)
|
||||
|
||||
|
||||
# Dataflow.
|
||||
|
||||
|
||||
|
|
|
@ -1391,8 +1391,10 @@ class SpipParser:
|
|||
if localId:
|
||||
dispatcherHost = context.getVar('dispatcherId')[11:]
|
||||
serverRole = 'uploadfiles'
|
||||
id = '[{glasnost:partialid:%s:%s}]' % (serverRole, localId)
|
||||
name = '[{glasnost:label:%s:%s}]' % (serverRole, localId)
|
||||
id = '[{glasnost:partialid:%s:%s:%s}]' % (
|
||||
dispatcherHost, serverRole, localId)
|
||||
name = '[{glasnost:label:%s:%s:%s}]' % (
|
||||
dispatcherHost, serverRole, localId)
|
||||
if (width or height) and self.formatter.prescaleImage():
|
||||
url = '[{glasnost:thumbnail:%s:%s:%s:width=%s:height=%s}]' % (
|
||||
dispatcherHost, serverRole, localId, width, height)
|
||||
|
|
|
@ -77,3 +77,4 @@ class Property(things.BaseThing):
|
|||
slotNames += ['name', 'kind']
|
||||
return slotNames
|
||||
things.register(Property)
|
||||
|
||||
|
|
|
@ -104,17 +104,6 @@ class ArticlesProxy(ArticlesCommonMixin, ObjectsProxy):
|
|||
objectId, requiredSlotNames, multiCall = multiCall)
|
||||
return [lazyObject() for lazyObject in multiCall.call()]
|
||||
|
||||
def getObjectDiff(self, objectId, editionTime):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
result = callServer(
|
||||
commonTools.extractServerId(objectId),
|
||||
'getObjectDiff',
|
||||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId,
|
||||
editionTime])
|
||||
result['diff'] = [ iso8859_15(line) for line in result['diff']]
|
||||
return result
|
||||
|
||||
def getObjectDocBookChapter(self, objectId):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
return iso8859_15(callServer(
|
||||
|
@ -123,14 +112,6 @@ class ArticlesProxy(ArticlesCommonMixin, ObjectsProxy):
|
|||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId]))
|
||||
|
||||
def getObjectHistory(self, objectId):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
return callServer(
|
||||
commonTools.extractServerId(objectId),
|
||||
'getObjectHistory',
|
||||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId])
|
||||
|
||||
def getObjectLatexChapter(self, objectId):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
return iso8859_15(callServer(
|
||||
|
@ -139,17 +120,6 @@ class ArticlesProxy(ArticlesCommonMixin, ObjectsProxy):
|
|||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId]))
|
||||
|
||||
def getObjectVersion(self, objectId, editionTime):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
result = callServer(
|
||||
commonTools.extractServerId(objectId),
|
||||
'getObjectVersion',
|
||||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId,
|
||||
editionTime])
|
||||
result['body'] = iso8859_15(result['body'])
|
||||
return result
|
||||
|
||||
def search(self, searchTerms, scope, language, serverId = None):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
serverId = self.getServerId(serverId = serverId)
|
||||
|
|
|
@ -1391,6 +1391,15 @@ class ObjectsProxy(ObjectsCommonMixin, AdministrableProxyMixin, Proxy):
|
|||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId, utf8(path), digest]))
|
||||
|
||||
def getObjectWithVersion(self, objectId, versionNumber):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
objectImport = callServer(
|
||||
commonTools.extractServerId(objectId),
|
||||
'getObjectWithVersion',
|
||||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId, versionNumber])
|
||||
return commonTools.importThing(objectImport)
|
||||
|
||||
def getPartialObject(self, objectId, requiredSlotNames, multiCall = None):
|
||||
"""Retrieve partial object instance from the server.
|
||||
|
||||
|
@ -1449,6 +1458,14 @@ class ObjectsProxy(ObjectsCommonMixin, AdministrableProxyMixin, Proxy):
|
|||
resultHandler = handleResult,
|
||||
multiCall = multiCall)
|
||||
|
||||
def getRevisionsInfos(self, objectId):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
return callServer(
|
||||
commonTools.extractServerId(objectId),
|
||||
'getRevisionsInfos',
|
||||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId])
|
||||
|
||||
def hasObject(self, objectId):
|
||||
"""Test if the server contains an object.
|
||||
|
||||
|
|
|
@ -73,3 +73,4 @@ class UploadFilesProxy(UploadFilesCommonMixin, ObjectsProxy):
|
|||
[commonTools.extractServerId(objectId), getApplicationToken(),
|
||||
userToken, objectId, width, height])
|
||||
return commonTools.importThing(objectImport)
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ __version__ = '$Revision$'[11:-2]
|
|||
|
||||
|
||||
import copy
|
||||
import cPickle
|
||||
import fcntl
|
||||
import getopt
|
||||
import os
|
||||
|
@ -53,6 +54,7 @@ import re
|
|||
import shutil
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
from threading import Lock
|
||||
import traceback
|
||||
import UserDict
|
||||
|
@ -632,6 +634,50 @@ class ObjectsVirtualServer(AdministrableVirtualServer):
|
|||
self.nextLocalId += 1
|
||||
return result
|
||||
|
||||
def getObjectWithVersion(self, objectId, versionNumber):
|
||||
server = context.getVar('server')
|
||||
rcFilePath = os.path.join(
|
||||
self.dataDirectoryPath,
|
||||
server.applicationName + '.revisions.pickle')
|
||||
try:
|
||||
fd = open(rcFilePath)
|
||||
except IOError:
|
||||
return None
|
||||
while 1:
|
||||
try:
|
||||
revision = cPickle.load(fd)
|
||||
except EOFError:
|
||||
break
|
||||
if revision['object'].id != objectId:
|
||||
continue
|
||||
if revision['object'].version != versionNumber:
|
||||
continue
|
||||
return revision['object']
|
||||
return None
|
||||
|
||||
def getRevisionsInfos(self, objectId):
|
||||
server = context.getVar('server')
|
||||
rcFilePath = os.path.join(
|
||||
self.dataDirectoryPath,
|
||||
server.applicationName + '.revisions.pickle')
|
||||
try:
|
||||
fd = open(rcFilePath)
|
||||
except IOError:
|
||||
return []
|
||||
revisions = []
|
||||
while 1:
|
||||
try:
|
||||
revision = cPickle.load(fd)
|
||||
except EOFError:
|
||||
break
|
||||
if revision['object'].id != objectId:
|
||||
continue
|
||||
revision['version'] = revision['object'].version
|
||||
del revision['object']
|
||||
revisions.append(revision)
|
||||
fd.close()
|
||||
return revisions
|
||||
|
||||
def importNonCore(self, importDirectoryPath):
|
||||
AdministrableVirtualServer.importNonCore(self, importDirectoryPath)
|
||||
for object in self.objects.values():
|
||||
|
@ -671,8 +717,41 @@ class ObjectsVirtualServer(AdministrableVirtualServer):
|
|||
for object in self.objects.values():
|
||||
object.removeIds(rolesToKeep)
|
||||
|
||||
def saveObjectRevision(self, objectId):
|
||||
if self.isReadOnly:
|
||||
return
|
||||
server = context.getVar('server')
|
||||
if not server.useDataFile:
|
||||
return
|
||||
object = self.loadObjectCore(objectId)
|
||||
object.acquireNonCore()
|
||||
data = {
|
||||
'time': time.time(),
|
||||
'userId': getProxyForServerRole('authentication').getUserId(),
|
||||
'object': object
|
||||
}
|
||||
try:
|
||||
if not os.access(self.dataDirectoryPath, os.F_OK):
|
||||
os.mkdir(self.dataDirectoryPath)
|
||||
os.chmod(self.dataDirectoryPath, 0750)
|
||||
rcFilePath = os.path.join(
|
||||
self.dataDirectoryPath,
|
||||
server.applicationName + '.revisions.pickle')
|
||||
self.lock.acquire()
|
||||
rcFile = open(rcFilePath, 'a')
|
||||
cPickle.dump(data, rcFile)
|
||||
rcFile.close()
|
||||
os.chmod(rcFilePath, 0640)
|
||||
self.lock.release()
|
||||
finally:
|
||||
object.releaseNonCore()
|
||||
|
||||
def savePendingRequests(self, saveNeeded = 0):
|
||||
if self.objectSaveActions is not None:
|
||||
for objectId in self.objectSaveActions.keys():
|
||||
if self.objectSaveActions[objectId] != 'save':
|
||||
continue
|
||||
self.saveObjectRevision(objectId)
|
||||
del self.objectSaveActions
|
||||
saveNeeded = 1
|
||||
# To remove as soon as the saveAllFIXME method will be removed.
|
||||
|
@ -2282,6 +2361,23 @@ class ObjectsServer(AdministrableServerMixin, Server):
|
|||
object.releaseNonCore()
|
||||
return result
|
||||
|
||||
def getObjectWithVersionXmlRpc(self, objectId, versionNumber):
|
||||
if not self.canGetObject(objectId):
|
||||
raise faults.UserAccessDenied()
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
object = virtualServer.getObjectWithVersion(objectId, versionNumber)
|
||||
if not object:
|
||||
raise faults.UnknownObjectVersion(objectId, versionNumber)
|
||||
return object.exportToXmlRpc()
|
||||
|
||||
def getRevisionsInfos(self, objectId):
|
||||
if not self.canGetObject(objectId):
|
||||
raise faults.UserAccessDenied()
|
||||
virtualServerId = context.getVar('applicationId')
|
||||
virtualServer = self.getVirtualServer(virtualServerId)
|
||||
return virtualServer.getRevisionsInfos(objectId)
|
||||
|
||||
def hasObject(self, objectId):
|
||||
"""Indicate whether the server contains an object.
|
||||
|
||||
|
@ -2348,8 +2444,7 @@ class ObjectsServer(AdministrableServerMixin, Server):
|
|||
if not self.canModifyObject(object.id) or not (
|
||||
self.isAdmin()
|
||||
or not objectChanges.hasSlotName('writersSet')
|
||||
or getProxyForServerRole('authentication'
|
||||
).setContainsUser(
|
||||
or getProxyForServerRole('authentication').setContainsUser(
|
||||
objectChanges.getSlot('writersSet').getValue())):
|
||||
if not object.canBeModifiedByClient():
|
||||
raise faults.UserAccessDenied()
|
||||
|
@ -2484,6 +2579,9 @@ class ObjectsServer(AdministrableServerMixin, Server):
|
|||
self.getObjectStringFromDigestXmlRpc)
|
||||
self.registerPublicMethod('getPartialObject',
|
||||
self.getPartialObjectXmlRpc)
|
||||
self.registerPublicMethod('getObjectWithVersion',
|
||||
self.getObjectWithVersionXmlRpc)
|
||||
self.registerPublicMethod('getRevisionsInfos')
|
||||
self.registerPublicMethod('hasObject')
|
||||
self.registerPublicMethod('hasObjectSlot')
|
||||
self.registerPublicMethod('modifyObject',
|
||||
|
|
|
@ -135,7 +135,7 @@ class Article(ObjectWebMixin, Article):
|
|||
context.setVar('pageTitle', title)
|
||||
|
||||
sectionLevel = context.getVar('sectionLevel')
|
||||
titleTag = getattr(X, 'h%s' % (sectionLevel+1))
|
||||
titleTag = getattr(X, 'h%s' % (sectionLevel))
|
||||
if not (parentSlot and parentSlot.getLabel() == 'Root') and title:
|
||||
layout += titleTag(_class = 'title')(title)
|
||||
|
||||
|
@ -192,54 +192,6 @@ class ArticlesWeb(ObjectsWebMixin, CommentableObjectMixin, ArticlesProxy):
|
|||
return ObjectsWebMixin.viewAll(self, slotNames = ['editionTime'])
|
||||
all.isPublicForWeb = 1
|
||||
|
||||
def diff(self, id, editionTime):
|
||||
try:
|
||||
editionTime = float(editionTime)
|
||||
except ValueError:
|
||||
return pageNotFound()
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
if not self.canGetObjectHistory(id):
|
||||
return accessForbidden()
|
||||
article = self.getObject(id)
|
||||
version = self.getObjectDiff(id, editionTime)
|
||||
|
||||
label = article.getLabelTranslated(
|
||||
context.getVar('readLanguages'))
|
||||
|
||||
layout = X.array()
|
||||
if version['diff']:
|
||||
for line in version['diff']:
|
||||
marker = line[0]
|
||||
line = line[1:]
|
||||
while line and line[-1] in ['\r', '\n']:
|
||||
line = line[:-1]
|
||||
stripped = line.lstrip()
|
||||
if len(line) - len(stripped) > 0:
|
||||
newLine = X.array()
|
||||
for i in range(len(line) - len(stripped)):
|
||||
newLine += X.nbsp
|
||||
newLine += stripped
|
||||
line = newLine
|
||||
if not line:
|
||||
line = X.nbsp
|
||||
if marker == '@':
|
||||
layout += X.hr(_class = 'diff')
|
||||
elif marker == '\\':
|
||||
if stripped == 'No newline at end of file':
|
||||
continue
|
||||
layout += X.div(_class = 'diff-error')(line)
|
||||
elif marker == '+':
|
||||
layout += X.div(_class = 'diff-new')(line)
|
||||
elif marker == '-':
|
||||
layout += X.div(_class = 'diff-old')(line)
|
||||
else:
|
||||
layout += X.div(_class = 'diff-context')(line)
|
||||
layout += X.hr(_class = 'diff')
|
||||
layout += X.br()
|
||||
return writePageLayout(layout, _('Differences - %s') % label)
|
||||
diff.isPublicForWeb = 1
|
||||
|
||||
def docBook(self, id):
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
|
@ -279,7 +231,7 @@ class ArticlesWeb(ObjectsWebMixin, CommentableObjectMixin, ArticlesProxy):
|
|||
context.getVar('readLanguages'))
|
||||
|
||||
layout = X.array()
|
||||
layout += X.pre(_class = 'spip')(docBook)
|
||||
layout += X.pre(docBook)
|
||||
return writePageLayout(layout, _('DocBook Source - %s') % label)
|
||||
docBookSource.isPublicForWeb = 1
|
||||
|
||||
|
@ -293,62 +245,15 @@ class ArticlesWeb(ObjectsWebMixin, CommentableObjectMixin, ArticlesProxy):
|
|||
layout = X.array()
|
||||
layout += ObjectsWebMixin.getViewNavigationButtonsBarLayout(
|
||||
self, object, fields)
|
||||
if self.canGetObjectHistory(object.id):
|
||||
layout += X.buttonStandalone(
|
||||
'history', X.idUrl(object.id, 'history'))
|
||||
#if self.canGetObjectHistory(object.id):
|
||||
# layout += X.buttonStandalone(
|
||||
# 'history', X.idUrl(object.id, 'history'))
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
if userToken and object.format and object.format != 'text':
|
||||
layout += X.buttonStandalone(
|
||||
'source', X.idUrl(object.id, 'source'))
|
||||
return layout
|
||||
|
||||
def history(self, id):
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
if not self.canGetObjectHistory(id):
|
||||
return accessForbidden()
|
||||
article = self.getObject(id)
|
||||
history = self.getObjectHistory(id)
|
||||
|
||||
label = article.getLabelTranslated(
|
||||
context.getVar('readLanguages'))
|
||||
|
||||
layout = X.array()
|
||||
if len(history) > 0:
|
||||
table = X.table(_class = 'history')
|
||||
layout += table
|
||||
thead = X.thead()
|
||||
table += thead
|
||||
thead += X.tr(
|
||||
X.th(_('Date')),
|
||||
X.th(_('Author')),
|
||||
X.th(''),
|
||||
X.th('') )
|
||||
tbody = X.tbody()
|
||||
table += tbody
|
||||
history.reverse()
|
||||
for version in history:
|
||||
editionTime = time.strftime(
|
||||
'%Y-%m-%d %H:%M:%S',
|
||||
time.localtime(version['editionTime']))
|
||||
if version.has_key('editorId'):
|
||||
editorHypertextLabel= X.objectHypertextLabel(
|
||||
version['editorId'])
|
||||
else:
|
||||
editorHypertextLabel = None
|
||||
tbody += X.tr(
|
||||
X.td(editionTime),
|
||||
X.td(editorHypertextLabel),
|
||||
X.td(X.buttonStandalone(
|
||||
'version', X.idUrl(id, 'version').add(
|
||||
'editionTime', version['editionTime']))),
|
||||
X.td(X.buttonStandalone('diff', X.idUrl(id, 'diff').add(
|
||||
'editionTime', version['editionTime']))),
|
||||
)
|
||||
layout += X.br()
|
||||
return writePageLayout(layout, _('History - %s') % label)
|
||||
history.isPublicForWeb = 1
|
||||
|
||||
def source(self, id):
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
|
@ -365,41 +270,10 @@ class ArticlesWeb(ObjectsWebMixin, CommentableObjectMixin, ArticlesProxy):
|
|||
label = article.getLabel()
|
||||
|
||||
layout = X.array()
|
||||
layout += X.pre(_class = 'spip')(body)
|
||||
layout += X.pre(body)
|
||||
return writePageLayout(layout, _('Source - %s') % label)
|
||||
source.isPublicForWeb = 1
|
||||
|
||||
def version(self, id, editionTime):
|
||||
try:
|
||||
editionTime = float(editionTime)
|
||||
except ValueError:
|
||||
return pageNotFound()
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
if not self.canGetObjectHistory(id):
|
||||
return accessForbidden()
|
||||
article = self.getObject(id)
|
||||
version = self.getObjectVersion(id, editionTime)
|
||||
article.format = version['format']
|
||||
article.body = version['body']
|
||||
slot = article.getSlot('body')
|
||||
article.body_kind = copy.copy(article.body_kind)
|
||||
# Deleted in article.getViewLayout() below.
|
||||
article.body_kind.isTranslatable = 0
|
||||
if version.has_key('editorId'):
|
||||
article.lastEditorId = version['editorId']
|
||||
article.editionTime = version['editionTime']
|
||||
|
||||
label = article.getLabelTranslated(context.getVar('readLanguages'))
|
||||
|
||||
layout = X.array()
|
||||
slot = slots.Root(article)
|
||||
widget = slot.getWidget()
|
||||
layout += widget.getModelPageBodyLayout(slot, None)
|
||||
|
||||
return writePageLayout(layout, _('Version - %s') % label)
|
||||
version.isPublicForWeb = 1
|
||||
|
||||
def viewAll(self):
|
||||
context.push(_level = 'viewAll',
|
||||
defaultDispatcherId = context.getVar('dispatcherId'))
|
||||
|
@ -435,7 +309,7 @@ class ArticlesWeb(ObjectsWebMixin, CommentableObjectMixin, ArticlesProxy):
|
|||
self.getViewAllNavigationButtonsBarLayout = \
|
||||
self.getViewAllNavigationButtonsBarLayoutSomeTimes
|
||||
layout += self.getViewAllButtonsBarLayout()
|
||||
del(self.getViewAllNavigationButtonsBarLayout)
|
||||
del self.getViewAllNavigationButtonsBarLayout
|
||||
finally:
|
||||
context.pull(_level = 'viewAll')
|
||||
return writePageLayout(layout, _('Articles'))
|
||||
|
|
|
@ -107,7 +107,6 @@ class AuthenticationWeb(AdministrableWebMixin, AuthenticationProxy):
|
|||
userToken = self.getUserToken(authMethod, authObject)
|
||||
session = context.getVar('session')
|
||||
sessionToken = context.getVar('sessionToken')
|
||||
oldSession = session
|
||||
if session is None:
|
||||
req = context.getVar('req')
|
||||
sessionsProxy = getProxyForServerRole('sessions')
|
||||
|
@ -122,9 +121,9 @@ class AuthenticationWeb(AdministrableWebMixin, AuthenticationProxy):
|
|||
context.setVar('session', session)
|
||||
context.setVar('userToken', userToken)
|
||||
userId = self.getUserId()
|
||||
session['userId'] = userId
|
||||
context.setVar('userId', userId)
|
||||
session['userToken'] = userToken
|
||||
session['userId'] = userId
|
||||
session['isDirty'] = 1
|
||||
nextUri = context.getVar('nextUri') or ''
|
||||
if not nextUri:
|
||||
|
@ -163,13 +162,13 @@ class AuthenticationWeb(AdministrableWebMixin, AuthenticationProxy):
|
|||
# getProxyForServerRole('sessions').deleteSession(sessionToken)
|
||||
# context.delVar('sessionToken')
|
||||
# context.delVar('session')
|
||||
if session.has_key('userId'):
|
||||
del session['userId']
|
||||
session['isDirty'] = 1
|
||||
context.setVar('userId', '')
|
||||
if session.has_key('userToken'):
|
||||
del session['userToken']
|
||||
session['isDirty'] = 1
|
||||
if session.has_key('userId'):
|
||||
del session['userId']
|
||||
session['isDirty'] = 1
|
||||
context.setVar('userToken', '')
|
||||
# if not context.getVar('sessionTokenInCookie', default = 0):
|
||||
# # The sessionToken was not stored in a cookie, so don't try to
|
||||
|
|
|
@ -58,7 +58,7 @@ class GlasnostTALInterpreter(TALInterpreter):
|
|||
try:
|
||||
return TALInterpreter.insertHTMLStructure(self, text, repldict)
|
||||
except HTMLParseError, e:
|
||||
webTools.sendTalkBack(context.getVar(req),
|
||||
webTools.sendTalkBack(context.getVar('req'),
|
||||
'%s\n\n%s\n' % (str(e), text))
|
||||
if not context.getVar('debug'):
|
||||
self.stream_write('\n<!-- errors detected in HTML -->\n')
|
||||
|
|
|
@ -273,16 +273,18 @@ class ObjectsWebMixin(AdministrableWebMixin):
|
|||
objectName = self.objectName)
|
||||
confirmDelete.isPublicForWeb = 1
|
||||
|
||||
def confirmDelete(self, id):
|
||||
if id and not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
|
||||
return confirmDelete(
|
||||
X.idUrl(id, 'delete'),
|
||||
id = id,
|
||||
objectName = self.objectName)
|
||||
confirmDelete.isPublicForWeb = 1
|
||||
|
||||
def confirmMakeVersionCurrent(self, id, versionNumber):
|
||||
layout = X.array()
|
||||
object = self.getObject(id)
|
||||
layout += X.p(_('Are you sure you want to replace the current version (%s) of "%s" by this previous version (%s) ?') % (object.version, object.getLabel(), versionNumber))
|
||||
layout += X.div(_class = 'buttons-bar')(
|
||||
X.buttonStandalone('replace',
|
||||
X.idUrl(id, 'makeVersionCurrent/%s' % versionNumber)),
|
||||
X.buttonStandalone('cancel',
|
||||
X.idUrl(id, 'version/%s' % versionNumber)) )
|
||||
return writePageLayout(layout, _('Confirm Replacement'))
|
||||
confirmMakeVersionCurrent.isPublicForWeb = 1
|
||||
|
||||
def delete(self, id):
|
||||
method = context.getVar('httpMethod')
|
||||
if id and self.hasObject(id):
|
||||
|
@ -302,6 +304,46 @@ class ObjectsWebMixin(AdministrableWebMixin):
|
|||
return pageNotFound()
|
||||
delete.isPublicForWeb = 1
|
||||
|
||||
def diff(self, id, newVersionNumber, oldVersionNumber = ''):
|
||||
newVersionNumber = int(newVersionNumber)
|
||||
if oldVersionNumber:
|
||||
oldVersionNumber = int(oldVersionNumber)
|
||||
else:
|
||||
oldVersionNumber = newVersionNumber - 1
|
||||
newObject = self.getObjectWithVersion(id, int(newVersionNumber))
|
||||
oldObject = self.getObjectWithVersion(id, int(oldVersionNumber))
|
||||
|
||||
slotNames = newObject.getLayoutSlotNames(None)
|
||||
if 'modificationTime' in slotNames:
|
||||
slotNames.remove('modificationTime')
|
||||
slotNames.insert(slotNames.index('version')+1, 'modificationTime')
|
||||
layout = X.array()
|
||||
for slotName in slotNames:
|
||||
newObjectSlot = newObject.getSlot(slotName)
|
||||
newValue = newObjectSlot.getValue()
|
||||
oldObjectSlot = oldObject.getSlot(slotName)
|
||||
oldValue = oldObjectSlot.getValue()
|
||||
|
||||
if newValue == oldValue:
|
||||
continue
|
||||
|
||||
widget = newObjectSlot.getWidget()
|
||||
newObjectSlot.getKind().isTranslatable = 0
|
||||
oldObjectSlot.getKind().isTranslatable = 0
|
||||
diffValue = widget.getHtmlViewDiffValue(
|
||||
oldObjectSlot, newObjectSlot)
|
||||
|
||||
row = X.div(_class = 'row')
|
||||
layout += row
|
||||
row += X.span(_class = 'label')(
|
||||
_(widget.getModelLabel(newObjectSlot)))
|
||||
cell = X.enclose(diffValue, _class = 'cell')
|
||||
row += cell
|
||||
|
||||
return writePageLayout(layout,
|
||||
_('%s - Differences between versions') % newObject.getLabel())
|
||||
diff.isPublicForWeb = 1
|
||||
|
||||
def download(self, id, filename = '', path = 'self'):
|
||||
localId = commonTools.extractLocalId(id)
|
||||
if localId == '__admin__':
|
||||
|
@ -644,11 +686,48 @@ class ObjectsWebMixin(AdministrableWebMixin):
|
|||
userToken = context.getVar('userToken', default = '')
|
||||
if userToken:
|
||||
layout += X.buttonStandalone('view-list', X.actionUrl())
|
||||
layout += X.buttonStandalone(
|
||||
'history', X.idUrl(object.id, 'history'))
|
||||
return layout
|
||||
|
||||
def getViewOtherActionButtonsBarLayout(self, object, fields):
|
||||
return None
|
||||
|
||||
def history(self, id):
|
||||
revisions = self.getRevisionsInfos(id)
|
||||
revisions.reverse()
|
||||
object = self.getObject(id)
|
||||
label = object.getLabelTranslated(context.getVar('readLanguages'))
|
||||
pageTitle = '%s - %s - %s' % (
|
||||
_('History'), _(self.objectNameCapitalized), label)
|
||||
if not revisions:
|
||||
layout = X.p(_("""No revision informations are available for this object."""))
|
||||
return writePageLayout(layout, pageTitle)
|
||||
table = X.table()
|
||||
table += X.tr(
|
||||
X.th(_('Date')),
|
||||
X.th(_('User')),
|
||||
X.th(''))
|
||||
for revision in revisions:
|
||||
revisionTime = time.strftime('%Y-%m-%d %H:%M:%S',
|
||||
time.localtime(revision['time']))
|
||||
if revision['userId']:
|
||||
userLabel = X.objectHypertextLabel(revision['userId'])
|
||||
else:
|
||||
userLabel = _('Unknown')
|
||||
td = X.td(X.buttonStandalone('version',
|
||||
X.idUrl(id,
|
||||
'version/%d' % revision['version'])))
|
||||
if revision is not revisions[-1]:
|
||||
td += X.buttonStandalone('diff',
|
||||
X.idUrl(id, 'diff/%d' % revision['version']))
|
||||
table += X.tr(
|
||||
X.td(revisionTime),
|
||||
X.td(userLabel),
|
||||
td)
|
||||
return writePageLayout(table, pageTitle)
|
||||
history.isPublicForWeb = 1
|
||||
|
||||
def id(self, id):
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
|
@ -701,6 +780,14 @@ class ObjectsWebMixin(AdministrableWebMixin):
|
|||
req.write(data)
|
||||
return OK
|
||||
imageEdit.isPublicForWeb = 1
|
||||
|
||||
def makeVersionCurrent(self, id, versionNumber):
|
||||
oldObject = self.getObjectWithVersion(id, int(versionNumber))
|
||||
currentObject = self.getObject(id)
|
||||
oldObject.version = currentObject.version
|
||||
self.modifyObject(oldObject)
|
||||
return redirect(X.idUrl(id))
|
||||
makeVersionCurrent.isPublicForWeb = 1
|
||||
|
||||
def rss(self):
|
||||
lastObjects = self.getLastObjects(20, None, None, None)
|
||||
|
@ -1013,6 +1100,21 @@ class ObjectsWebMixin(AdministrableWebMixin):
|
|||
return self.view(id)
|
||||
use.isPublicForWeb = 1
|
||||
|
||||
def version(self, id, versionNumber):
|
||||
#return self.viewObject(object)
|
||||
object = self.getObjectWithVersion(id, int(versionNumber))
|
||||
slot = slots.Root(object)
|
||||
widget = slot.getWidget()
|
||||
layout = widget.getModelPageBodyLayout(slot, None)
|
||||
buttonsBar = X.div(_class = 'buttons-bar')
|
||||
layout += buttonsBar
|
||||
buttonsBar += X.buttonStandalone('history', X.idUrl(id, 'history'))
|
||||
buttonsBar += X.buttonStandalone(_('Make this version current'),
|
||||
X.idUrl(id, 'confirmMakeVersionCurrent/%s' % versionNumber))
|
||||
return writePageLayout(layout,
|
||||
_('%s - Version %d') % (object.getLabel(), object.version))
|
||||
version.isPublicForWeb = 1
|
||||
|
||||
def view(self, id):
|
||||
if not self.hasObject(id):
|
||||
return pageNotFound()
|
||||
|
@ -1020,7 +1122,10 @@ class ObjectsWebMixin(AdministrableWebMixin):
|
|||
return accessForbidden()
|
||||
object = self.getObject(id)
|
||||
rememberObject(id)
|
||||
return self.viewObject(object)
|
||||
view.isPublicForWeb = 1
|
||||
|
||||
def viewObject(self, object):
|
||||
label = object.getLabelTranslated(context.getVar('readLanguages'))
|
||||
|
||||
layout = X.array()
|
||||
|
@ -1043,7 +1148,6 @@ class ObjectsWebMixin(AdministrableWebMixin):
|
|||
layout = writePageLayout(layout, pageTitle)
|
||||
context.pull()
|
||||
return layout
|
||||
view.isPublicForWeb = 1
|
||||
|
||||
def viewAll(self, slotNames = None):
|
||||
if type(slotNames) is not types.ListType:
|
||||
|
|
|
@ -88,8 +88,11 @@ class Rubric(ObjectWebMixin, Rubric):
|
|||
getObjectLabelTranslated(self.id,
|
||||
context.getVar('readLanguages')))
|
||||
object.title = ''
|
||||
|
||||
context.push()
|
||||
else:
|
||||
context.push(sectionLevel = context.getVar('sectionLevel')+1)
|
||||
layout += object.getEmbeddedViewLayout()
|
||||
context.pull()
|
||||
|
||||
if web.canModifyObject(self.contentId):
|
||||
layout += X.buttonStandalone(
|
||||
|
|
|
@ -325,6 +325,7 @@ register(Localization)
|
|||
|
||||
|
||||
class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
||||
|
||||
def canViewAll(self, serverId = None):
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
if userToken:
|
||||
|
@ -394,6 +395,26 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
finally:
|
||||
context.pull(_level = 'edit')
|
||||
|
||||
def everyString(self, localizationKey):
|
||||
fromLanguage = localizationKey[:2]
|
||||
toLanguage = localizationKey[2:4]
|
||||
pageTitle = '%s: %s' % (
|
||||
_('Translations'),
|
||||
_('%(from)s to %(to)s') % {
|
||||
'from': _(translation.languageLabels[
|
||||
fromLanguage]),
|
||||
'to': _(translation.languageLabels[toLanguage]),
|
||||
})
|
||||
|
||||
digestsAndLabels = self.getSomeDigestsAndLabels(
|
||||
localizationKey, -1, ['untranslated'])
|
||||
layout = self.getObjectsSectionLayout(
|
||||
localizationKey, digestsAndLabels,
|
||||
_("""Strings yet to translate"""))
|
||||
|
||||
return writePageLayout(layout, pageTitle)
|
||||
everyString.isPublicForWeb = 1
|
||||
|
||||
def getAdmin(self, serverId = None):
|
||||
admin = TranslationsProxy.getAdmin(self, serverId = serverId)
|
||||
admin.translationLanguages = []
|
||||
|
@ -616,9 +637,16 @@ class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
|
|||
otherLocalization['label'])
|
||||
layout += X.br()
|
||||
|
||||
buttonsBar = X.div(_class = 'buttons-bar')()
|
||||
if localizationKey:
|
||||
buttonsBar += X.buttonStandalone(
|
||||
_('Every untranslated string'),
|
||||
X.actionUrl('everyString/%s' % localizationKey))
|
||||
if self.canGetAdmin():
|
||||
layout += X.div(_class = 'buttons-bar')(
|
||||
X.buttonStandalone('settings', X.actionUrl('admin')))
|
||||
buttonsBar += X.buttonStandalone(
|
||||
'settings', X.actionUrl('admin'))
|
||||
if buttonsBar.children:
|
||||
layout += buttonsBar
|
||||
finally:
|
||||
context.pull(_level = 'viewAll')
|
||||
return writePageLayout(layout, pageTitle)
|
||||
|
|
|
@ -87,15 +87,6 @@ class UploadFile(ObjectWebMixin, UploadFile):
|
|||
dataType_kind_widget_fieldLabel = N_('Mime Type')
|
||||
dataType_kind_widgetName = 'InputText'
|
||||
|
||||
height_kind_hasToRepairField = 0
|
||||
height_kind_hasToSubmitField = 0
|
||||
height_kind_stateInEditMode = 'read-only'
|
||||
height_kind_min = 0
|
||||
height_kind_textMaxLength = 4
|
||||
height_kind_widget_fieldLabel = N_('Height')
|
||||
height_kind_widget_size = 4
|
||||
height_kind_widgetName = 'InputText'
|
||||
|
||||
size_kind_hasToSubmitField = 0
|
||||
size_kind_stateInEditMode = 'read-only'
|
||||
size_kind_min = 0
|
||||
|
@ -108,21 +99,12 @@ class UploadFile(ObjectWebMixin, UploadFile):
|
|||
title_kind_widget_size = 40
|
||||
title_kind_widgetName = 'InputText'
|
||||
|
||||
width_kind_hasToRepairField = 0
|
||||
width_kind_hasToSubmitField = 0
|
||||
width_kind_stateInEditMode = 'read-only'
|
||||
width_kind_min = 0
|
||||
width_kind_textMaxLength = 4
|
||||
width_kind_widget_fieldLabel = N_('Width')
|
||||
width_kind_widget_size = 4
|
||||
width_kind_widgetName = 'InputText'
|
||||
|
||||
def getEditLayoutSlotNames(self, fields, parentSlot = None):
|
||||
slotNames = ObjectWebMixin.getViewLayoutSlotNames(
|
||||
self, fields, parentSlot = parentSlot)
|
||||
slotNames = slotNames[:]
|
||||
hiddenSlotNames = []
|
||||
for slotName in ('height', 'size', 'width'):
|
||||
for slotName in ['size', 'properties'] + self.getPropertyNames():
|
||||
hiddenSlotNames.append(slotName)
|
||||
if self.id is None:
|
||||
for slotName in ('creationTime', 'dataFileName', 'dataType',
|
||||
|
@ -138,16 +120,13 @@ class UploadFile(ObjectWebMixin, UploadFile):
|
|||
self, fields, parentSlot = parentSlot)
|
||||
slotNames = slotNames[:]
|
||||
dataType = self.getSlot('dataType').getValue()
|
||||
if not isTypeOfMimeType(dataType, 'image'):
|
||||
if 'height' in slotNames:
|
||||
slotNames.remove('height')
|
||||
if 'width' in slotNames:
|
||||
slotNames.remove('width')
|
||||
userToken = context.getVar('userToken', default = '')
|
||||
if 'properties' in slotNames:
|
||||
slotNames.remove('properties')
|
||||
if not userToken or context.getVar('useCompactLayout', default = 0):
|
||||
for slotName in [
|
||||
'creationTime', 'dataFileName', 'dataType', 'height',
|
||||
'size', 'modificationTime', 'readersSet', 'width',
|
||||
'creationTime', 'dataFileName', 'dataType',
|
||||
'size', 'modificationTime', 'readersSet',
|
||||
'writersSet']:
|
||||
if slotName in slotNames:
|
||||
slotNames.remove(slotName)
|
||||
|
@ -199,18 +178,22 @@ class UploadFilesWeb(ObjectsWebMixin, UploadFilesProxy):
|
|||
ids = self.getObjectIds()
|
||||
partialObjects = {}
|
||||
for id in ids:
|
||||
partialObjects[id] = self.getPartialObject(id, ['title'])
|
||||
partialObjects[id] = self.getPartialObject(
|
||||
id, ['title', 'dataType', 'size'])
|
||||
ids = self.getSortedIds(partialObjects)
|
||||
layout += self.getObjectsLayout(partialObjects, ids)
|
||||
layout += self.getObjectsLayout(partialObjects, ids,
|
||||
slotNames = ['dataType', 'size'])
|
||||
layout += self.getViewAllButtonsBarLayout()
|
||||
finally:
|
||||
context.pull(_level = 'viewAll')
|
||||
return writePageLayout(layout, _(self.objectsNameCapitalized))
|
||||
all.isPublicForWeb = 1
|
||||
|
||||
#uploadFileFile = cStringIO.StringIO(data)
|
||||
#uploadFileObject = PILImage.open(uploadFileFile)
|
||||
#width, height = uploadFileObject.size
|
||||
def getViewAllNavigationButtonsBarLayoutSomeTimes(self):
|
||||
layout = X.array()
|
||||
layout += X.buttonStandalone('every-uploadfiles', X.actionUrl('all'))
|
||||
layout += ObjectsWebMixin.getViewAllNavigationButtonsBarLayout(self)
|
||||
return layout
|
||||
|
||||
def image(self, id, fileName = None):
|
||||
if not self.hasObject(id):
|
||||
|
@ -295,8 +278,7 @@ class UploadFilesWeb(ObjectsWebMixin, UploadFilesProxy):
|
|||
height = int(height)
|
||||
except ValueError:
|
||||
height = 128
|
||||
object = self.getObjectThumbnail(
|
||||
id, width, height)
|
||||
object = self.getObjectThumbnail(id, width, height)
|
||||
rememberObject(id)
|
||||
|
||||
req = context.getVar('req')
|
||||
|
@ -315,16 +297,18 @@ class UploadFilesWeb(ObjectsWebMixin, UploadFilesProxy):
|
|||
if not self.canGetObject(id):
|
||||
return accessForbidden()
|
||||
|
||||
object = UploadFile()
|
||||
slotNames = [x for x in object.getSlotNames() if x != 'data']
|
||||
object = self.getPartialObject(id, slotNames)
|
||||
#object = UploadFile()
|
||||
#slotNames = [x for x in object.getSlotNames() if x != 'data']
|
||||
#object = self.getPartialObject(id, slotNames)
|
||||
# Force the presence of the download link in
|
||||
# web/widgets.py/UploadFile.getHtmlViewValue().
|
||||
# note that it fucks up previewing inline OpenOffice.org documents
|
||||
# that could be converted to html
|
||||
object.data = 'fake data'
|
||||
if object.dataType == 'application/vnd.sun.xml.writer':
|
||||
object = self.getObject(id)
|
||||
#object.data = 'fake data'
|
||||
#if object.dataType == 'application/vnd.sun.xml.writer':
|
||||
# object = self.getObject(id)
|
||||
|
||||
object = self.getObject(id)
|
||||
rememberObject(id)
|
||||
|
||||
if isTypeOfMimeType(object.dataType, 'image'):
|
||||
|
@ -380,7 +364,11 @@ class UploadFilesWeb(ObjectsWebMixin, UploadFilesProxy):
|
|||
_("""The last files"""),
|
||||
displayedSlotNames)
|
||||
|
||||
# ArticlesWeb did that; I don't remember why.
|
||||
self.getViewAllNavigationButtonsBarLayout = \
|
||||
self.getViewAllNavigationButtonsBarLayoutSomeTimes
|
||||
layout += self.getViewAllButtonsBarLayout()
|
||||
del self.getViewAllNavigationButtonsBarLayout
|
||||
finally:
|
||||
context.pull(_level = 'viewAll')
|
||||
return writePageLayout(layout, _('Files'))
|
||||
|
|
|
@ -662,6 +662,11 @@ def writePageLayout(layout, title, canCache = 1):
|
|||
dict['contextualHeaders'] = '\n'.join(context.getVar('htmlHeaders'))
|
||||
dict['body'] = layout.getAsXml()
|
||||
|
||||
try:
|
||||
import GlasnostNewTal
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
fileName = context.getVar('templateFileName')
|
||||
if not currentTemplates.has_key(fileName):
|
||||
# caching this in currentTemplates improves rendering time by about
|
||||
|
@ -672,20 +677,22 @@ def writePageLayout(layout, title, canCache = 1):
|
|||
fallbackPath = context.getVar('fallbackTemplatesDirectoryPath')
|
||||
|
||||
try:
|
||||
file = open(os.path.join(path, templateDirectoryName, fileName))
|
||||
fd = open(os.path.join(path, templateDirectoryName, fileName))
|
||||
except IOError:
|
||||
file = open(os.path.join(fallbackPath, 'default', fileName))
|
||||
fd = open(os.path.join(fallbackPath, 'default', fileName))
|
||||
# should raise 404 if it fails
|
||||
file = file.read()
|
||||
|
||||
from TAL.HTMLTALParser import HTMLTALParser
|
||||
from glasnost.web.GlasnostTALGenerator import GlasnostTALGenerator
|
||||
t = HTMLTALParser(gen = GlasnostTALGenerator(xml=0))
|
||||
t.parseString(file)
|
||||
t.parseString(fd.read())
|
||||
### with python-simpletal, it would be
|
||||
### t = GlasnostNewTal.compileHTMLTemplate(fd)
|
||||
|
||||
currentTemplates[fileName] = t
|
||||
|
||||
t = currentTemplates[fileName]
|
||||
|
||||
program, macros = t.getCode()
|
||||
# this takes roughly 0.01 seconds, not worth caching
|
||||
|
||||
|
@ -711,6 +718,14 @@ def writePageLayout(layout, title, canCache = 1):
|
|||
info[0], info[1])[0].strip()
|
||||
writePageException(exception)
|
||||
|
||||
### with python-simpletal:
|
||||
### ctx = GlasnostNewTal.Context()
|
||||
### for k, v in dict.items() + WebAPI.getAPIDict().items():
|
||||
### ctx.addGlobal(k, v)
|
||||
### interpreter = GlasnostNewTal.TemplateInterpreter()
|
||||
### interpreter.initialise(ctx, req)
|
||||
### t.expand(ctx, req, interpreter = interpreter)
|
||||
|
||||
if req.caching:
|
||||
req.closeCachePage()
|
||||
return OK
|
||||
|
|
|
@ -168,7 +168,7 @@ def sendTalkBack(req, message):
|
|||
if req.headers_in.has_key('User-Agent'):
|
||||
userAgent = req.headers_in['User-Agent']
|
||||
if req.headers_in.has_key('Host'):
|
||||
host = '%s (from host header)' % req.headers_in['Host']
|
||||
host = req.headers_in['Host']
|
||||
else:
|
||||
host = '%s (from socket.getfqdn())' % socket.getfqdn()
|
||||
message = """From: %(from)s
|
||||
|
|
|
@ -48,6 +48,7 @@ __doc__ = """Glasnost Web Widgets"""
|
|||
__version__ = '$Revision$'[11:-2]
|
||||
|
||||
|
||||
import difflib
|
||||
import locale
|
||||
import re
|
||||
import time
|
||||
|
@ -170,6 +171,11 @@ class WidgetMixin(things.ThingMixin):
|
|||
else:
|
||||
return self.getHtmlFormValue(slot, fields, **keywords)
|
||||
|
||||
def getHtmlViewDiffValue(self, oldSlot, newSlot, **keywords):
|
||||
return X.table(_class = 'diff')(
|
||||
X.td(self.getHtmlViewValue(oldSlot, None, **keywords)),
|
||||
X.td(self.getHtmlViewValue(newSlot, None, **keywords)) )
|
||||
|
||||
def getHtmlViewValue(self, slot, fields, **keywords):
|
||||
fieldValue = slot.getValue() or ''
|
||||
format = slot.getKind().getTextFormat(slot)
|
||||
|
@ -1535,6 +1541,30 @@ register(SelectId)
|
|||
|
||||
|
||||
class TextArea(WidgetMixin, proxyWidgets.TextArea):
|
||||
def getHtmlViewDiffValue(self, oldSlot, newSlot, **keywords):
|
||||
newValue = newSlot.getValue().split('\n')
|
||||
oldValue = oldSlot.getValue().split('\n')
|
||||
try:
|
||||
difflib.ndiff
|
||||
except AttributeError:
|
||||
# no ndiff function in Python 2.1
|
||||
return WidgetMixin.getHtmlViewDiffValue(
|
||||
self, oldSlot, newSlot, **keywords)
|
||||
|
||||
result = []
|
||||
for line in difflib.ndiff(oldValue, newValue):
|
||||
marker, line = line[0], line[2:]
|
||||
if marker == ' ':
|
||||
result.append(line)
|
||||
elif marker == '-':
|
||||
result.append('<span class="diff-old">%s</span>' % line)
|
||||
elif marker == '+':
|
||||
result.append('<span class="diff-new">%s</span>' % line)
|
||||
elif marker == '?':
|
||||
continue
|
||||
|
||||
return X.pre(_class = 'diff')(X.asIs('\n'.join(result)))
|
||||
|
||||
def getHtmlViewValue(self, slot, fields, **keywords):
|
||||
fieldValue, translationBar = \
|
||||
translation.getFieldValueAndTranslationBar(slot, fields)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
width: 90%;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "form-tb.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
background: #1f0f10 url('/images/autumn.jpeg') repeat fixed;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
/*background: #170434;*/
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
color: #000000;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<span metal:define-macro="breadCrumb"
|
||||
tal:define="objectPath getPathToObject(currentObject)"
|
||||
tal:condition="objectPath">
|
||||
<span tal:condition="exists('breadCrumbPrefix')" tal:replace="breadCrumbPrefix" />
|
||||
<span tal:condition="exists('breadCrumbPrefix')" tal:replace="breadCrumbPrefix">a</span>
|
||||
<div tal:condition="currentObject" tal:omit-tag="" tal:repeat="o objectPath">
|
||||
<a tal:attributes="href o.getUrl()" tal:content="o.label">breadCrumb</a> →
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<doc metal:use-macro="StandardLookAndFeel.html/pre"/>
|
||||
<doc metal:use-macro="StandardLookAndFeel.html/pre"></doc>
|
||||
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
|
||||
xmlns:metal="http://xml.zope.org/namespaces/metal"
|
||||
metal:use-macro="StandardLookAndFeel.html/master">
|
||||
|
@ -9,7 +9,7 @@
|
|||
<body>
|
||||
<div metal:fill-slot="main" tal:omit-tag="">
|
||||
<h1 tal:condition="title" tal:content="title">the title goes here</h1>
|
||||
<span tal:replace="structure body" />
|
||||
<span tal:replace="structure body">body</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -854,3 +854,18 @@ ul.article-meta li {
|
|||
line-height: 1em;
|
||||
}
|
||||
|
||||
table.diff {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
table.diff td {
|
||||
width: 45%;
|
||||
border: 1px solid black;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.diff div.changed {
|
||||
margin: 0;
|
||||
background-color: #ffff80;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* Default */
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
background: white;
|
||||
|
|
|
@ -48,11 +48,11 @@
|
|||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<span metal:use-macro="buttons.tal/preferences" />
|
||||
<span metal:use-macro="buttons.tal/admin" />
|
||||
<span metal:use-macro="buttons.tal/logout" />
|
||||
<span metal:use-macro="buttons.tal/newAccount" />
|
||||
<span metal:use-macro="buttons.tal/login" />
|
||||
<span metal:use-macro="buttons.tal/preferences"></span>
|
||||
<span metal:use-macro="buttons.tal/admin"></span>
|
||||
<span metal:use-macro="buttons.tal/logout"></span>
|
||||
<span metal:use-macro="buttons.tal/newAccount"></span>
|
||||
<span metal:use-macro="buttons.tal/login"></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
|
@ -208,12 +209,12 @@ div#commands a:active {
|
|||
}
|
||||
|
||||
|
||||
div#main-content a.button, input.button {
|
||||
div#main-content a.button, div#main-content input.button {
|
||||
color: black;
|
||||
border: 1px outset #aaa;
|
||||
}
|
||||
|
||||
div#main-content a.button:active, input.button:active {
|
||||
div#main-content a.button:active, div#main-content input.button:active {
|
||||
border: 1px inset #aaa;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
color: black;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
html, body {
|
||||
background: #f5b72e;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
width: 90%;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -34,28 +34,28 @@
|
|||
|
||||
<div id="tabs">
|
||||
<a tal:attributes="href rootUrl()" tal:translate="">Home</a>
|
||||
<div metal:use-macro="nav.tal/aBlockOneLevelAllObjects" />
|
||||
<div metal:use-macro="nav.tal/aBlockOneLevelAllObjects"></div>
|
||||
</div>
|
||||
|
||||
<div id="personalBar">
|
||||
<a class="button user" tal:condition="user" tal:attributes="href user.getUrl()" tal:content="user">userName</a>
|
||||
<span tal:condition="not user" tal:translate="">You are not logged in</span>
|
||||
<span metal:use-macro="buttons.tal/login" />
|
||||
<span metal:use-macro="buttons.tal/preferences" />
|
||||
<span metal:use-macro="buttons.tal/admin" />
|
||||
<span metal:use-macro="buttons.tal/logout" />
|
||||
<span metal:use-macro="buttons.tal/about" />
|
||||
<span metal:use-macro="buttons.tal/login">login</span>
|
||||
<span metal:use-macro="buttons.tal/preferences">preferences</span>
|
||||
<span metal:use-macro="buttons.tal/admin">admin</span>
|
||||
<span metal:use-macro="buttons.tal/logout">logout</span>
|
||||
<span metal:use-macro="buttons.tal/about">about</span>
|
||||
</div>
|
||||
|
||||
<div id="pathBar" tal:condition="fileName.startswith('index') or currentObject"
|
||||
tal:define="breadCrumbPrefix 'Vous êtes ici: '">
|
||||
<span metal:use-macro="misc.tal/breadCrumb" />
|
||||
<span metal:use-macro="misc.tal/breadCrumb">breadCrumb</span>
|
||||
</div>
|
||||
|
||||
<div id="leftBar">
|
||||
<div id="navBox" tal:condition="user">
|
||||
<span class="boxTitle">Navigation</span>
|
||||
<ul metal:use-macro="nav.tal/ulRoles" />
|
||||
<ul metal:use-macro="nav.tal/ulRoles"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import url("/css/balloonHelp.css");
|
||||
@import "balloonHelp.css";
|
||||
@import "form-tb.css";
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
|
@ -395,16 +396,16 @@ div#loginBox div a.button {
|
|||
border: 0;
|
||||
}
|
||||
|
||||
table.rst, table.spip, table.objects-table {
|
||||
table {
|
||||
border: 1px solid #8cacbb;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.spip a, table.objects-table a {
|
||||
table a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table.rst th, table.spip th, table.objects-table th {
|
||||
th {
|
||||
background: #dee7ec;
|
||||
border: 1px solid #8cacbb;
|
||||
color: black;
|
||||
|
@ -414,7 +415,7 @@ table.rst th, table.spip th, table.objects-table th {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.rst td, table.spip td, table.objects-table td {
|
||||
td {
|
||||
border-left: 1px solid #8cacbb;
|
||||
padding: 0em 1em;
|
||||
text-align: left;
|
||||
|
@ -679,36 +680,26 @@ div.system-message {
|
|||
font-size: 80%;
|
||||
}
|
||||
|
||||
div.diff-context {
|
||||
table.diff {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
div.diff-error {
|
||||
font-size: 80%;
|
||||
table.diff td {
|
||||
width: 45%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div.diff-new {
|
||||
background-color: #80ff80;
|
||||
}
|
||||
|
||||
div.diff-old {
|
||||
background-color: #ffff80;
|
||||
}
|
||||
|
||||
hr.diff {
|
||||
}
|
||||
|
||||
span.diff-equal {
|
||||
white-space: pre;
|
||||
pre.diff {
|
||||
background: inherit;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
span.diff-new {
|
||||
background-color: #80ff80;
|
||||
white-space: pre;
|
||||
background-color: #8f8;
|
||||
}
|
||||
|
||||
span.diff-old {
|
||||
background-color: #ffff80;
|
||||
white-space: pre;
|
||||
background-color: #f88;
|
||||
}
|
||||
|
||||
span.spellcheck {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import url("/css/balloonHelp.css");
|
||||
@import "balloonHelp.css";
|
||||
@import "form-tb.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
background: white;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "form-tb.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
|
@ -109,34 +111,6 @@ div#content form {
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
div.row {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
div.row .label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.row .tooltip {
|
||||
display: block;
|
||||
font-style: italic;
|
||||
font-size: smaller;
|
||||
margin-left: 2em;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.row .cell {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
div.row div.cell span.cell {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.fieldset span.cell {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div#content div.error-message h1 {
|
||||
color: red;
|
||||
border: 0;
|
||||
|
@ -270,3 +244,68 @@ pre {
|
|||
padding: 0.5em;
|
||||
}
|
||||
|
||||
div.dropdown {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 10em;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
div.dropdown ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.dropdown span {
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
background: white url('/images/v.png') top right no-repeat;
|
||||
}
|
||||
|
||||
div.dropdown:hover span {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
div.dropdown span:after {
|
||||
content: "...";
|
||||
}
|
||||
|
||||
div.dropdown:hover span:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
div.dropdown li {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.dropdown:hover ul {
|
||||
display: block;
|
||||
background: white;
|
||||
/*background: url('/images/ntbg.png');*/
|
||||
}
|
||||
|
||||
div#commands a {
|
||||
display: block;
|
||||
color: black;
|
||||
border: 1px solid white;
|
||||
text-decoration: none;
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
|
||||
div#commands a:hover {
|
||||
background: #eee;
|
||||
border: 1px outset black;
|
||||
}
|
||||
|
||||
div#commands a:active {
|
||||
border: 1px inset black;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import url("/css/balloonHelp.css");
|
||||
@import "balloonHelp.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
/* 2 columns, header and footer layout working with NN4
|
||||
thanks to http://realworldstyle.com/ */
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
body {
|
||||
background: white url(/images/upthings.jpeg) fixed top left no-repeat;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
html, body {
|
||||
background: #10743d;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import url("/css/balloonHelp.css");
|
||||
@import "balloonHelp.css";
|
||||
@import "form-lr.css";
|
||||
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
|
@ -135,16 +136,7 @@ div#content form {
|
|||
}
|
||||
|
||||
div.row .label {
|
||||
width: 9em;
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
div.row .cell {
|
||||
display: block;
|
||||
margin-left: 10em;
|
||||
}
|
||||
|
||||
div.row {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "default.css";
|
||||
@import "form-tb.css";
|
||||
/********************************************
|
||||
* entrouvert css
|
||||
*
|
||||
|
|
|
@ -11,10 +11,6 @@ testSuites = (
|
|||
'GroupsTests',
|
||||
'KindsTests',
|
||||
'VirtualHostsTests',
|
||||
'CardsTests',
|
||||
'GroupsTests',
|
||||
'KindsTests',
|
||||
'VirtualHostsTests',
|
||||
'AuthenticationLoginPasswordTests',
|
||||
'ArticlesTests',
|
||||
'SpipTests',
|
||||
|
|
Reference in New Issue