[bin] job.py: if the specified user does not exist in the DB, try with a user named 'admin'; [gen] Calendar: allow to insert additional, custom (XHTML or textual) info in any cell of the calendar, via new attribute Calendar.additionalInfo; [pod] some code refactoring; xhtml2odt: allow, when converting tables, to take into account attributes 'width' of tds; bugfix when converting 'href' attrs of 'a' tags.
This commit is contained in:
parent
ba148c51aa
commit
caca61516f
|
@ -56,6 +56,9 @@ else:
|
|||
# Log as Zope admin
|
||||
from AccessControl.SecurityManagement import newSecurityManager
|
||||
user = app.acl_users.getUserById(zopeUser)
|
||||
if not user:
|
||||
# Try with user "admin"
|
||||
user = app.acl_users.getUserById('admin')
|
||||
if not hasattr(user, 'aq_base'):
|
||||
user = user.__of__(app.acl_users)
|
||||
newSecurityManager(None, user)
|
||||
|
|
|
@ -17,8 +17,8 @@ class Calendar(Type):
|
|||
specificWritePermission=False, width=None, height=300,
|
||||
colspan=1, master=None, masterValue=None, focus=False,
|
||||
mapping=None, label=None, maxEventLength=50,
|
||||
otherCalendars=None, startDate=None, endDate=None,
|
||||
defaultDate=None):
|
||||
otherCalendars=None, additionalInfo=None, startDate=None,
|
||||
endDate=None, defaultDate=None):
|
||||
Type.__init__(self, validator, (0,1), None, default, False, False,
|
||||
show, page, group, layouts, move, False, False,
|
||||
specificReadPermission, specificWritePermission,
|
||||
|
@ -52,6 +52,12 @@ class Calendar(Type):
|
|||
# leading "#" when relevant) into which events of the calendar must
|
||||
# appear.
|
||||
self.otherCalendars = otherCalendars
|
||||
# One may want to add custom information in the calendar. When a method
|
||||
# is given in p_additionalInfo, for every cell of the month view, this
|
||||
# method will be called with a single arg (the cell's date). The
|
||||
# method's result (a string that can hold text or a chunk of XHTML) will
|
||||
# be inserted in the cell.
|
||||
self.additionalInfo = additionalInfo
|
||||
# One may limit event encoding and viewing to a limited period of time,
|
||||
# via p_startDate and p_endDate. Those parameters, if given, must hold
|
||||
# methods accepting no arg and returning a Zope DateTime instance.
|
||||
|
@ -131,6 +137,13 @@ class Calendar(Type):
|
|||
res[i][1] = res[i][0].getField(res[i][1])
|
||||
return res
|
||||
|
||||
def getAdditionalInfoAt(self, obj, date):
|
||||
'''If the user has specified a method in self.additionalInfo, we call
|
||||
it for displaying this additional info in the calendar, at some
|
||||
p_date.'''
|
||||
if not self.additionalInfo: return
|
||||
return self.additionalInfo(obj.appy(), date)
|
||||
|
||||
def getEventTypes(self, obj):
|
||||
'''Returns the (dynamic or static) event types as defined in
|
||||
self.eventTypes.'''
|
||||
|
@ -158,6 +171,13 @@ class Calendar(Type):
|
|||
res = days[day]
|
||||
return res
|
||||
|
||||
def getEventTypeAt(self, obj, date):
|
||||
'''Returns the event type of the first event defined at p_day, or None
|
||||
if unspecified.'''
|
||||
events = self.getEventsAt(obj, date, asDict=False)
|
||||
if not events: return
|
||||
return events[0].eventType
|
||||
|
||||
def hasEventsAt(self, obj, date, otherEvents):
|
||||
'''Returns True if, at p_date, an event is found of the same type as
|
||||
p_otherEvents.'''
|
||||
|
@ -202,11 +222,20 @@ class Calendar(Type):
|
|||
else:
|
||||
return DateTime() # Now
|
||||
|
||||
def createEvent(self, obj, date, handleEventSpan=True):
|
||||
'''Create a new event in the calendar, at some p_date (day). If
|
||||
p_handleEventSpan is True, we will use rq["eventSpan"] and also
|
||||
def createEvent(self, obj, date, eventType=None, eventSpan=None,
|
||||
handleEventSpan=True):
|
||||
'''Create a new event in the calendar, at some p_date (day).
|
||||
If p_eventType is given, it is used; else, rq['eventType'] is used.
|
||||
If p_handleEventSpan is True, we will use p_eventSpan (or
|
||||
rq["eventSpan"] if p_eventSpan is not given) and also
|
||||
create the same event for successive days.'''
|
||||
obj = obj.o # Ensure p_obj is not a wrapper.
|
||||
rq = obj.REQUEST
|
||||
# Get values from parameters
|
||||
if not eventType: eventType = rq['eventType']
|
||||
if handleEventSpan and not eventSpan:
|
||||
eventSpan = rq.get('eventSpan', None)
|
||||
# Split the p_date into separate parts
|
||||
year, month, day = date.year(), date.month(), date.day()
|
||||
# Check that the "preferences" dict exists or not.
|
||||
if not hasattr(obj.aq_base, self.name):
|
||||
|
@ -230,11 +259,11 @@ class Calendar(Type):
|
|||
daysDict[day] = events = PersistentList()
|
||||
# Create and store the event, excepted if an event already exists.
|
||||
if not events:
|
||||
event = Object(eventType=rq['eventType'])
|
||||
event = Object(eventType=eventType)
|
||||
events.append(event)
|
||||
# Span the event on the successive days if required
|
||||
if handleEventSpan and rq['eventSpan']:
|
||||
nbOfDays = min(int(rq['eventSpan']), self.maxEventLength)
|
||||
if handleEventSpan and eventSpan:
|
||||
nbOfDays = min(int(eventSpan), self.maxEventLength)
|
||||
for i in range(nbOfDays):
|
||||
date = date + 1
|
||||
self.createEvent(obj, date, handleEventSpan=False)
|
||||
|
@ -243,6 +272,7 @@ class Calendar(Type):
|
|||
'''Deletes an event. It actually deletes all events at rq['day'].
|
||||
If p_handleEventSpan is True, we will use rq["deleteNext"] to
|
||||
delete successive events, too.'''
|
||||
obj = obj.o # Ensure p_obj is not a wrapper.
|
||||
rq = obj.REQUEST
|
||||
if not self.getEventsAt(obj, date): return
|
||||
daysDict = getattr(obj, self.name)[date.year()][date.month()]
|
||||
|
|
|
@ -104,6 +104,9 @@
|
|||
tal:attributes="style python: 'color: %s;; font-style: italic' % event['color']"></div>
|
||||
</tal:e>
|
||||
</tal:others>
|
||||
<tal:comment replace="nothing">Additional info</tal:comment>
|
||||
<tal:info define="info python: contextObj.callField(fieldName,'getAdditionalInfoAt', contextObj, date)"
|
||||
condition="info" replace="structure info"/>
|
||||
</tal:day>
|
||||
</td>
|
||||
</tal:td>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import time
|
||||
from appy.shared.utils import Traceback
|
||||
from appy.shared.xml_parser import escapeXhtml
|
||||
|
||||
# Some POD-specific constants --------------------------------------------------
|
||||
XHTML_HEADINGS = ('h1', 'h2', 'h3', 'h4', 'h5', 'h6')
|
||||
|
@ -27,8 +28,6 @@ XHTML_PARAGRAPH_TAGS = XHTML_HEADINGS + XHTML_LISTS + ('p',)
|
|||
XHTML_PARAGRAPH_TAGS_NO_LISTS = XHTML_HEADINGS + ('p',)
|
||||
XHTML_INNER_TAGS = ('b', 'i', 'u', 'em')
|
||||
XHTML_UNSTYLABLE_TAGS = XHTML_LISTS + ('li', 'a')
|
||||
XML_SPECIAL_CHARS = {'<': '<', '>': '>', '&': '&', '"': '"',
|
||||
"'": '''}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class PodError(Exception):
|
||||
|
@ -83,17 +82,6 @@ class PodError(Exception):
|
|||
buffer.write('</%s>' % withinElement.OD.elem)
|
||||
dump = staticmethod(dump)
|
||||
|
||||
def convertToXhtml(s):
|
||||
'''Produces the XHTML-friendly version of p_s.'''
|
||||
res = ''
|
||||
for c in s:
|
||||
if XML_SPECIAL_CHARS.has_key(c):
|
||||
res += XML_SPECIAL_CHARS[c]
|
||||
elif c == '\n':
|
||||
res += '<br/>'
|
||||
elif c == '\r':
|
||||
pass
|
||||
else:
|
||||
res += c
|
||||
return res
|
||||
# XXX To remove, present for backward compatibility only.
|
||||
convertToXhtml = escapeXhtml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
import re
|
||||
|
||||
from xml.sax.saxutils import quoteattr
|
||||
from appy.pod import PodError, XML_SPECIAL_CHARS
|
||||
from appy.shared.xml_parser import xmlPrologue, escapeXml
|
||||
from appy.pod import PodError
|
||||
from appy.pod.elements import *
|
||||
from appy.pod.actions import IfAction, ElseAction, ForAction, VariableAction, \
|
||||
NullAction
|
||||
from appy.shared import xmlPrologue
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ParsingError(Exception): pass
|
||||
|
@ -157,11 +157,7 @@ class Buffer:
|
|||
|
||||
def dumpContent(self, content):
|
||||
'''Dumps string p_content into the buffer.'''
|
||||
for c in content:
|
||||
if XML_SPECIAL_CHARS.has_key(c):
|
||||
self.write(XML_SPECIAL_CHARS[c])
|
||||
else:
|
||||
self.write(c)
|
||||
self.write(escapeXml(content))
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class FileBuffer(Buffer):
|
||||
|
|
|
@ -10,8 +10,9 @@ class OdtTable:
|
|||
tns = 'table:'
|
||||
txns = 'text:'
|
||||
|
||||
def __init__(self, name, paraStyle, cellStyle, nbOfCols,
|
||||
paraHeaderStyle=None, cellHeaderStyle=None, html=False):
|
||||
def __init__(self, name, paraStyle='podTablePara', cellStyle='podTableCell',
|
||||
nbOfCols=1, paraHeaderStyle=None, cellHeaderStyle=None,
|
||||
html=False):
|
||||
# An ODT table must have a name. In the case of an HTML table, p_name
|
||||
# represents the CSS class for the whole table.
|
||||
self.name = name
|
||||
|
@ -24,7 +25,7 @@ class OdtTable:
|
|||
# The default style of every paragraph within a header cell
|
||||
self.paraHeaderStyle = paraHeaderStyle or paraStyle
|
||||
# The default style of every header cell
|
||||
self.cellHeaderStyle = cellHeaderStyle or cellStyle
|
||||
self.cellHeaderStyle = cellHeaderStyle or 'podTableHeaderCell'
|
||||
# The buffer where the resulting table will be rendered
|
||||
self.res = ''
|
||||
# Do we need to generate an HTML table instead of an ODT table ?
|
||||
|
|
|
@ -21,9 +21,9 @@ import os, os.path, sys, zipfile, re, shutil
|
|||
import appy.shared.test
|
||||
from appy.shared.test import TesterError
|
||||
from appy.shared.utils import FolderDeleter
|
||||
from appy.shared.xml_parser import escapeXml
|
||||
from appy.pod.odf_parser import OdfEnvironment, OdfParser
|
||||
from appy.pod.renderer import Renderer
|
||||
from appy.pod import XML_SPECIAL_CHARS
|
||||
|
||||
# TesterError-related constants ------------------------------------------------
|
||||
TEMPLATE_NOT_FOUND = 'Template file "%s" was not found.'
|
||||
|
@ -70,12 +70,7 @@ class AnnotationsRemover(OdfParser):
|
|||
self.res += '</%s>' % elem
|
||||
def characters(self, content):
|
||||
e = OdfParser.characters(self, content)
|
||||
if not self.ignore:
|
||||
for c in content:
|
||||
if XML_SPECIAL_CHARS.has_key(c):
|
||||
self.res += XML_SPECIAL_CHARS[c]
|
||||
else:
|
||||
self.res += c
|
||||
if not self.ignore: self.res += escapeXml(content)
|
||||
def getResult(self):
|
||||
return self.res
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
import xml.sax, time, random
|
||||
from appy.shared.xml_parser import XmlEnvironment, XmlParser
|
||||
from appy.shared.xml_parser import XmlEnvironment, XmlParser, escapeXml
|
||||
from appy.pod.odf_parser import OdfEnvironment
|
||||
from appy.pod.styles_manager import Style
|
||||
from appy.pod import *
|
||||
|
@ -235,6 +235,10 @@ class HtmlTable:
|
|||
# The following list stores, for every column, the size of the biggest
|
||||
# content of all its cells.
|
||||
self.columnContentSizes = []
|
||||
# The following list stores, for every column, its width, if specified.
|
||||
# If widths are found, self.columnContentSizes will not be used:
|
||||
# self.columnWidths will be used instead.
|
||||
self.columnWidths = []
|
||||
|
||||
def computeColumnStyles(self, renderer):
|
||||
'''Once the table has been completely parsed, self.columnContentSizes
|
||||
|
@ -242,22 +246,34 @@ class HtmlTable:
|
|||
of every column and create the corresponding style declarations, in
|
||||
p_renderer.dynamicStyles.'''
|
||||
total = 65000.0 # A number representing the total width of the table
|
||||
# Ensure first that self.columnContentSizes is correct
|
||||
if (len(self.columnContentSizes) != self.nbOfColumns) or \
|
||||
(None in self.columnContentSizes):
|
||||
# There was a problem while parsing the table. Set every column
|
||||
# with the same width.
|
||||
widths = [int(total/self.nbOfColumns)] * self.nbOfColumns
|
||||
# Use (a) self.columnWidths if complete, or
|
||||
# (b) self.columnContentSizes if complete, or
|
||||
# (c) a fixed width else.
|
||||
if self.columnWidths and (len(self.columnWidths) == self.nbOfColumns) \
|
||||
and (None not in self.columnWidths):
|
||||
# Use self.columnWidths
|
||||
toUse = self.columnWidths
|
||||
# Use self.columnContentSizes if complete
|
||||
elif (len(self.columnContentSizes) == self.nbOfColumns) and \
|
||||
(None not in self.columnContentSizes):
|
||||
# Use self.columnContentSizes
|
||||
toUse = self.columnContentSizes
|
||||
else:
|
||||
toUse = None
|
||||
if toUse:
|
||||
widths = []
|
||||
# Compute the sum of all column content sizes
|
||||
contentTotal = 0
|
||||
for size in self.columnContentSizes: contentTotal += size
|
||||
for size in toUse: contentTotal += size
|
||||
contentTotal = float(contentTotal)
|
||||
for size in self.columnContentSizes:
|
||||
for size in toUse:
|
||||
width = int((size/contentTotal) * total)
|
||||
widths.append(width)
|
||||
# Compute style declatation corresponding to every column.
|
||||
else:
|
||||
# There was a problem while parsing the table. Set every column
|
||||
# with the same width.
|
||||
widths = [int(total/self.nbOfColumns)] * self.nbOfColumns
|
||||
# Compute style declaration corresponding to every column.
|
||||
s = self.styleNs
|
||||
i = 0
|
||||
for width in widths:
|
||||
|
@ -321,14 +337,10 @@ class XhtmlEnvironment(XmlEnvironment):
|
|||
currentElem.addInnerParagraph(self)
|
||||
# Dump and reinitialize the current content
|
||||
content = self.currentContent.strip('\n\t')
|
||||
# We remove leading and trailing carriage returns, but not
|
||||
# whitespace because whitespace may be part of the text to dump.
|
||||
contentSize = len(content)
|
||||
for c in content:
|
||||
# We remove leading and trailing carriage returns, but not
|
||||
# whitespace because whitespace may be part of the text to dump.
|
||||
if XML_SPECIAL_CHARS.has_key(c):
|
||||
self.dumpString(XML_SPECIAL_CHARS[c])
|
||||
else:
|
||||
self.dumpString(c)
|
||||
self.dumpString(escapeXml(content))
|
||||
self.currentContent = u''
|
||||
# If we are within a table cell, update the total size of cell content.
|
||||
if self.currentTables and self.currentTables[-1].inCell:
|
||||
|
@ -444,6 +456,16 @@ class XhtmlEnvironment(XmlEnvironment):
|
|||
# If we are in the first row of a table, update columns count
|
||||
if not table.firstRowParsed:
|
||||
table.nbOfColumns += colspan
|
||||
if attrs.has_key('width') and (colspan == 1):
|
||||
# Get the width, keep figures only.
|
||||
width = ''
|
||||
for c in attrs['width']:
|
||||
if c.isdigit(): width += c
|
||||
width = int(width)
|
||||
# Ensure self.columnWidths is long enough
|
||||
while (len(table.columnWidths)-1) < table.cellIndex:
|
||||
table.columnWidths.append(None)
|
||||
table.columnWidths[table.cellIndex] = width
|
||||
return currentElem
|
||||
|
||||
def onElementEnd(self, elem):
|
||||
|
@ -525,7 +547,8 @@ class XhtmlParser(XmlParser):
|
|||
elif elem == 'a':
|
||||
e.dumpString('<%s %s:type="simple"' % (odfTag, e.linkNs))
|
||||
if attrs.has_key('href'):
|
||||
e.dumpString(' %s:href="%s"' % (e.linkNs, attrs['href']))
|
||||
e.dumpString(' %s:href="%s"' % (e.linkNs,
|
||||
escapeXml(attrs['href'])))
|
||||
e.dumpString('>')
|
||||
elif elem in XHTML_LISTS:
|
||||
prologue = ''
|
||||
|
|
|
@ -19,7 +19,6 @@ mimeTypesExts = {
|
|||
'image/pjpeg' : 'jpg',
|
||||
'image/gif' : 'gif'
|
||||
}
|
||||
xmlPrologue = '<?xml version="1.0" encoding="utf-8" ?>\n'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class UnmarshalledFile:
|
||||
|
|
|
@ -22,16 +22,19 @@ import xml.sax, difflib, types, cgi
|
|||
from xml.sax.handler import ContentHandler, ErrorHandler, feature_external_ges
|
||||
from xml.sax.xmlreader import InputSource
|
||||
from xml.sax import SAXParseException
|
||||
from appy.shared import UnicodeBuffer, xmlPrologue
|
||||
from appy.shared import UnicodeBuffer
|
||||
from appy.shared.errors import AppyError
|
||||
from appy.shared.utils import sequenceTypes
|
||||
from appy.shared.css import parseStyleAttribute
|
||||
|
||||
# Constants --------------------------------------------------------------------
|
||||
xmlPrologue = '<?xml version="1.0" encoding="utf-8" ?>\n'
|
||||
CONVERSION_ERROR = '"%s" value "%s" could not be converted by the XML ' \
|
||||
'unmarshaller.'
|
||||
CUSTOM_CONVERSION_ERROR = 'Custom converter for "%s" values produced an ' \
|
||||
'error while converting value "%s". %s'
|
||||
XML_SPECIAL_CHARS = {'<': '<', '>': '>', '&': '&', '"': '"',
|
||||
"'": '''}
|
||||
XML_ENTITIES = {'lt': '<', 'gt': '>', 'amp': '&', 'quot': "'", 'apos': "'"}
|
||||
HTML_ENTITIES = {
|
||||
'iexcl': '¡', 'cent': '¢', 'pound': '£', 'curren': '€', 'yen': '¥',
|
||||
|
@ -60,6 +63,38 @@ for k, v in htmlentitydefs.entitydefs.iteritems():
|
|||
if not HTML_ENTITIES.has_key(k) and not XML_ENTITIES.has_key(k):
|
||||
HTML_ENTITIES[k] = ''
|
||||
|
||||
def escapeXml(s):
|
||||
'''Returns p_s, whose XML special chars have been replaced with escaped XML
|
||||
entities.'''
|
||||
if isinstance(s, unicode):
|
||||
res = u''
|
||||
else:
|
||||
res = ''
|
||||
for c in s:
|
||||
if XML_SPECIAL_CHARS.has_key(c):
|
||||
res += XML_SPECIAL_CHARS[c]
|
||||
else:
|
||||
res += c
|
||||
return res
|
||||
|
||||
def escapeXhtml(s):
|
||||
'''Return p_s, whose XHTML special chars and carriage return chars have
|
||||
been replaced with corresponding XHTML entities.'''
|
||||
if isinstance(s, unicode):
|
||||
res = u''
|
||||
else:
|
||||
res = ''
|
||||
for c in s:
|
||||
if XML_SPECIAL_CHARS.has_key(c):
|
||||
res += XML_SPECIAL_CHARS[c]
|
||||
elif c == '\n':
|
||||
res += '<br/>'
|
||||
elif c == '\r':
|
||||
pass
|
||||
else:
|
||||
res += c
|
||||
return res
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class XmlElement:
|
||||
'''Represents an XML tag.'''
|
||||
|
|
Loading…
Reference in a new issue