--- a/appobject.py Tue Feb 17 22:49:54 2009 +0100
+++ b/appobject.py Tue Feb 17 23:00:28 2009 +0100
@@ -13,6 +13,7 @@
from logilab.common.decorators import classproperty
from logilab.common.deprecation import obsolete
+from logilab.mtconverter import html_escape
from rql.nodes import VariableRef, SubQuery
from rql.stmts import Union, Select
@@ -20,8 +21,7 @@
from cubicweb import Unauthorized
from cubicweb.vregistry import VObject
from cubicweb.selectors import yes
-from cubicweb.common.utils import UStringIO
-from cubicweb.common.uilib import html_escape, ustrftime
+from cubicweb.utils import UStringIO, ustrftime
from cubicweb.common.registerers import yes_registerer, priority_registerer
--- a/common/html4zope.py Tue Feb 17 22:49:54 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision: 1.2 $
-# Date: $Date: 2005-07-04 16:36:50 $
-# Copyright: This module has been placed in the public domain.
-
-"""
-Simple HyperText Markup Language document tree Writer.
-
-The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
-HTML version 1.0 Transitional DTD (*almost* strict). The output contains a
-minimum of formatting information. A cascading style sheet ("default.css" by
-default) is required for proper viewing with a modern graphical browser.
-
-http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
-"""
-
-__docformat__ = 'reStructuredText'
-
-from logilab.mtconverter import html_escape
-
-from docutils import nodes
-from docutils.writers.html4css1 import Writer as CSS1Writer
-from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
-import os
-
-default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
-
-class Writer(CSS1Writer):
- """css writer using our html translator"""
- def __init__(self, base_url):
- CSS1Writer.__init__(self)
- self.translator_class = URLBinder(base_url, HTMLTranslator)
-
- def apply_template(self):
- """overriding this is necessary with docutils >= 0.5"""
- return self.visitor.astext()
-
-class URLBinder:
- def __init__(self, url, klass):
- self.base_url = url
- self.translator_class = HTMLTranslator
-
- def __call__(self, document):
- translator = self.translator_class(document)
- translator.base_url = self.base_url
- return translator
-
-class HTMLTranslator(CSS1HTMLTranslator):
- """ReST tree to html translator"""
-
- def astext(self):
- """return the extracted html"""
- return ''.join(self.body)
-
- def visit_title(self, node):
- """Only 6 section levels are supported by HTML."""
- if isinstance(node.parent, nodes.topic):
- self.body.append(
- self.starttag(node, 'p', '', CLASS='topic-title'))
- if node.parent.hasattr('id'):
- self.body.append(
- self.starttag({}, 'a', '', name=node.parent['id']))
- self.context.append('</a></p>\n')
- else:
- self.context.append('</p>\n')
- elif self.section_level == 0:
- # document title
- self.head.append('<title>%s</title>\n'
- % self.encode(node.astext()))
- self.body.append(self.starttag(node, 'h%d' % default_level, '',
- CLASS='title'))
- self.context.append('</h%d>\n' % default_level)
- else:
- self.body.append(
- self.starttag(node, 'h%s' % (
- default_level+self.section_level-1), ''))
- atts = {}
- if node.hasattr('refid'):
- atts['class'] = 'toc-backref'
- atts['href'] = '%s#%s' % (self.base_url, node['refid'])
- self.body.append(self.starttag({}, 'a', '', **atts))
- self.context.append('</a></h%s>\n' % (
- default_level+self.section_level-1))
-
- def visit_subtitle(self, node):
- """format a subtitle"""
- if isinstance(node.parent, nodes.sidebar):
- self.body.append(self.starttag(node, 'p', '',
- CLASS='sidebar-subtitle'))
- self.context.append('</p>\n')
- else:
- self.body.append(
- self.starttag(node, 'h%s' % (default_level+1), '',
- CLASS='subtitle'))
- self.context.append('</h%s>\n' % (default_level+1))
-
- def visit_document(self, node):
- """syt: i don't want the enclosing <div class="document">"""
- def depart_document(self, node):
- """syt: i don't want the enclosing <div class="document">"""
-
- def visit_reference(self, node):
- """syt: i want absolute urls"""
- if node.has_key('refuri'):
- href = node['refuri']
- if ( self.settings.cloak_email_addresses
- and href.startswith('mailto:')):
- href = self.cloak_mailto(href)
- self.in_mailto = 1
- else:
- assert node.has_key('refid'), \
- 'References must have "refuri" or "refid" attribute.'
- href = '%s#%s' % (self.base_url, node['refid'])
- atts = {'href': href, 'class': 'reference'}
- if not isinstance(node.parent, nodes.TextElement):
- assert len(node) == 1 and isinstance(node[0], nodes.image)
- atts['class'] += ' image-reference'
- self.body.append(self.starttag(node, 'a', '', **atts))
-
- ## override error messages to avoid XHTML problems ########################
- def visit_problematic(self, node):
- pass
-
- def depart_problematic(self, node):
- pass
-
- def visit_system_message(self, node):
- backref_text = ''
- if len(node['backrefs']):
- backrefs = node['backrefs']
- if len(backrefs) == 1:
- backref_text = '; <em>backlink</em>'
- else:
- i = 1
- backlinks = []
- for backref in backrefs:
- backlinks.append(str(i))
- i += 1
- backref_text = ('; <em>backlinks: %s</em>'
- % ', '.join(backlinks))
- if node.hasattr('line'):
- line = ', line %s' % node['line']
- else:
- line = ''
- a_start = a_end = ''
- error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
- a_start, node['type'], node['level'], a_end,
- self.encode(node['source']), line, backref_text)
- self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
-
- def depart_system_message(self, node):
- pass
--- a/common/mttransforms.py Tue Feb 17 22:49:54 2009 +0100
+++ b/common/mttransforms.py Tue Feb 17 23:00:28 2009 +0100
@@ -31,16 +31,7 @@
output = 'text/html'
def _convert(self, trdata):
return html_publish(trdata.appobject, trdata.data)
-
-class ept_to_html(Transform):
- inputs = ('text/cubicweb-page-template',)
- output = 'text/html'
- output_encoding = 'utf-8'
- def _convert(self, trdata):
- from cubicweb.common.tal import compile_template
- value = trdata.encode(self.output_encoding)
- return trdata.appobject.tal_render(compile_template(value), {})
-
+
# Instantiate and configure the transformation engine
@@ -49,7 +40,23 @@
ENGINE = TransformEngine()
ENGINE.add_transform(rest_to_html())
ENGINE.add_transform(html_to_html())
-ENGINE.add_transform(ept_to_html())
+
+try:
+ from cubicweb.common.tal import compile_template
+except ImportError:
+ HAS_TAL = False
+else:
+ HAS_TAL = True
+
+ class ept_to_html(Transform):
+ inputs = ('text/cubicweb-page-template',)
+ output = 'text/html'
+ output_encoding = 'utf-8'
+ def _convert(self, trdata):
+ value = trdata.encode(self.output_encoding)
+ return trdata.appobject.tal_render(compile_template(value), {})
+
+ ENGINE.add_transform(ept_to_html())
if register_pil_transforms(ENGINE, verb=False):
HAS_PIL_TRANSFORMS = True
--- a/common/rest.py Tue Feb 17 22:49:54 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-"""rest publishing functions
-
-contains some functions and setup of docutils for cubicweb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-from itertools import chain
-from logging import getLogger
-from os.path import join
-
-from docutils import statemachine, nodes, utils, io
-from docutils.core import publish_string
-from docutils.parsers.rst import Parser, states, directives
-from docutils.parsers.rst.roles import register_canonical_role, set_classes
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.common.html4zope import Writer
-
-# We provide our own parser as an attempt to get rid of
-# state machine reinstanciation
-
-import re
-# compile states.Body patterns
-for k, v in states.Body.patterns.items():
- if isinstance(v, str):
- states.Body.patterns[k] = re.compile(v)
-
-# register ReStructured Text mimetype / extensions
-import mimetypes
-mimetypes.add_type('text/rest', '.rest')
-mimetypes.add_type('text/rest', '.rst')
-
-
-LOGGER = getLogger('cubicweb.rest')
-
-def eid_reference_role(role, rawtext, text, lineno, inliner,
- options={}, content=[]):
- try:
- try:
- eid_num, rest = text.split(u':', 1)
- except:
- eid_num, rest = text, '#'+text
- eid_num = int(eid_num)
- if eid_num < 0:
- raise ValueError
- except ValueError:
- msg = inliner.reporter.error(
- 'EID number must be a positive number; "%s" is invalid.'
- % text, line=lineno)
- prb = inliner.problematic(rawtext, rawtext, msg)
- return [prb], [msg]
- # Base URL mainly used by inliner.pep_reference; so this is correct:
- context = inliner.document.settings.context
- refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
- ref = refedentity.absolute_url()
- set_classes(options)
- return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
- **options)], []
-
-register_canonical_role('eid', eid_reference_role)
-
-
-def card_reference_role(role, rawtext, text, lineno, inliner,
- options={}, content=[]):
- text = text.strip()
- try:
- wikiid, rest = text.split(u':', 1)
- except:
- wikiid, rest = text, text
- context = inliner.document.settings.context
- cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
- {'id': wikiid})
- if cardrset:
- ref = cardrset.get_entity(0, 0).absolute_url()
- else:
- schema = context.schema
- if schema.eschema('Card').has_perm(context.req, 'add'):
- ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
- else:
- ref = '#'
- set_classes(options)
- return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
- **options)], []
-
-register_canonical_role('card', card_reference_role)
-
-
-def winclude_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- """Include a reST file as part of the content of this reST file.
-
- same as standard include directive but using config.locate_doc_resource to
- get actual file to include.
-
- Most part of this implementation is copied from `include` directive defined
- in `docutils.parsers.rst.directives.misc`
- """
- context = state.document.settings.context
- source = state_machine.input_lines.source(
- lineno - state_machine.input_offset - 1)
- #source_dir = os.path.dirname(os.path.abspath(source))
- fid = arguments[0]
- for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
- context.config.available_languages()):
- rid = '%s_%s.rst' % (fid, lang)
- resourcedir = context.config.locate_doc_file(rid)
- if resourcedir:
- break
- else:
- severe = state_machine.reporter.severe(
- 'Problems with "%s" directive path:\nno resource matching %s.'
- % (name, fid),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- path = join(resourcedir, rid)
- encoding = options.get('encoding', state.document.settings.input_encoding)
- try:
- state.document.settings.record_dependencies.add(path)
- include_file = io.FileInput(
- source_path=path, encoding=encoding,
- error_handler=state.document.settings.input_encoding_error_handler,
- handle_io_errors=None)
- except IOError, error:
- severe = state_machine.reporter.severe(
- 'Problems with "%s" directive path:\n%s: %s.'
- % (name, error.__class__.__name__, error),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- try:
- include_text = include_file.read()
- except UnicodeError, error:
- severe = state_machine.reporter.severe(
- 'Problem with "%s" directive:\n%s: %s'
- % (name, error.__class__.__name__, error),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- if options.has_key('literal'):
- literal_block = nodes.literal_block(include_text, include_text,
- source=path)
- literal_block.line = 1
- return literal_block
- else:
- include_lines = statemachine.string2lines(include_text,
- convert_whitespace=1)
- state_machine.insert_input(include_lines, path)
- return []
-
-winclude_directive.arguments = (1, 0, 1)
-winclude_directive.options = {'literal': directives.flag,
- 'encoding': directives.encoding}
-directives.register_directive('winclude', winclude_directive)
-
-class CubicWebReSTParser(Parser):
- """The (customized) reStructuredText parser."""
-
- def __init__(self):
- self.initial_state = 'Body'
- self.state_classes = states.state_classes
- self.inliner = states.Inliner()
- self.statemachine = states.RSTStateMachine(
- state_classes=self.state_classes,
- initial_state=self.initial_state,
- debug=0)
-
- def parse(self, inputstring, document):
- """Parse `inputstring` and populate `document`, a document tree."""
- self.setup_parse(inputstring, document)
- inputlines = statemachine.string2lines(inputstring,
- convert_whitespace=1)
- self.statemachine.run(inputlines, document, inliner=self.inliner)
- self.finish_parse()
-
-
-_REST_PARSER = CubicWebReSTParser()
-
-def rest_publish(context, data):
- """publish a string formatted as ReStructured Text to HTML
-
- :type context: a cubicweb application object
-
- :type data: str
- :param data: some ReST text
-
- :rtype: unicode
- :return:
- the data formatted as HTML or the original data if an error occured
- """
- req = context.req
- if isinstance(data, unicode):
- encoding = 'unicode'
- else:
- encoding = req.encoding
- settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
- 'warning_stream': StringIO(), 'context': context,
- # dunno what's the max, severe is 4, and we never want a crash
- # (though try/except may be a better option...)
- 'halt_level': 10,
- }
- if context:
- if hasattr(req, 'url'):
- base_url = req.url()
- elif hasattr(context, 'absolute_url'):
- base_url = context.absolute_url()
- else:
- base_url = req.base_url()
- else:
- base_url = None
- try:
- return publish_string(writer=Writer(base_url=base_url),
- parser=_REST_PARSER, source=data,
- settings_overrides=settings)
- except Exception:
- LOGGER.exception('error while publishing ReST text')
- if not isinstance(data, unicode):
- data = unicode(data, encoding, 'replace')
- return html_escape(req._('error while publishing ReST text')
- + '\n\n' + data)
--- a/common/tal.py Tue Feb 17 22:49:54 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-"""provides simpleTAL extensions for CubicWeb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-__docformat__ = "restructuredtext en"
-
-import sys
-import re
-from os.path import exists, isdir, join
-from logging import getLogger
-from StringIO import StringIO
-
-from simpletal import simpleTAL, simpleTALES
-
-from logilab.common.decorators import cached
-
-LOGGER = getLogger('cubicweb.tal')
-
-
-class LoggerAdapter(object):
- def __init__(self, tal_logger):
- self.tal_logger = tal_logger
-
- def debug(self, msg):
- LOGGER.debug(msg)
-
- def warn(self, msg):
- LOGGER.warning(msg)
-
- def __getattr__(self, attrname):
- return getattr(self.tal_logger, attrname)
-
-
-class CubicWebContext(simpleTALES.Context):
- """add facilities to access entity / resultset"""
-
- def __init__(self, options=None, allowPythonPath=1):
- simpleTALES.Context.__init__(self, options, allowPythonPath)
- self.log = LoggerAdapter(self.log)
-
- def update(self, context):
- for varname, value in context.items():
- self.addGlobal(varname, value)
-
- def addRepeat(self, name, var, initialValue):
- simpleTALES.Context.addRepeat(self, name, var, initialValue)
-
-# XXX FIXME need to find a clean to define OPCODE values for extensions
-I18N_CONTENT = 18
-I18N_REPLACE = 19
-RQL_EXECUTE = 20
-# simpleTAL uses the OPCODE values to define priority over commands.
-# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
-# we can't use the same OPCODE for two different commands without changing
-# the simpleTAL implementation. Another solution would be to totally override
-# the REPEAT implementation with the ITER one, but some specific operations
-# (involving len() for instance) are not implemented for ITER, so we prefer
-# to keep both implementations for now, and to fool simpleTAL by using a float
-# number between 3 and 4
-TAL_ITER = 3.1
-
-
-# FIX simpleTAL HTML 4.01 stupidity
-# (simpleTAL never closes tags like INPUT, IMG, HR ...)
-simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
-
-class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
- """extends default compiler by adding i18n:content commands"""
-
- def __init__(self):
- simpleTAL.HTMLTemplateCompiler.__init__(self)
- self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
- self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
- self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
- self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
-
- def setTALPrefix(self, prefix):
- simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
- self.tal_attribute_map['i18n:content'] = I18N_CONTENT
- self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
- self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
- self.tal_attribute_map['tal:iter'] = TAL_ITER
-
- def compile_cmd_i18n_content(self, argument):
- # XXX tal:content structure=, text= should we support this ?
- structure_flag = 0
- return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
-
- def compile_cmd_i18n_replace(self, argument):
- # XXX tal:content structure=, text= should we support this ?
- structure_flag = 0
- return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
-
- def compile_cmd_rql(self, argument):
- return (RQL_EXECUTE, (argument, self.endTagSymbol))
-
- def compile_cmd_tal_iter(self, argument):
- original_id, (var_name, expression, end_tag_symbol) = \
- simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
- return (TAL_ITER, (var_name, expression, self.endTagSymbol))
-
- def getTemplate(self):
- return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
-
- def compileCmdAttributes (self, argument):
- """XXX modified to support single attribute
- definition ending by a ';'
-
- backport this to simpleTAL
- """
- # Compile tal:attributes into attribute command
- # Argument: [(attributeName, expression)]
-
- # Break up the list of attribute settings first
- commandArgs = []
- # We only want to match semi-colons that are not escaped
- argumentSplitter = re.compile(r'(?<!;);(?!;)')
- for attributeStmt in argumentSplitter.split(argument):
- if not attributeStmt.strip():
- continue
- # remove any leading space and un-escape any semi-colons
- attributeStmt = attributeStmt.lstrip().replace(';;', ';')
- # Break each attributeStmt into name and expression
- stmtBits = attributeStmt.split(' ')
- if (len (stmtBits) < 2):
- # Error, badly formed attributes command
- msg = "Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument
- self.log.error(msg)
- raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
- attName = stmtBits[0]
- attExpr = " ".join(stmtBits[1:])
- commandArgs.append((attName, attExpr))
- return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
-
-
-class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
- """provides implementation for interpreting cubicweb extensions"""
- def __init__(self):
- simpleTAL.TemplateInterpreter.__init__(self)
- self.commandHandler[I18N_CONTENT] = self.cmd_i18n
- self.commandHandler[TAL_ITER] = self.cmdRepeat
- # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
-
- def cmd_i18n(self, command, args):
- """i18n:content and i18n:replace implementation"""
- string, replace_flag, structure_flag, end_symbol = args
- if replace_flag:
- self.outputTag = 0
- result = self.context.globals['_'](string)
- self.tagContent = (0, result)
- self.movePCForward = self.symbolTable[end_symbol]
- self.programCounter += 1
-
-
-class CubicWebTemplate(simpleTAL.HTMLTemplate):
- """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
- """
- def expand(self, context, outputFile):
- interpreter = CubicWebTemplateInterpreter()
- interpreter.initialise(context, outputFile)
- simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
- interpreter=interpreter)
-
- def expandInline(self, context, outputFile, interpreter):
- """ Internally used when expanding a template that is part of a context."""
- try:
- interpreter.execute(self)
- except UnicodeError, unierror:
- LOGGER.exception(str(unierror))
- raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
-
-
-def compile_template(template):
- """compiles a TAL template string
- :type template: unicode
- :param template: a TAL-compliant template string
- """
- string_buffer = StringIO(template)
- compiler = CubicWebTemplateCompiler()
- compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
- return compiler.getTemplate()
-
-
-def compile_template_file(filepath):
- """compiles a TAL template file
- :type filepath: str
- :param template: path of the file to compile
- """
- fp = file(filepath)
- file_content = unicode(fp.read()) # template file should be pure ASCII
- fp.close()
- return compile_template(file_content)
-
-
-def evaluatePython (self, expr):
- if not self.allowPythonPath:
- return self.false
- globals = {}
- for name, value in self.globals.items():
- if isinstance (value, simpleTALES.ContextVariable):
- value = value.rawValue()
- globals[name] = value
- globals['path'] = self.pythonPathFuncs.path
- globals['string'] = self.pythonPathFuncs.string
- globals['exists'] = self.pythonPathFuncs.exists
- globals['nocall'] = self.pythonPathFuncs.nocall
- globals['test'] = self.pythonPathFuncs.test
- locals = {}
- for name, value in self.locals.items():
- if (isinstance (value, simpleTALES.ContextVariable)):
- value = value.rawValue()
- locals[name] = value
- # XXX precompile expr will avoid late syntax error
- try:
- result = eval(expr, globals, locals)
- except Exception, ex:
- ex = ex.__class__('in %r: %s' % (expr, ex))
- raise ex, None, sys.exc_info()[-1]
- if (isinstance (result, simpleTALES.ContextVariable)):
- return result.value()
- return result
-
-simpleTALES.Context.evaluatePython = evaluatePython
-
-
-class talbased(object):
- def __init__(self, filename, write=True):
-## if not osp.isfile(filepath):
-## # print "[tal.py] just for tests..."
-## # get parent frame
-## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
-## filepath = osp.join(directory, filepath)
- self.filename = filename
- self.write = write
-
- def __call__(self, viewfunc):
- def wrapped(instance, *args, **kwargs):
- variables = viewfunc(instance, *args, **kwargs)
- html = instance.tal_render(self._compiled_template(instance), variables)
- if self.write:
- instance.w(html)
- else:
- return html
- return wrapped
-
- def _compiled_template(self, instance):
- for fileordirectory in instance.config.vregistry_path():
- filepath = join(fileordirectory, self.filename)
- if isdir(fileordirectory) and exists(filepath):
- return compile_template_file(filepath)
- raise Exception('no such template %s' % self.filename)
- _compiled_template = cached(_compiled_template, 0)
-
--- a/common/uilib.py Tue Feb 17 22:49:54 2009 +0100
+++ b/common/uilib.py Tue Feb 17 23:00:28 2009 +0100
@@ -11,29 +11,18 @@
import csv
import decimal
-import locale
import re
from urllib import quote as urlquote
from cStringIO import StringIO
from copy import deepcopy
-import simplejson
from mx.DateTime import DateTimeType, DateTimeDeltaType
from logilab.common.textutils import unormalize
from logilab.mtconverter import html_escape, html_unescape
-def ustrftime(date, fmt='%Y-%m-%d'):
- """like strftime, but returns a unicode string instead of an encoded
- string which may be problematic with localized date.
-
- encoding is guessed by locale.getpreferredencoding()
- """
- # date format may depend on the locale
- encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8'
- return unicode(date.strftime(fmt), encoding)
-
+from cubicweb.utils import ustrftime
def rql_for_eid(eid):
"""return the rql query necessary to fetch entity with the given eid. This
@@ -258,6 +247,7 @@
elif vid:
params.append(repr(vid))
if extraparams:
+ import simplejson
params.append(simplejson.dumps(extraparams))
if swap:
params.append('true')
--- a/common/utils.py Tue Feb 17 22:49:54 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +0,0 @@
-"""Some utilities for CubicWeb server/clients.
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from md5 import md5
-from time import time
-from random import randint, seed
-
-# initialize random seed from current time
-seed()
-
-def make_uid(key):
- """forge a unique identifier"""
- msg = str(key) + "%.10f"%time() + str(randint(0, 1000000))
- return md5(msg).hexdigest()
-
-def working_hours(mxdate):
- """
- Predicate returning True is the date's hour is in working hours (8h->20h)
- """
- if mxdate.hour > 7 and mxdate.hour < 21:
- return True
- return False
-
-def date_range(begin, end, incr=1, include=None):
- """yields each date between begin and end
- :param begin: the start date
- :param end: the end date
- :param incr: the step to use to iterate over dates. Default is
- one day.
- :param include: None (means no exclusion) or a function taking a
- date as parameter, and returning True if the date
- should be included.
- """
- date = begin
- while date <= end:
- if include is None or include(date):
- yield date
- date += incr
-
-
-def dump_class(cls, clsname):
- """create copy of a class by creating an empty class inheriting
- from the given cls.
-
- Those class will be used as place holder for attribute and relation
- description
- """
- # type doesn't accept unicode name
- # return type.__new__(type, str(clsname), (cls,), {})
- # __autogenerated__ attribute is just a marker
- return type(str(clsname), (cls,), {'__autogenerated__': True})
-
-
-def merge_dicts(dict1, dict2):
- """update a copy of `dict1` with `dict2`"""
- dict1 = dict(dict1)
- dict1.update(dict2)
- return dict1
-
-
-class SizeConstrainedList(list):
- """simple list that makes sure the list does not get bigger
- than a given size.
-
- when the list is full and a new element is added, the first
- element of the list is removed before appending the new one
-
- >>> l = SizeConstrainedList(2)
- >>> l.append(1)
- >>> l.append(2)
- >>> l
- [1, 2]
- >>> l.append(3)
- [2, 3]
- """
- def __init__(self, maxsize):
- self.maxsize = maxsize
-
- def append(self, element):
- if len(self) == self.maxsize:
- del self[0]
- super(SizeConstrainedList, self).append(element)
-
- def extend(self, sequence):
- super(SizeConstrainedList, self).extend(sequence)
- keepafter = len(self) - self.maxsize
- if keepafter > 0:
- del self[:keepafter]
-
- __iadd__ = extend
-
-
-class UStringIO(list):
- """a file wrapper which automatically encode unicode string to an encoding
- specifed in the constructor
- """
-
- def __nonzero__(self):
- return True
-
- def write(self, value):
- assert isinstance(value, unicode), u"unicode required not %s : %s"\
- % (type(value).__name__, repr(value))
- self.append(value)
-
- def getvalue(self):
- return u''.join(self)
-
- def __repr__(self):
- return '<%s at %#x>' % (self.__class__.__name__, id(self))
-
-
-class HTMLHead(UStringIO):
- """wraps HTML header's stream
-
- Request objects use a HTMLHead instance to ease adding of
- javascripts and stylesheets
- """
- js_unload_code = u'jQuery(window).unload(unloadPageData);'
-
- def __init__(self):
- super(HTMLHead, self).__init__()
- self.jsvars = []
- self.jsfiles = []
- self.cssfiles = []
- self.ie_cssfiles = []
- self.post_inlined_scripts = []
- self.pagedata_unload = False
-
-
- def add_raw(self, rawheader):
- self.write(rawheader)
-
- def define_var(self, var, value):
- self.jsvars.append( (var, value) )
-
- def add_post_inline_script(self, content):
- self.post_inlined_scripts.append(content)
-
- def add_onload(self, jscode):
- self.add_post_inline_script(u"""jQuery(document).ready(function () {
- %s
- });""" % jscode)
-
-
- def add_js(self, jsfile):
- """adds `jsfile` to the list of javascripts used in the webpage
-
- This function checks if the file has already been added
- :param jsfile: the script's URL
- """
- if jsfile not in self.jsfiles:
- self.jsfiles.append(jsfile)
-
- def add_css(self, cssfile, media):
- """adds `cssfile` to the list of javascripts used in the webpage
-
- This function checks if the file has already been added
- :param cssfile: the stylesheet's URL
- """
- if (cssfile, media) not in self.cssfiles:
- self.cssfiles.append( (cssfile, media) )
-
- def add_ie_css(self, cssfile, media='all'):
- """registers some IE specific CSS"""
- if (cssfile, media) not in self.ie_cssfiles:
- self.ie_cssfiles.append( (cssfile, media) )
-
- def add_unload_pagedata(self):
- """registers onunload callback to clean page data on server"""
- if not self.pagedata_unload:
- self.post_inlined_scripts.append(self.js_unload_code)
- self.pagedata_unload = True
-
- def getvalue(self, skiphead=False):
- """reimplement getvalue to provide a consistent (and somewhat browser
- optimzed cf. http://stevesouders.com/cuzillion) order in external
- resources declaration
- """
- w = self.write
- # 1/ variable declaration if any
- if self.jsvars:
- from simplejson import dumps
- w(u'<script type="text/javascript">\n')
- for var, value in self.jsvars:
- w(u'%s = %s;\n' % (var, dumps(value)))
- w(u'</script>\n')
- # 2/ css files
- for cssfile, media in self.cssfiles:
- w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
- (media, cssfile))
- # 3/ ie css if necessary
- if self.ie_cssfiles:
- w(u'<!--[if lt IE 8]>\n')
- for cssfile, media in self.ie_cssfiles:
- w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
- (media, cssfile))
- w(u'<![endif]--> \n')
- # 4/ js files
- for jsfile in self.jsfiles:
- w(u'<script type="text/javascript" src="%s"></script>\n' % jsfile)
- # 5/ post inlined scripts (i.e. scripts depending on other JS files)
- if self.post_inlined_scripts:
- w(u'<script type="text/javascript">\n')
- w(u'\n\n'.join(self.post_inlined_scripts))
- w(u'\n</script>\n')
- header = super(HTMLHead, self).getvalue()
- if skiphead:
- return header
- return u'<head>\n%s</head>\n' % header
-
-
-class HTMLStream(object):
- """represents a HTML page.
-
- This is used my main templates so that HTML headers can be added
- at any time during the page generation.
-
- HTMLStream uses the (U)StringIO interface to be compliant with
- existing code.
- """
-
- def __init__(self, req):
- # stream for <head>
- self.head = req.html_headers
- # main stream
- self.body = UStringIO()
- self.doctype = u''
- # xmldecl and html opening tag
- self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
- self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
- 'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
- 'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
-
-
- def write(self, data):
- """StringIO interface: this method will be assigned to self.w
- """
- self.body.write(data)
-
- def getvalue(self):
- """writes HTML headers, closes </head> tag and writes HTML body"""
- return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
- self.htmltag,
- self.head.getvalue(),
- self.body.getvalue())
-
-
-class AcceptMixIn(object):
- """Mixin class for vobjects defining the 'accepts' attribute describing
- a set of supported entity type (Any by default).
- """
- # XXX deprecated, no more necessary
-
-
-from logilab.common.deprecation import moved, class_moved
-rql_for_eid = moved('cubicweb.common.uilib', 'rql_for_eid')
-ajax_replace_url = moved('cubicweb.common.uilib', 'ajax_replace_url')
-
-import cubicweb
-Binary = class_moved(cubicweb.Binary)
--- a/debian/control Tue Feb 17 22:49:54 2009 +0100
+++ b/debian/control Tue Feb 17 23:00:28 2009 +0100
@@ -73,8 +73,8 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, cubicweb-core (= ${source:Version}), python-logilab-mtconverter (>= 0.4.0), python-simpletal (>= 4.0), graphviz, gettext, python-lxml
-Recommends: python-psyco
+Depends: ${python:Depends}, cubicweb-core (= ${source:Version}), python-logilab-mtconverter (>= 0.4.0), graphviz, gettext, python-lxml
+Recommends: python-psyco, python-simpletal (>= 4.0)
Description: common library for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
--- a/entity.py Tue Feb 17 22:49:54 2009 +0100
+++ b/entity.py Tue Feb 17 23:00:28 2009 +0100
@@ -9,19 +9,24 @@
from logilab.common import interface
from logilab.common.compat import all
from logilab.common.decorators import cached
-from logilab.mtconverter import TransformData, TransformError
+from logilab.mtconverter import TransformData, TransformError, html_escape
from rql.utils import rqlvar_maker
from cubicweb import Unauthorized
from cubicweb.rset import ResultSet
from cubicweb.selectors import yes
-from cubicweb.common.appobject import AppRsetObject
+from cubicweb.appobject import AppRsetObject
+from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
from cubicweb.common.registerers import id_registerer
-from cubicweb.common.uilib import printable_value, html_escape, soup2xhtml
-from cubicweb.common.mixins import MI_REL_TRIGGERS
-from cubicweb.common.mttransforms import ENGINE
-from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
+
+try:
+ from cubicweb.common.uilib import printable_value, soup2xhtml
+ from cubicweb.common.mixins import MI_REL_TRIGGERS
+ from cubicweb.common.mttransforms import ENGINE
+except ImportError:
+ # missing -common
+ MI_REL_TRIGGERS = {}
_marker = object()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/html4zope.py Tue Feb 17 23:00:28 2009 +0100
@@ -0,0 +1,153 @@
+# Author: David Goodger
+# Contact: goodger@users.sourceforge.net
+# Revision: $Revision: 1.2 $
+# Date: $Date: 2005-07-04 16:36:50 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple HyperText Markup Language document tree Writer.
+
+The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
+HTML version 1.0 Transitional DTD (*almost* strict). The output contains a
+minimum of formatting information. A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical browser.
+
+http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
+"""
+
+__docformat__ = 'reStructuredText'
+
+from logilab.mtconverter import html_escape
+
+from docutils import nodes
+from docutils.writers.html4css1 import Writer as CSS1Writer
+from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
+import os
+
+default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
+
+class Writer(CSS1Writer):
+ """css writer using our html translator"""
+ def __init__(self, base_url):
+ CSS1Writer.__init__(self)
+ self.translator_class = URLBinder(base_url, HTMLTranslator)
+
+ def apply_template(self):
+ """overriding this is necessary with docutils >= 0.5"""
+ return self.visitor.astext()
+
+class URLBinder:
+ def __init__(self, url, klass):
+ self.base_url = url
+ self.translator_class = HTMLTranslator
+
+ def __call__(self, document):
+ translator = self.translator_class(document)
+ translator.base_url = self.base_url
+ return translator
+
+class HTMLTranslator(CSS1HTMLTranslator):
+ """ReST tree to html translator"""
+
+ def astext(self):
+ """return the extracted html"""
+ return ''.join(self.body)
+
+ def visit_title(self, node):
+ """Only 6 section levels are supported by HTML."""
+ if isinstance(node.parent, nodes.topic):
+ self.body.append(
+ self.starttag(node, 'p', '', CLASS='topic-title'))
+ if node.parent.hasattr('id'):
+ self.body.append(
+ self.starttag({}, 'a', '', name=node.parent['id']))
+ self.context.append('</a></p>\n')
+ else:
+ self.context.append('</p>\n')
+ elif self.section_level == 0:
+ # document title
+ self.head.append('<title>%s</title>\n'
+ % self.encode(node.astext()))
+ self.body.append(self.starttag(node, 'h%d' % default_level, '',
+ CLASS='title'))
+ self.context.append('</h%d>\n' % default_level)
+ else:
+ self.body.append(
+ self.starttag(node, 'h%s' % (
+ default_level+self.section_level-1), ''))
+ atts = {}
+ if node.hasattr('refid'):
+ atts['class'] = 'toc-backref'
+ atts['href'] = '%s#%s' % (self.base_url, node['refid'])
+ self.body.append(self.starttag({}, 'a', '', **atts))
+ self.context.append('</a></h%s>\n' % (
+ default_level+self.section_level-1))
+
+ def visit_subtitle(self, node):
+ """format a subtitle"""
+ if isinstance(node.parent, nodes.sidebar):
+ self.body.append(self.starttag(node, 'p', '',
+ CLASS='sidebar-subtitle'))
+ self.context.append('</p>\n')
+ else:
+ self.body.append(
+ self.starttag(node, 'h%s' % (default_level+1), '',
+ CLASS='subtitle'))
+ self.context.append('</h%s>\n' % (default_level+1))
+
+ def visit_document(self, node):
+ """syt: i don't want the enclosing <div class="document">"""
+ def depart_document(self, node):
+ """syt: i don't want the enclosing <div class="document">"""
+
+ def visit_reference(self, node):
+ """syt: i want absolute urls"""
+ if node.has_key('refuri'):
+ href = node['refuri']
+ if ( self.settings.cloak_email_addresses
+ and href.startswith('mailto:')):
+ href = self.cloak_mailto(href)
+ self.in_mailto = 1
+ else:
+ assert node.has_key('refid'), \
+ 'References must have "refuri" or "refid" attribute.'
+ href = '%s#%s' % (self.base_url, node['refid'])
+ atts = {'href': href, 'class': 'reference'}
+ if not isinstance(node.parent, nodes.TextElement):
+ assert len(node) == 1 and isinstance(node[0], nodes.image)
+ atts['class'] += ' image-reference'
+ self.body.append(self.starttag(node, 'a', '', **atts))
+
+ ## override error messages to avoid XHTML problems ########################
+ def visit_problematic(self, node):
+ pass
+
+ def depart_problematic(self, node):
+ pass
+
+ def visit_system_message(self, node):
+ backref_text = ''
+ if len(node['backrefs']):
+ backrefs = node['backrefs']
+ if len(backrefs) == 1:
+ backref_text = '; <em>backlink</em>'
+ else:
+ i = 1
+ backlinks = []
+ for backref in backrefs:
+ backlinks.append(str(i))
+ i += 1
+ backref_text = ('; <em>backlinks: %s</em>'
+ % ', '.join(backlinks))
+ if node.hasattr('line'):
+ line = ', line %s' % node['line']
+ else:
+ line = ''
+ a_start = a_end = ''
+ error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
+ a_start, node['type'], node['level'], a_end,
+ self.encode(node['source']), line, backref_text)
+ self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+
+ def depart_system_message(self, node):
+ pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/rest.py Tue Feb 17 23:00:28 2009 +0100
@@ -0,0 +1,223 @@
+"""rest publishing functions
+
+contains some functions and setup of docutils for cubicweb
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+from itertools import chain
+from logging import getLogger
+from os.path import join
+
+from docutils import statemachine, nodes, utils, io
+from docutils.core import publish_string
+from docutils.parsers.rst import Parser, states, directives
+from docutils.parsers.rst.roles import register_canonical_role, set_classes
+
+from logilab.mtconverter import html_escape
+
+from cubicweb.ext.html4zope import Writer
+
+# We provide our own parser as an attempt to get rid of
+# state machine reinstanciation
+
+import re
+# compile states.Body patterns
+for k, v in states.Body.patterns.items():
+ if isinstance(v, str):
+ states.Body.patterns[k] = re.compile(v)
+
+# register ReStructured Text mimetype / extensions
+import mimetypes
+mimetypes.add_type('text/rest', '.rest')
+mimetypes.add_type('text/rest', '.rst')
+
+
+LOGGER = getLogger('cubicweb.rest')
+
+def eid_reference_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ try:
+ try:
+ eid_num, rest = text.split(u':', 1)
+ except:
+ eid_num, rest = text, '#'+text
+ eid_num = int(eid_num)
+ if eid_num < 0:
+ raise ValueError
+ except ValueError:
+ msg = inliner.reporter.error(
+ 'EID number must be a positive number; "%s" is invalid.'
+ % text, line=lineno)
+ prb = inliner.problematic(rawtext, rawtext, msg)
+ return [prb], [msg]
+ # Base URL mainly used by inliner.pep_reference; so this is correct:
+ context = inliner.document.settings.context
+ refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
+ ref = refedentity.absolute_url()
+ set_classes(options)
+ return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+ **options)], []
+
+register_canonical_role('eid', eid_reference_role)
+
+
+def card_reference_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ text = text.strip()
+ try:
+ wikiid, rest = text.split(u':', 1)
+ except:
+ wikiid, rest = text, text
+ context = inliner.document.settings.context
+ cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
+ {'id': wikiid})
+ if cardrset:
+ ref = cardrset.get_entity(0, 0).absolute_url()
+ else:
+ schema = context.schema
+ if schema.eschema('Card').has_perm(context.req, 'add'):
+ ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
+ else:
+ ref = '#'
+ set_classes(options)
+ return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+ **options)], []
+
+register_canonical_role('card', card_reference_role)
+
+
+def winclude_directive(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ """Include a reST file as part of the content of this reST file.
+
+ same as standard include directive but using config.locate_doc_resource to
+ get actual file to include.
+
+ Most part of this implementation is copied from `include` directive defined
+ in `docutils.parsers.rst.directives.misc`
+ """
+ context = state.document.settings.context
+ source = state_machine.input_lines.source(
+ lineno - state_machine.input_offset - 1)
+ #source_dir = os.path.dirname(os.path.abspath(source))
+ fid = arguments[0]
+ for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
+ context.config.available_languages()):
+ rid = '%s_%s.rst' % (fid, lang)
+ resourcedir = context.config.locate_doc_file(rid)
+ if resourcedir:
+ break
+ else:
+ severe = state_machine.reporter.severe(
+ 'Problems with "%s" directive path:\nno resource matching %s.'
+ % (name, fid),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ path = join(resourcedir, rid)
+ encoding = options.get('encoding', state.document.settings.input_encoding)
+ try:
+ state.document.settings.record_dependencies.add(path)
+ include_file = io.FileInput(
+ source_path=path, encoding=encoding,
+ error_handler=state.document.settings.input_encoding_error_handler,
+ handle_io_errors=None)
+ except IOError, error:
+ severe = state_machine.reporter.severe(
+ 'Problems with "%s" directive path:\n%s: %s.'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ try:
+ include_text = include_file.read()
+ except UnicodeError, error:
+ severe = state_machine.reporter.severe(
+ 'Problem with "%s" directive:\n%s: %s'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ if options.has_key('literal'):
+ literal_block = nodes.literal_block(include_text, include_text,
+ source=path)
+ literal_block.line = 1
+ return literal_block
+ else:
+ include_lines = statemachine.string2lines(include_text,
+ convert_whitespace=1)
+ state_machine.insert_input(include_lines, path)
+ return []
+
+winclude_directive.arguments = (1, 0, 1)
+winclude_directive.options = {'literal': directives.flag,
+ 'encoding': directives.encoding}
+directives.register_directive('winclude', winclude_directive)
+
+class CubicWebReSTParser(Parser):
+ """The (customized) reStructuredText parser."""
+
+ def __init__(self):
+ self.initial_state = 'Body'
+ self.state_classes = states.state_classes
+ self.inliner = states.Inliner()
+ self.statemachine = states.RSTStateMachine(
+ state_classes=self.state_classes,
+ initial_state=self.initial_state,
+ debug=0)
+
+ def parse(self, inputstring, document):
+ """Parse `inputstring` and populate `document`, a document tree."""
+ self.setup_parse(inputstring, document)
+ inputlines = statemachine.string2lines(inputstring,
+ convert_whitespace=1)
+ self.statemachine.run(inputlines, document, inliner=self.inliner)
+ self.finish_parse()
+
+
+_REST_PARSER = CubicWebReSTParser()
+
+def rest_publish(context, data):
+ """publish a string formatted as ReStructured Text to HTML
+
+ :type context: a cubicweb application object
+
+ :type data: str
+ :param data: some ReST text
+
+ :rtype: unicode
+ :return:
+ the data formatted as HTML or the original data if an error occured
+ """
+ req = context.req
+ if isinstance(data, unicode):
+ encoding = 'unicode'
+ else:
+ encoding = req.encoding
+ settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
+ 'warning_stream': StringIO(), 'context': context,
+ # dunno what's the max, severe is 4, and we never want a crash
+ # (though try/except may be a better option...)
+ 'halt_level': 10,
+ }
+ if context:
+ if hasattr(req, 'url'):
+ base_url = req.url()
+ elif hasattr(context, 'absolute_url'):
+ base_url = context.absolute_url()
+ else:
+ base_url = req.base_url()
+ else:
+ base_url = None
+ try:
+ return publish_string(writer=Writer(base_url=base_url),
+ parser=_REST_PARSER, source=data,
+ settings_overrides=settings)
+ except Exception:
+ LOGGER.exception('error while publishing ReST text')
+ if not isinstance(data, unicode):
+ data = unicode(data, encoding, 'replace')
+ return html_escape(req._('error while publishing ReST text')
+ + '\n\n' + data)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/tal.py Tue Feb 17 23:00:28 2009 +0100
@@ -0,0 +1,256 @@
+"""provides simpleTAL extensions for CubicWeb
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+import sys
+import re
+from os.path import exists, isdir, join
+from logging import getLogger
+from StringIO import StringIO
+
+from simpletal import simpleTAL, simpleTALES
+
+from logilab.common.decorators import cached
+
+LOGGER = getLogger('cubicweb.tal')
+
+
+class LoggerAdapter(object):
+ def __init__(self, tal_logger):
+ self.tal_logger = tal_logger
+
+ def debug(self, msg):
+ LOGGER.debug(msg)
+
+ def warn(self, msg):
+ LOGGER.warning(msg)
+
+ def __getattr__(self, attrname):
+ return getattr(self.tal_logger, attrname)
+
+
+class CubicWebContext(simpleTALES.Context):
+ """add facilities to access entity / resultset"""
+
+ def __init__(self, options=None, allowPythonPath=1):
+ simpleTALES.Context.__init__(self, options, allowPythonPath)
+ self.log = LoggerAdapter(self.log)
+
+ def update(self, context):
+ for varname, value in context.items():
+ self.addGlobal(varname, value)
+
+ def addRepeat(self, name, var, initialValue):
+ simpleTALES.Context.addRepeat(self, name, var, initialValue)
+
+# XXX FIXME need to find a clean to define OPCODE values for extensions
+I18N_CONTENT = 18
+I18N_REPLACE = 19
+RQL_EXECUTE = 20
+# simpleTAL uses the OPCODE values to define priority over commands.
+# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
+# we can't use the same OPCODE for two different commands without changing
+# the simpleTAL implementation. Another solution would be to totally override
+# the REPEAT implementation with the ITER one, but some specific operations
+# (involving len() for instance) are not implemented for ITER, so we prefer
+# to keep both implementations for now, and to fool simpleTAL by using a float
+# number between 3 and 4
+TAL_ITER = 3.1
+
+
+# FIX simpleTAL HTML 4.01 stupidity
+# (simpleTAL never closes tags like INPUT, IMG, HR ...)
+simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
+
+class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
+ """extends default compiler by adding i18n:content commands"""
+
+ def __init__(self):
+ simpleTAL.HTMLTemplateCompiler.__init__(self)
+ self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
+ self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
+ self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
+ self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
+
+ def setTALPrefix(self, prefix):
+ simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
+ self.tal_attribute_map['i18n:content'] = I18N_CONTENT
+ self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
+ self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
+ self.tal_attribute_map['tal:iter'] = TAL_ITER
+
+ def compile_cmd_i18n_content(self, argument):
+ # XXX tal:content structure=, text= should we support this ?
+ structure_flag = 0
+ return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
+
+ def compile_cmd_i18n_replace(self, argument):
+ # XXX tal:content structure=, text= should we support this ?
+ structure_flag = 0
+ return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
+
+ def compile_cmd_rql(self, argument):
+ return (RQL_EXECUTE, (argument, self.endTagSymbol))
+
+ def compile_cmd_tal_iter(self, argument):
+ original_id, (var_name, expression, end_tag_symbol) = \
+ simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
+ return (TAL_ITER, (var_name, expression, self.endTagSymbol))
+
+ def getTemplate(self):
+ return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
+
+ def compileCmdAttributes (self, argument):
+ """XXX modified to support single attribute
+ definition ending by a ';'
+
+ backport this to simpleTAL
+ """
+ # Compile tal:attributes into attribute command
+ # Argument: [(attributeName, expression)]
+
+ # Break up the list of attribute settings first
+ commandArgs = []
+ # We only want to match semi-colons that are not escaped
+ argumentSplitter = re.compile(r'(?<!;);(?!;)')
+ for attributeStmt in argumentSplitter.split(argument):
+ if not attributeStmt.strip():
+ continue
+ # remove any leading space and un-escape any semi-colons
+ attributeStmt = attributeStmt.lstrip().replace(';;', ';')
+ # Break each attributeStmt into name and expression
+ stmtBits = attributeStmt.split(' ')
+ if (len (stmtBits) < 2):
+ # Error, badly formed attributes command
+ msg = "Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument
+ self.log.error(msg)
+ raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
+ attName = stmtBits[0]
+ attExpr = " ".join(stmtBits[1:])
+ commandArgs.append((attName, attExpr))
+ return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
+
+
+class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
+ """provides implementation for interpreting cubicweb extensions"""
+ def __init__(self):
+ simpleTAL.TemplateInterpreter.__init__(self)
+ self.commandHandler[I18N_CONTENT] = self.cmd_i18n
+ self.commandHandler[TAL_ITER] = self.cmdRepeat
+ # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
+
+ def cmd_i18n(self, command, args):
+ """i18n:content and i18n:replace implementation"""
+ string, replace_flag, structure_flag, end_symbol = args
+ if replace_flag:
+ self.outputTag = 0
+ result = self.context.globals['_'](string)
+ self.tagContent = (0, result)
+ self.movePCForward = self.symbolTable[end_symbol]
+ self.programCounter += 1
+
+
+class CubicWebTemplate(simpleTAL.HTMLTemplate):
+ """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
+ """
+ def expand(self, context, outputFile):
+ interpreter = CubicWebTemplateInterpreter()
+ interpreter.initialise(context, outputFile)
+ simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
+ interpreter=interpreter)
+
+ def expandInline(self, context, outputFile, interpreter):
+ """ Internally used when expanding a template that is part of a context."""
+ try:
+ interpreter.execute(self)
+ except UnicodeError, unierror:
+ LOGGER.exception(str(unierror))
+ raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
+
+
+def compile_template(template):
+ """compiles a TAL template string
+ :type template: unicode
+ :param template: a TAL-compliant template string
+ """
+ string_buffer = StringIO(template)
+ compiler = CubicWebTemplateCompiler()
+ compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
+ return compiler.getTemplate()
+
+
+def compile_template_file(filepath):
+ """compiles a TAL template file
+ :type filepath: str
+ :param template: path of the file to compile
+ """
+ fp = file(filepath)
+ file_content = unicode(fp.read()) # template file should be pure ASCII
+ fp.close()
+ return compile_template(file_content)
+
+
+def evaluatePython (self, expr):
+ if not self.allowPythonPath:
+ return self.false
+ globals = {}
+ for name, value in self.globals.items():
+ if isinstance (value, simpleTALES.ContextVariable):
+ value = value.rawValue()
+ globals[name] = value
+ globals['path'] = self.pythonPathFuncs.path
+ globals['string'] = self.pythonPathFuncs.string
+ globals['exists'] = self.pythonPathFuncs.exists
+ globals['nocall'] = self.pythonPathFuncs.nocall
+ globals['test'] = self.pythonPathFuncs.test
+ locals = {}
+ for name, value in self.locals.items():
+ if (isinstance (value, simpleTALES.ContextVariable)):
+ value = value.rawValue()
+ locals[name] = value
+ # XXX precompile expr will avoid late syntax error
+ try:
+ result = eval(expr, globals, locals)
+ except Exception, ex:
+ ex = ex.__class__('in %r: %s' % (expr, ex))
+ raise ex, None, sys.exc_info()[-1]
+ if (isinstance (result, simpleTALES.ContextVariable)):
+ return result.value()
+ return result
+
+simpleTALES.Context.evaluatePython = evaluatePython
+
+
+class talbased(object):
+ def __init__(self, filename, write=True):
+## if not osp.isfile(filepath):
+## # print "[tal.py] just for tests..."
+## # get parent frame
+## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
+## filepath = osp.join(directory, filepath)
+ self.filename = filename
+ self.write = write
+
+ def __call__(self, viewfunc):
+ def wrapped(instance, *args, **kwargs):
+ variables = viewfunc(instance, *args, **kwargs)
+ html = instance.tal_render(self._compiled_template(instance), variables)
+ if self.write:
+ instance.w(html)
+ else:
+ return html
+ return wrapped
+
+ def _compiled_template(self, instance):
+ for fileordirectory in instance.config.vregistry_path():
+ filepath = join(fileordirectory, self.filename)
+ if isdir(fileordirectory) and exists(filepath):
+ return compile_template_file(filepath)
+ raise Exception('no such template %s' % self.filename)
+ _compiled_template = cached(_compiled_template, 0)
+
--- a/schema.py Tue Feb 17 22:49:54 2009 +0100
+++ b/schema.py Tue Feb 17 23:00:28 2009 +0100
@@ -954,9 +954,14 @@
# translation
PERM_USE_TEMPLATE_FORMAT = _('use_template_format')
+from cubicweb.mttransforms import HAS_TAL
+
class FormatConstraint(StaticVocabularyConstraint):
- need_perm_formats = (_('text/cubicweb-page-template'),
- )
+ if HAS_TAL:
+ need_perm_formats = (_('text/cubicweb-page-template'),)
+ else:
+ need_perm_formats = ()
+
regular_formats = (_('text/rest'),
_('text/html'),
_('text/plain'),
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/utils.py Tue Feb 17 23:00:28 2009 +0100
@@ -0,0 +1,277 @@
+"""Some utilities for CubicWeb server/clients.
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import locale
+from md5 import md5
+from time import time
+from random import randint, seed
+
+# initialize random seed from current time
+seed()
+
+def make_uid(key):
+ """forge a unique identifier"""
+ msg = str(key) + "%.10f"%time() + str(randint(0, 1000000))
+ return md5(msg).hexdigest()
+
+def working_hours(mxdate):
+ """
+ Predicate returning True is the date's hour is in working hours (8h->20h)
+ """
+ if mxdate.hour > 7 and mxdate.hour < 21:
+ return True
+ return False
+
+def date_range(begin, end, incr=1, include=None):
+ """yields each date between begin and end
+ :param begin: the start date
+ :param end: the end date
+ :param incr: the step to use to iterate over dates. Default is
+ one day.
+ :param include: None (means no exclusion) or a function taking a
+ date as parameter, and returning True if the date
+ should be included.
+ """
+ date = begin
+ while date <= end:
+ if include is None or include(date):
+ yield date
+ date += incr
+
+def ustrftime(date, fmt='%Y-%m-%d'):
+ """like strftime, but returns a unicode string instead of an encoded
+ string which may be problematic with localized date.
+
+ encoding is guessed by locale.getpreferredencoding()
+ """
+ # date format may depend on the locale
+ encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8'
+ return unicode(date.strftime(fmt), encoding)
+
+
+def dump_class(cls, clsname):
+ """create copy of a class by creating an empty class inheriting
+ from the given cls.
+
+ Those class will be used as place holder for attribute and relation
+ description
+ """
+ # type doesn't accept unicode name
+ # return type.__new__(type, str(clsname), (cls,), {})
+ # __autogenerated__ attribute is just a marker
+ return type(str(clsname), (cls,), {'__autogenerated__': True})
+
+
+def merge_dicts(dict1, dict2):
+ """update a copy of `dict1` with `dict2`"""
+ dict1 = dict(dict1)
+ dict1.update(dict2)
+ return dict1
+
+
+class SizeConstrainedList(list):
+ """simple list that makes sure the list does not get bigger
+ than a given size.
+
+ when the list is full and a new element is added, the first
+ element of the list is removed before appending the new one
+
+ >>> l = SizeConstrainedList(2)
+ >>> l.append(1)
+ >>> l.append(2)
+ >>> l
+ [1, 2]
+ >>> l.append(3)
+ [2, 3]
+ """
+ def __init__(self, maxsize):
+ self.maxsize = maxsize
+
+ def append(self, element):
+ if len(self) == self.maxsize:
+ del self[0]
+ super(SizeConstrainedList, self).append(element)
+
+ def extend(self, sequence):
+ super(SizeConstrainedList, self).extend(sequence)
+ keepafter = len(self) - self.maxsize
+ if keepafter > 0:
+ del self[:keepafter]
+
+ __iadd__ = extend
+
+
+class UStringIO(list):
+ """a file wrapper which automatically encode unicode string to an encoding
+ specifed in the constructor
+ """
+
+ def __nonzero__(self):
+ return True
+
+ def write(self, value):
+ assert isinstance(value, unicode), u"unicode required not %s : %s"\
+ % (type(value).__name__, repr(value))
+ self.append(value)
+
+ def getvalue(self):
+ return u''.join(self)
+
+ def __repr__(self):
+ return '<%s at %#x>' % (self.__class__.__name__, id(self))
+
+
+class HTMLHead(UStringIO):
+ """wraps HTML header's stream
+
+ Request objects use a HTMLHead instance to ease adding of
+ javascripts and stylesheets
+ """
+ js_unload_code = u'jQuery(window).unload(unloadPageData);'
+
+ def __init__(self):
+ super(HTMLHead, self).__init__()
+ self.jsvars = []
+ self.jsfiles = []
+ self.cssfiles = []
+ self.ie_cssfiles = []
+ self.post_inlined_scripts = []
+ self.pagedata_unload = False
+
+
+ def add_raw(self, rawheader):
+ self.write(rawheader)
+
+ def define_var(self, var, value):
+ self.jsvars.append( (var, value) )
+
+ def add_post_inline_script(self, content):
+ self.post_inlined_scripts.append(content)
+
+ def add_onload(self, jscode):
+ self.add_post_inline_script(u"""jQuery(document).ready(function () {
+ %s
+ });""" % jscode)
+
+
+ def add_js(self, jsfile):
+ """adds `jsfile` to the list of javascripts used in the webpage
+
+ This function checks if the file has already been added
+ :param jsfile: the script's URL
+ """
+ if jsfile not in self.jsfiles:
+ self.jsfiles.append(jsfile)
+
+ def add_css(self, cssfile, media):
+ """adds `cssfile` to the list of javascripts used in the webpage
+
+ This function checks if the file has already been added
+ :param cssfile: the stylesheet's URL
+ """
+ if (cssfile, media) not in self.cssfiles:
+ self.cssfiles.append( (cssfile, media) )
+
+ def add_ie_css(self, cssfile, media='all'):
+ """registers some IE specific CSS"""
+ if (cssfile, media) not in self.ie_cssfiles:
+ self.ie_cssfiles.append( (cssfile, media) )
+
+ def add_unload_pagedata(self):
+ """registers onunload callback to clean page data on server"""
+ if not self.pagedata_unload:
+ self.post_inlined_scripts.append(self.js_unload_code)
+ self.pagedata_unload = True
+
+ def getvalue(self, skiphead=False):
+ """reimplement getvalue to provide a consistent (and somewhat browser
+ optimzed cf. http://stevesouders.com/cuzillion) order in external
+ resources declaration
+ """
+ w = self.write
+ # 1/ variable declaration if any
+ if self.jsvars:
+ from simplejson import dumps
+ w(u'<script type="text/javascript">\n')
+ for var, value in self.jsvars:
+ w(u'%s = %s;\n' % (var, dumps(value)))
+ w(u'</script>\n')
+ # 2/ css files
+ for cssfile, media in self.cssfiles:
+ w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
+ (media, cssfile))
+ # 3/ ie css if necessary
+ if self.ie_cssfiles:
+ w(u'<!--[if lt IE 8]>\n')
+ for cssfile, media in self.ie_cssfiles:
+ w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
+ (media, cssfile))
+ w(u'<![endif]--> \n')
+ # 4/ js files
+ for jsfile in self.jsfiles:
+ w(u'<script type="text/javascript" src="%s"></script>\n' % jsfile)
+ # 5/ post inlined scripts (i.e. scripts depending on other JS files)
+ if self.post_inlined_scripts:
+ w(u'<script type="text/javascript">\n')
+ w(u'\n\n'.join(self.post_inlined_scripts))
+ w(u'\n</script>\n')
+ header = super(HTMLHead, self).getvalue()
+ if skiphead:
+ return header
+ return u'<head>\n%s</head>\n' % header
+
+
+class HTMLStream(object):
+ """represents a HTML page.
+
+ This is used my main templates so that HTML headers can be added
+ at any time during the page generation.
+
+ HTMLStream uses the (U)StringIO interface to be compliant with
+ existing code.
+ """
+
+ def __init__(self, req):
+ # stream for <head>
+ self.head = req.html_headers
+ # main stream
+ self.body = UStringIO()
+ self.doctype = u''
+ # xmldecl and html opening tag
+ self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
+ self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
+ 'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
+ 'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
+
+
+ def write(self, data):
+ """StringIO interface: this method will be assigned to self.w
+ """
+ self.body.write(data)
+
+ def getvalue(self):
+ """writes HTML headers, closes </head> tag and writes HTML body"""
+ return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
+ self.htmltag,
+ self.head.getvalue(),
+ self.body.getvalue())
+
+
+class AcceptMixIn(object):
+ """Mixin class for vobjects defining the 'accepts' attribute describing
+ a set of supported entity type (Any by default).
+ """
+ # XXX deprecated, no more necessary
+
+
+from logilab.common.deprecation import moved, class_moved
+rql_for_eid = moved('cubicweb.common.uilib', 'rql_for_eid')
+ajax_replace_url = moved('cubicweb.common.uilib', 'ajax_replace_url')
+
+import cubicweb
+Binary = class_moved(cubicweb.Binary)
--- a/view.py Tue Feb 17 22:49:54 2009 +0100
+++ b/view.py Tue Feb 17 23:00:28 2009 +0100
@@ -15,9 +15,9 @@
from cubicweb.selectors import (yes, match_user_groups, implements,
nonempty_rset, none_rset)
from cubicweb.selectors import require_group_compat, accepts_compat
+from cubicweb.appobject import AppRsetObject
+from cubicweb.utils import UStringIO, HTMLStream
from cubicweb.common.registerers import accepts_registerer, priority_registerer, yes_registerer
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.utils import UStringIO, HTMLStream
_ = unicode
@@ -321,7 +321,7 @@
"""
# XXX deprecate
__registerer__ = accepts_registerer
- __selectors__ = (implements('Any'),)
+ __select__ = implements('Any')
registered = accepts_compat(View.registered.im_func)
category = 'entityview'
@@ -332,7 +332,7 @@
to be displayed (so they can always be displayed !)
"""
__registerer__ = priority_registerer
- __selectors__ = (none_rset,)
+ __select__ = none_rset()
registered = require_group_compat(View.registered.im_func)
category = 'startupview'
@@ -354,7 +354,7 @@
"""base class for entity views which may also be applied to None
result set (usually a default rql is provided by the view class)
"""
- __selectors__ = ((none_rset | implements('Any')),)
+ __select__ = none_rset() | implements('Any')
default_rql = None
@@ -389,7 +389,7 @@
class AnyRsetView(View):
"""base class for views applying on any non empty result sets"""
- __selectors__ = (nonempty_rset,)
+ __select__ = nonempty_rset()
category = 'anyrsetview'
@@ -418,7 +418,7 @@
is only used globally (i.e. no result set adaptation)
"""
__registry__ = 'templates'
- __selectors__ = (yes,)
+ __select__ = yes()
registered = require_group_compat(View.registered.im_func)
@@ -501,7 +501,7 @@
"""base class for components"""
__registry__ = 'components'
__registerer__ = yes_registerer
- __selectors__ = (yes,)
+ __select__ = yes()
property_defs = {
_('visible'): dict(type='Boolean', default=True,
help=_('display the box or not')),
--- a/vregistry.py Tue Feb 17 22:49:54 2009 +0100
+++ b/vregistry.py Tue Feb 17 23:00:28 2009 +0100
@@ -245,6 +245,7 @@
def register(self, obj, registryname=None, oid=None, clear=False):
"""base method to add an object in the registry"""
+ assert not '__abstract__' in obj.__dict__
registryname = registryname or obj.__registry__
oid = oid or obj.id
registry = self._registries.setdefault(registryname, {})
--- a/web/views/actions.py Tue Feb 17 22:49:54 2009 +0100
+++ b/web/views/actions.py Tue Feb 17 23:00:28 2009 +0100
@@ -51,8 +51,7 @@
if accept match.
"""
id = 'select'
- __selectors__ = (match_search_state('linksearch'),
- match_searched_etype)
+ __select__ = match_search_state('linksearch') & match_searched_etype()
title = _('select')
category = 'mainactions'
@@ -64,7 +63,7 @@
class CancelSelectAction(Action):
id = 'cancel'
- __selectors__ = (match_search_state('linksearch'),)
+ __select__ = match_search_state('linksearch')
title = _('cancel select')
category = 'mainactions'
@@ -78,10 +77,10 @@
class ViewAction(Action):
id = 'view'
- __selectors__ = (match_search_state('normal'),
- match_user_groups('users', 'managers'),
- view_is_not_default_view,
- non_final_entity())
+ __select__ = (match_search_state('normal') &
+ match_user_groups('users', 'managers') &
+ view_is_not_default_view() &
+ non_final_entity())
title = _('view')
category = 'mainactions'
@@ -97,9 +96,9 @@
class ModifyAction(Action):
id = 'edit'
- __selectors__ = (match_search_state('normal'),
- one_line_rset,
- has_permission('update') | has_editable_relation('add'))
+ __select__ = (match_search_state('normal') &
+ one_line_rset() &
+ (has_permission('update') | has_editable_relation('add')))
title = _('modify')
category = 'mainactions'
@@ -112,9 +111,9 @@
class MultipleEditAction(Action):
id = 'muledit' # XXX get strange conflicts if id='edit'
- __selectors__ = (match_search_state('normal'),
- two_lines_rset, one_etype_rset,
- has_permission('update'))
+ __select__ = (match_search_state('normal') &
+ two_lines_rset(), one_etype_rset() &
+ has_permission('update'))
title = _('modify')
category = 'mainactions'
@@ -128,25 +127,27 @@
class ManagePermissionsAction(Action):
id = 'addpermission'
- __selectors__ = match_user_groups('managers')
+ __select__ = match_user_groups('managers')
title = _('manage permissions')
category = 'moreactions'
order = 100
+ @classmethod
def registered(cls, vreg):
super(ManagePermissionsAction, cls).registered(vreg)
if 'require_permission' in vreg.schema:
- cls.__selectors__ |= relation_possible('require_permission', 'subject', 'EPermission',
- action='add')
-
+ cls.__select__ |= relation_possible('require_permission', 'subject', 'EPermission',
+ action='add')
+ return super(ManagePermissionsAction, cls).registered(vreg)
+
def url(self):
return self.rset.get_entity(0, 0).absolute_url(vid='security')
class DeleteAction(Action):
id = 'delete'
- __selectors__ = (one_line_rset, has_permission('delete'))
+ __select__ = one_line_rset() & has_permission('delete')
title = _('delete')
category = 'moreactions'
@@ -161,7 +162,7 @@
class CopyAction(Action):
id = 'copy'
- __selectors__ = (one_line_rset, has_permission('add'))
+ __select__ = one_line_rset() & has_permission('add')
title = _('copy')
category = 'moreactions'
@@ -177,11 +178,10 @@
add a new one
"""
id = 'addentity'
- __selectors__ = (match_search_state('normal'),
- (addable_etype_empty_rset
- # XXX has_add_permission in the middle so '&' is available
- | (two_lines_rset & has_add_permission() & one_etype_rset ))
- )
+ __select__ = (match_search_state('normal') &
+ (addable_etype_empty_rset()
+ | (two_lines_rset() & one_etype_rset & & has_add_permission()))
+ )
category = 'moreactions'
order = 40
@@ -204,7 +204,7 @@
class UserPreferencesAction(Action):
id = 'myprefs'
- __selectors__ = (authenticated_user,)
+ __select__ = authenticated_user()
title = _('user preferences')
category = 'useractions'
@@ -216,7 +216,7 @@
class UserInfoAction(Action):
id = 'myinfos'
- __selectors__ = (authenticated_user,)
+ __select__ = authenticated_user()
title = _('personnal informations')
category = 'useractions'
@@ -228,7 +228,7 @@
class LogoutAction(Action):
id = 'logout'
- __selectors__ = (authenticated_user,)
+ __select__ = authenticated_user()
title = _('logout')
category = 'useractions'
@@ -242,7 +242,7 @@
class ManagersAction(Action):
__abstract__ = True
- __selectors__ = (match_user_groups('managers'),)
+ __select__ = match_user_groups('managers')
category = 'siteactions'
@@ -264,7 +264,7 @@
class ViewSchemaAction(Action):
id = 'schema'
- __selectors__ = (yes,)
+ __select__ = yes()
title = _("site schema")
category = 'siteactions'
--- a/web/views/edit_multiple.pt Tue Feb 17 22:49:54 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-<!-- rows are precomputed first to consume error messages if necessary -->
-<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);"
- action="%(action)s"
- tal:define="rows python:[self.edit_form(e) for e in rset.entities()]"
- >
- <div tal:replace="structure self/error_message"/>
- <div id="progress" tal:content="progress">validating changes...</div>
- <fieldset>
- <input type="hidden" name="__errorurl" value="#"
- tal:attributes="value req/url;" />
- <input type="hidden" name="__form_id" value="#"
- tal:attributes="value python:self.id"/>
- <input type="hidden" name="__redirectvid" value="primary"
- tal:attributes="value python:req.form.get('__redirectvid', 'list');"/>
- <input type="hidden" name="__redirectrql" value="#"
- tal:attributes="value python:req.form.get('__redirectrql', rset.printable_rql());"/>
- <table class="listing">
- <tr class="header">
- <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>
- <tal:th tal:iter="rdef python:sampleentity.relations_by_category('primary', 'add')">
- <th tal:condition="python: rdef[0].type != 'eid'"
- tal:content="python: rdef[0].display_name(req, rdef[-1])"/>
- </tal:th>
- </tr>
- <tr tal:iter="row rows" tal:attributes="class python: repeat['row'].getOdd() and 'even' or 'odd'" tal:content="structure row"/>
- </table>
- <table width="100%%">
- <tr>
- <td align="left">
- <input class="validateButton" type="submit" value="#"
- tal:attributes="value okbuttonmsg; title okbuttontitle;"/>
- <input class="validateButton" type="reset" name="__action_cancel" value="#"
- tal:attributes="value cancelbuttonmsg; title cancelbuttontitle;"/>
- </td>
- </tr>
- </table>
- </fieldset>
-</form>
--- a/web/views/edit_relations.pt Tue Feb 17 22:49:54 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-<fieldset class="subentity">
-<legend class="iformTitle" tal:content="python: label">relations</legend>
-<table id="relatedEntities"
- tal:define="pendings python: list(self.restore_pending_inserts(entity))">
- <span tal:iter="row python: self.relations_table(entity)" tal:omit-tag="python: True">
- <tr tal:condition="python: row[2]">
- <th class="labelCol" tal:content="python: display_name(req, row[0].type, row[1])">relation name</th>
- <td>
- <ul>
- <li tal:iter="viewparams python: row[2]" class="invisible">
- <span tal:replace="structure python:viewparams[1]">[del it if you can]</span>
- <div tal:attributes="id python: 'span'+viewparams[0]; class python: viewparams[2]"
- tal:content="structure python: viewparams[3]">related entity view</div>
- </li>
- <li class="invisible"
- tal:condition="python: not self.force_display and self.maxrelitems < len(row[2])"
- tal:content="structure python:self.force_display_link()"/>
- </ul>
- </td>
- </tr>
- </span>
- <tr tal:iter="row pendings"
- tal:attributes="id python: 'tr' + row[1]">
- <!-- row: (relname, nodeid, js, url, eview) -->
- <th tal:content="python: row[3]">relation name</th>
- <td>
- <a class="handle" title="cancel this insert"
- tal:attributes="href python: row[2]">[x]</a>
- <a class="editionPending"
- tal:attributes="href python: row[4]; id python: 'a' + row[1]"
- tal:content="python: row[5]">entity\'s text_view</a>
- </td>
- </tr>
- <tr tal:condition="not:pendings"><th> </th><td> </td></tr>
- <tr class="separator" tal:attributes="id string: relationSelectorRow_$eid;">
- <th class="labelCol">
- <span i18n:content="add relation"></span>
- <select tal:attributes="id string: relationSelector_${eid};
- tabindex req/next_tabindex;
- onchange string: javascript:showMatchingSelect(this.options[this.selectedIndex].value,${eid});">
- <option value="" i18n:content="select a relation">select a relation</option>
- <option tal:iter="rel python: entity.srelations_by_category(('generic', 'metadata'), 'add')"
- tal:attributes="value python: '%s_%s' % (rel[1], rel[2])"
- tal:content="python: rel[0]">rel</option>
- </select>
- </th>
- <td tal:attributes="id string: unrelatedDivs_$eid">
- </td>
- </tr>
-</table>
-</fieldset>