--- a/common/__init__.py Tue Dec 08 10:40:20 2009 +0100
+++ b/common/__init__.py Tue Dec 08 10:58:56 2009 +0100
@@ -7,47 +7,3 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from logilab.common.adbh import FunctionDescr
-
-from cubicweb._exceptions import * # bw compat
-
-from rql.utils import register_function, iter_funcnode_variables
-
-class COMMA_JOIN(FunctionDescr):
- supported_backends = ('postgres', 'sqlite',)
- rtype = 'String'
-
- @classmethod
- def st_description(cls, funcnode, mainindex, tr):
- return ', '.join(sorted(term.get_description(mainindex, tr)
- for term in iter_funcnode_variables(funcnode)))
-
-register_function(COMMA_JOIN) # XXX do not expose?
-
-
-class CONCAT_STRINGS(COMMA_JOIN):
- aggregat = True
-
-register_function(CONCAT_STRINGS) # XXX bw compat
-
-class GROUP_CONCAT(CONCAT_STRINGS):
- supported_backends = ('mysql', 'postgres', 'sqlite',)
-
-register_function(GROUP_CONCAT)
-
-
-class LIMIT_SIZE(FunctionDescr):
- supported_backends = ('postgres', 'sqlite',)
- rtype = 'String'
-
- @classmethod
- def st_description(cls, funcnode, mainindex, tr):
- return funcnode.children[0].get_description(mainindex, tr)
-
-register_function(LIMIT_SIZE)
-
-
-class TEXT_LIMIT_SIZE(LIMIT_SIZE):
- supported_backends = ('mysql', 'postgres', 'sqlite',)
-
-register_function(TEXT_LIMIT_SIZE)
--- a/common/mail.py Tue Dec 08 10:40:20 2009 +0100
+++ b/common/mail.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,272 +1,5 @@
-"""Common utilies to format / semd emails.
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from base64 import b64encode, b64decode
-from itertools import repeat
-from time import time
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEText import MIMEText
-from email.MIMEImage import MIMEImage
-from email.Header import Header
-try:
- from socket import gethostname
-except ImportError:
- def gethostname(): # gae
- return 'XXX'
-
-from cubicweb.view import EntityView
-from cubicweb.entity import Entity
-
-def header(ustring):
- return Header(ustring.encode('UTF-8'), 'UTF-8')
-
-def addrheader(uaddr, uname=None):
- # even if an email address should be ascii, encode it using utf8 since
- # automatic tests may generate non ascii email address
- addr = uaddr.encode('UTF-8')
- if uname:
- return '%s <%s>' % (header(uname).encode(), addr)
- return addr
-
-
-def construct_message_id(appid, eid, withtimestamp=True):
- if withtimestamp:
- addrpart = 'eid=%s×tamp=%.10f' % (eid, time())
- else:
- addrpart = 'eid=%s' % eid
- # we don't want any equal sign nor trailing newlines
- leftpart = b64encode(addrpart, '.-').rstrip().rstrip('=')
- return '<%s@%s.%s>' % (leftpart, appid, gethostname())
-
-
-def parse_message_id(msgid, appid):
- if msgid[0] == '<':
- msgid = msgid[1:]
- if msgid[-1] == '>':
- msgid = msgid[:-1]
- try:
- values, qualif = msgid.split('@')
- padding = len(values) % 4
- values = b64decode(str(values + '='*padding), '.-')
- values = dict(v.split('=') for v in values.split('&'))
- fromappid, host = qualif.split('.', 1)
- except:
- return None
- if appid != fromappid or host != gethostname():
- return None
- return values
-
-
-def format_mail(uinfo, to_addrs, content, subject="",
- cc_addrs=(), msgid=None, references=(), config=None):
- """Sends an Email to 'e_addr' with content 'content', and subject 'subject'
-
- to_addrs and cc_addrs are expected to be a list of email address without
- name
- """
- assert type(content) is unicode, repr(content)
- msg = MIMEText(content.encode('UTF-8'), 'plain', 'UTF-8')
- # safety: keep only the first newline
- subject = subject.splitlines()[0]
- msg['Subject'] = header(subject)
- if uinfo.get('email'):
- email = uinfo['email']
- elif config and config['sender-addr']:
- email = unicode(config['sender-addr'])
- else:
- email = u''
- if uinfo.get('name'):
- name = uinfo['name']
- elif config and config['sender-addr']:
- name = unicode(config['sender-name'])
- else:
- name = u''
- msg['From'] = addrheader(email, name)
- if config and config['sender-addr'] and config['sender-addr'] != email:
- appaddr = addrheader(config['sender-addr'], config['sender-name'])
- msg['Reply-to'] = '%s, %s' % (msg['From'], appaddr)
- elif email:
- msg['Reply-to'] = msg['From']
- if config is not None:
- msg['X-CW'] = config.appid
- unique_addrs = lambda addrs: sorted(set(addr for addr in addrs if addr is not None))
- msg['To'] = ', '.join(addrheader(addr) for addr in unique_addrs(to_addrs))
- if cc_addrs:
- msg['Cc'] = ', '.join(addrheader(addr) for addr in unique_addrs(cc_addrs))
- if msgid:
- msg['Message-id'] = msgid
- if references:
- msg['References'] = ', '.join(references)
- return msg
-
-
-class HtmlEmail(MIMEMultipart):
-
- def __init__(self, subject, textcontent, htmlcontent,
- sendermail=None, sendername=None, recipients=None, ccrecipients=None):
- MIMEMultipart.__init__(self, 'related')
- self['Subject'] = header(subject)
- self.preamble = 'This is a multi-part message in MIME format.'
- # Attach alternative text message
- alternative = MIMEMultipart('alternative')
- self.attach(alternative)
- msgtext = MIMEText(textcontent.encode('UTF-8'), 'plain', 'UTF-8')
- alternative.attach(msgtext)
- # Attach html message
- msghtml = MIMEText(htmlcontent.encode('UTF-8'), 'html', 'UTF-8')
- alternative.attach(msghtml)
- if sendermail or sendername:
- self['From'] = addrheader(sendermail, sendername)
- if recipients:
- self['To'] = ', '.join(addrheader(addr) for addr in recipients if addr is not None)
- if ccrecipients:
- self['Cc'] = ', '.join(addrheader(addr) for addr in ccrecipients if addr is not None)
-
- def attach_image(self, data, htmlId):
- image = MIMEImage(data)
- image.add_header('Content-ID', '<%s>' % htmlId)
- self.attach(image)
-
-
-class NotificationView(EntityView):
- """abstract view implementing the "email" API (eg to simplify sending
- notification)
- """
- # XXX refactor this class to work with len(rset) > 1
-
- msgid_timestamp = True
-
- # this is usually the method to call
- def render_and_send(self, **kwargs):
- """generate and send an email message for this view"""
- delayed = kwargs.pop('delay_to_commit', None)
- for recipients, msg in self.render_emails(**kwargs):
- if delayed is None:
- self.send(recipients, msg)
- elif delayed:
- self.send_on_commit(recipients, msg)
- else:
- self.send_now(recipients, msg)
-
- def cell_call(self, row, col=0, **kwargs):
- self.w(self._cw._(self.content) % self.context(**kwargs))
-
- def render_emails(self, **kwargs):
- """generate and send emails for this view (one per recipient)"""
- self._kwargs = kwargs
- recipients = self.recipients()
- if not recipients:
- self.info('skipping %s notification, no recipients', self.__regid__)
- return
- if self.cw_rset is not None:
- entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
- # if the view is using timestamp in message ids, no way to reference
- # previous email
- if not self.msgid_timestamp:
- refs = [self.construct_message_id(eid)
- for eid in entity.notification_references(self)]
- else:
- refs = ()
- msgid = self.construct_message_id(entity.eid)
- else:
- refs = ()
- msgid = None
- req = self._cw
- self.user_data = req.user_data()
- origlang = req.lang
- for something in recipients:
- if isinstance(something, Entity):
- # hi-jack self._cw to get a session for the returned user
- self._cw = self._cw.hijack_user(something)
- emailaddr = something.get_email()
- else:
- emailaddr, lang = something
- self._cw.set_language(lang)
- # since the same view (eg self) may be called multiple time and we
- # need a fresh stream at each iteration, reset it explicitly
- self.w = None
- # XXX call render before subject to set .row/.col attributes on the
- # view
- try:
- content = self.render(row=0, col=0, **kwargs)
- subject = self.subject()
- except SkipEmail:
- continue
- except Exception, ex:
- # shouldn't make the whole transaction fail because of rendering
- # error (unauthorized or such)
- self.exception(str(ex))
- continue
- msg = format_mail(self.user_data, [emailaddr], content, subject,
- config=self._cw.vreg.config, msgid=msgid, references=refs)
- yield [emailaddr], msg
- # restore language
- req.set_language(origlang)
-
- # recipients / email sending ###############################################
-
- def recipients(self):
- """return a list of either 2-uple (email, language) or user entity to
- who this email should be sent
- """
- # use super_session when available, we don't want to consider security
- # when selecting recipients_finder
- try:
- req = self._cw.super_session
- except AttributeError:
- req = self._cw
- finder = self._cw.vreg['components'].select('recipients_finder', req,
- rset=self.cw_rset,
- row=self.cw_row or 0,
- col=self.cw_col or 0)
- return finder.recipients()
-
- def send_now(self, recipients, msg):
- self._cw.vreg.config.sendmails([(msg, recipients)])
-
- def send_on_commit(self, recipients, msg):
- raise NotImplementedError
-
- send = send_now
-
- # email generation helpers #################################################
-
- def construct_message_id(self, eid):
- return construct_message_id(self._cw.vreg.config.appid, eid, self.msgid_timestamp)
-
- def format_field(self, attr, value):
- return ':%(attr)s: %(value)s' % {'attr': attr, 'value': value}
-
- def format_section(self, attr, value):
- return '%(attr)s\n%(ul)s\n%(value)s\n' % {
- 'attr': attr, 'ul': '-'*len(attr), 'value': value}
-
- def subject(self):
- entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
- subject = self._cw._(self.message)
- etype = entity.dc_type()
- eid = entity.eid
- login = self.user_data['login']
- return self._cw._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
-
- def context(self, **kwargs):
- entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
- for key, val in kwargs.iteritems():
- if val and isinstance(val, unicode) and val.strip():
- kwargs[key] = self._cw._(val)
- kwargs.update({'user': self.user_data['login'],
- 'eid': entity.eid,
- 'etype': entity.dc_type(),
- 'url': entity.absolute_url(),
- 'title': entity.dc_long_title(),})
- return kwargs
-
-
-class SkipEmail(Exception):
- """raise this if you decide to skip an email during its generation"""
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.mail', DeprecationWarning, stacklevel=2)
+from cubicweb.mail import *
--- a/common/mixins.py Tue Dec 08 10:40:20 2009 +0100
+++ b/common/mixins.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,315 +1,5 @@
-"""mixins of entity/views organized somewhat in a graph or tree structure
-
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.common.deprecation import deprecated
-from logilab.common.decorators import cached
-
-from cubicweb import typed_eid
-from cubicweb.selectors import implements
-from cubicweb.interfaces import IEmailable, ITree
-
-
-class TreeMixIn(object):
- """base tree-mixin providing the tree interface
-
- This mixin has to be inherited explicitly and configured using the
- tree_attribute, parent_target and children_target class attribute to
- benefit from this default implementation
- """
- tree_attribute = None
- # XXX misnamed
- parent_target = 'subject'
- children_target = 'object'
-
- def different_type_children(self, entities=True):
- """return children entities of different type as this entity.
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- res = self.related(self.tree_attribute, self.children_target,
- entities=entities)
- if entities:
- return [e for e in res if e.e_schema != self.e_schema]
- return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.cw_col)
-
- def same_type_children(self, entities=True):
- """return children entities of the same type as this entity.
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- res = self.related(self.tree_attribute, self.children_target,
- entities=entities)
- if entities:
- return [e for e in res if e.e_schema == self.e_schema]
- return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.cw_col)
-
- def iterchildren(self, _done=None):
- if _done is None:
- _done = set()
- for child in self.children():
- if child.eid in _done:
- self.error('loop in %s tree', self.__regid__.lower())
- continue
- yield child
- _done.add(child.eid)
-
- def prefixiter(self, _done=None):
- if _done is None:
- _done = set()
- if self.eid in _done:
- return
- yield self
- _done.add(self.eid)
- for child in self.iterchildren(_done):
- try:
- for entity in child.prefixiter(_done):
- yield entity
- except AttributeError:
- pass
-
- @cached
- def path(self):
- """returns the list of eids from the root object to this object"""
- path = []
- parent = self
- while parent:
- if parent.eid in path:
- self.error('loop in %s tree', self.__regid__.lower())
- break
- path.append(parent.eid)
- try:
- # check we are not leaving the tree
- if (parent.tree_attribute != self.tree_attribute or
- parent.parent_target != self.parent_target):
- break
- parent = parent.parent()
- except AttributeError:
- break
-
- path.reverse()
- return path
-
- def iterparents(self):
- def _uptoroot(self):
- curr = self
- while True:
- curr = curr.parent()
- if curr is None:
- break
- yield curr
- return _uptoroot(self)
-
- def notification_references(self, view):
- """used to control References field of email send on notification
- for this entity. `view` is the notification view.
-
- Should return a list of eids which can be used to generate message ids
- of previously sent email
- """
- return self.path()[:-1]
-
-
- ## ITree interface ########################################################
- def parent(self):
- """return the parent entity if any, else None (e.g. if we are on the
- root
- """
- try:
- return self.related(self.tree_attribute, self.parent_target,
- entities=True)[0]
- except (KeyError, IndexError):
- return None
-
- def children(self, entities=True, sametype=False):
- """return children entities
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- if sametype:
- return self.same_type_children(entities)
- else:
- return self.related(self.tree_attribute, self.children_target,
- entities=entities)
-
- def children_rql(self):
- return self.related_rql(self.tree_attribute, self.children_target)
-
- def is_leaf(self):
- return len(self.children()) == 0
-
- def is_root(self):
- return self.parent() is None
-
- def root(self):
- """return the root object"""
- return self._cw.entity_from_eid(self.path()[0])
-
-
-class EmailableMixIn(object):
- """base mixin providing the default get_email() method used by
- the massmailing view
-
- NOTE: The default implementation is based on the
- primary_email / use_email scheme
- """
- __implements__ = (IEmailable,)
-
- def get_email(self):
- if getattr(self, 'primary_email', None):
- return self.primary_email[0].address
- if getattr(self, 'use_email', None):
- return self.use_email[0].address
- return None
-
- @classmethod
- def allowed_massmail_keys(cls):
- """returns a set of allowed email substitution keys
-
- The default is to return the entity's attribute list but an
- entity class might override this method to allow extra keys.
- For instance, the Person class might want to return a `companyname`
- key.
- """
- return set(rschema.type
- for rschema, attrtype in cls.e_schema.attribute_definitions()
- if attrtype.type not in ('Password', 'Bytes'))
-
- def as_email_context(self):
- """returns the dictionary as used by the sendmail controller to
- build email bodies.
-
- NOTE: the dictionary keys should match the list returned by the
- `allowed_massmail_keys` method.
- """
- return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
-
-
-"""pluggable mixins system: plug classes registered in MI_REL_TRIGGERS on entity
-classes which have the relation described by the dict's key.
-
-NOTE: pluggable mixins can't override any method of the 'explicit' user classes tree
-(eg without plugged classes). This includes bases Entity and AnyEntity classes.
-"""
-MI_REL_TRIGGERS = {
- ('primary_email', 'subject'): EmailableMixIn,
- ('use_email', 'subject'): EmailableMixIn,
- }
-
-
-
-def _done_init(done, view, row, col):
- """handle an infinite recursion safety belt"""
- if done is None:
- done = set()
- entity = view.cw_rset.get_entity(row, col)
- if entity.eid in done:
- msg = entity._cw._('loop in %(rel)s relation (%(eid)s)') % {
- 'rel': entity.tree_attribute,
- 'eid': entity.eid
- }
- return None, msg
- done.add(entity.eid)
- return done, entity
-
-
-class TreeViewMixIn(object):
- """a recursive tree view"""
- __regid__ = 'tree'
- item_vid = 'treeitem'
- __select__ = implements(ITree)
-
- def call(self, done=None, **kwargs):
- if done is None:
- done = set()
- super(TreeViewMixIn, self).call(done=done, **kwargs)
-
- def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
- done, entity = _done_init(done, self, row, col)
- if done is None:
- # entity is actually an error message
- self.w(u'<li class="badcontent">%s</li>' % entity)
- return
- self.open_item(entity)
- entity.view(vid or self.item_vid, w=self.w, **kwargs)
- relatedrset = entity.children(entities=False)
- self.wview(self.__regid__, relatedrset, 'null', done=done, **kwargs)
- self.close_item(entity)
-
- def open_item(self, entity):
- self.w(u'<li class="%s">\n' % entity.__regid__.lower())
- def close_item(self, entity):
- self.w(u'</li>\n')
-
-
-class TreePathMixIn(object):
- """a recursive path view"""
- __regid__ = 'path'
- item_vid = 'oneline'
- separator = u' > '
-
- def call(self, **kwargs):
- self.w(u'<div class="pathbar">')
- super(TreePathMixIn, self).call(**kwargs)
- self.w(u'</div>')
-
- def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
- done, entity = _done_init(done, self, row, col)
- if done is None:
- # entity is actually an error message
- self.w(u'<span class="badcontent">%s</span>' % entity)
- return
- parent = entity.parent()
- if parent:
- parent.view(self.__regid__, w=self.w, done=done)
- self.w(self.separator)
- entity.view(vid or self.item_vid, w=self.w)
-
-
-class ProgressMixIn(object):
- """provide default implementations for IProgress interface methods"""
- # This is an adapter isn't it ?
-
- @property
- def cost(self):
- return self.progress_info()['estimated']
-
- @property
- def revised_cost(self):
- return self.progress_info().get('estimatedcorrected', self.cost)
-
- @property
- def done(self):
- return self.progress_info()['done']
-
- @property
- def todo(self):
- return self.progress_info()['todo']
-
- @cached
- def progress_info(self):
- raise NotImplementedError()
-
- def finished(self):
- return not self.in_progress()
-
- def in_progress(self):
- raise NotImplementedError()
-
- def progress(self):
- try:
- return 100. * self.done / self.revised_cost
- except ZeroDivisionError:
- # total cost is 0 : if everything was estimated, task is completed
- if self.progress_info().get('notestimated'):
- return 0.
- return 100
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.mixins', DeprecationWarning, stacklevel=2)
+from cubicweb.mixins import *
--- a/common/mttransforms.py Tue Dec 08 10:40:20 2009 +0100
+++ b/common/mttransforms.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,102 +1,5 @@
-"""mime type transformation engine for cubicweb, based on mtconverter
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab import mtconverter
-
-from logilab.mtconverter.engine import TransformEngine
-from logilab.mtconverter.transform import Transform
-from logilab.mtconverter import (register_base_transforms,
- register_pil_transforms,
- register_pygments_transforms)
-
-from cubicweb.utils import UStringIO
-from cubicweb.common.uilib import rest_publish, html_publish
-
-HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
-
-# CubicWeb specific transformations
-
-class rest_to_html(Transform):
- inputs = ('text/rest', 'text/x-rst')
- output = 'text/html'
- def _convert(self, trdata):
- return rest_publish(trdata.appobject, trdata.decode())
-
-class html_to_html(Transform):
- inputs = HTML_MIMETYPES
- output = 'text/html'
- def _convert(self, trdata):
- return html_publish(trdata.appobject, trdata.data)
-
-
-# Instantiate and configure the transformation engine
-
-mtconverter.UNICODE_POLICY = 'replace'
-
-ENGINE = TransformEngine()
-ENGINE.add_transform(rest_to_html())
-ENGINE.add_transform(html_to_html())
-
-try:
- from cubicweb.ext.tal import CubicWebContext, compile_template
-except ImportError:
- HAS_TAL = False
- from cubicweb import schema
- schema.NEED_PERM_FORMATS.remove('text/cubicweb-page-template')
-
-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):
- context = CubicWebContext()
- appobject = trdata.appobject
- context.update({'self': appobject, 'rset': appobject.cw_rset,
- 'req': appobject._cw,
- '_' : appobject._cw._,
- 'user': appobject._cw.user})
- output = UStringIO()
- template = compile_template(trdata.encode(self.output_encoding))
- template.expand(context, output)
- return output.getvalue()
-
- ENGINE.add_transform(ept_to_html())
-
-if register_pil_transforms(ENGINE, verb=False):
- HAS_PIL_TRANSFORMS = True
-else:
- HAS_PIL_TRANSFORMS = False
-
-try:
- from logilab.mtconverter.transforms import pygmentstransforms
- for mt in ('text/plain',) + HTML_MIMETYPES:
- try:
- pygmentstransforms.mimetypes.remove(mt)
- except ValueError:
- continue
- register_pygments_transforms(ENGINE, verb=False)
-
- def patch_convert(cls):
- def _convert(self, trdata, origconvert=cls._convert):
- try:
- trdata.appobject._cw.add_css('pygments.css')
- except AttributeError: # session has no add_css, only http request
- pass
- return origconvert(self, trdata)
- cls._convert = _convert
- patch_convert(pygmentstransforms.PygmentsHTMLTransform)
-
- HAS_PYGMENTS_TRANSFORMS = True
-except ImportError:
- HAS_PYGMENTS_TRANSFORMS = False
-
-register_base_transforms(ENGINE, verb=False)
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.mttransforms', DeprecationWarning, stacklevel=2)
+from cubicweb.mttransforms import *
--- a/common/tags.py Tue Dec 08 10:40:20 2009 +0100
+++ b/common/tags.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,49 +1,5 @@
-"""helper classes to generate simple (X)HTML tags
-
-:organization: Logilab
-:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb.common.uilib import simple_sgml_tag, sgml_attributes
-
-class tag(object):
- def __init__(self, name, escapecontent=True):
- self.name = name
- self.escapecontent = escapecontent
-
- def __call__(self, __content=None, **attrs):
- attrs.setdefault('escapecontent', self.escapecontent)
- return simple_sgml_tag(self.name, __content, **attrs)
-
-button = tag('button')
-input = tag('input')
-textarea = tag('textarea')
-a = tag('a')
-span = tag('span')
-div = tag('div', False)
-img = tag('img')
-label = tag('label')
-option = tag('option')
-h1 = tag('h1')
-h2 = tag('h2')
-h3 = tag('h3')
-h4 = tag('h4')
-h5 = tag('h5')
-tr = tag('tr')
-th = tag('th')
-td = tag('td')
-
-def select(name, id=None, multiple=False, options=[], **attrs):
- if multiple:
- attrs['multiple'] = 'multiple'
- if id:
- attrs['id'] = id
- attrs['name'] = name
- html = [u'<select %s>' % sgml_attributes(attrs)]
- html += options
- html.append(u'</select>')
- return u'\n'.join(html)
-
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.tags', DeprecationWarning, stacklevel=2)
+from cubicweb.tags import *
--- a/common/test/data/bootstrap_cubes Tue Dec 08 10:40:20 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- a/common/test/unittest_mail.py Tue Dec 08 10:40:20 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unit tests for module cubicweb.common.mail
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-import os
-import sys
-
-from logilab.common.testlib import unittest_main
-from logilab.common.umessage import message_from_string
-
-from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.common.mail import format_mail
-
-
-def getlogin():
- """avoid usinng os.getlogin() because of strange tty / stdin problems
- (man 3 getlogin)
- Another solution would be to use $LOGNAME, $USER or $USERNAME
- """
- if sys.platform != 'win32':
- import pwd
- return pwd.getpwuid(os.getuid())[0]
- else:
- return os.environ.get('USERNAME')
-
-
-class EmailTC(CubicWebTC):
-
- def test_format_mail(self):
- self.set_option('sender-addr', 'bim@boum.fr')
- self.set_option('sender-name', 'BimBam')
-
- mail = format_mail({'name': 'oim', 'email': 'oim@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou', u'bïjour',
- config=self.config)
- self.assertLinesEquals(mail.as_string(), """\
-MIME-Version: 1.0
-Content-Type: text/plain; charset="utf-8"
-Content-Transfer-Encoding: base64
-Subject: =?utf-8?q?b=C3=AFjour?=
-From: =?utf-8?q?oim?= <oim@logilab.fr>
-Reply-to: =?utf-8?q?oim?= <oim@logilab.fr>, =?utf-8?q?BimBam?= <bim@boum.fr>
-X-CW: data
-To: test@logilab.fr
-
-dW4gcGV0aXQgY8O2dWNvdQ==
-""")
- msg = message_from_string(mail.as_string())
- self.assertEquals(msg.get('subject'), u'bïjour')
- self.assertEquals(msg.get('from'), u'oim <oim@logilab.fr>')
- self.assertEquals(msg.get('to'), u'test@logilab.fr')
- self.assertEquals(msg.get('reply-to'), u'oim <oim@logilab.fr>, BimBam <bim@boum.fr>')
- self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou')
-
-
- def test_format_mail_euro(self):
- mail = format_mail({'name': u'oîm', 'email': u'oim@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
- self.assertLinesEquals(mail.as_string(), """\
-MIME-Version: 1.0
-Content-Type: text/plain; charset="utf-8"
-Content-Transfer-Encoding: base64
-Subject: =?utf-8?b?YsOvam91ciDigqw=?=
-From: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
-Reply-to: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
-To: test@logilab.fr
-
-dW4gcGV0aXQgY8O2dWNvdSDigqw=
-""")
- msg = message_from_string(mail.as_string())
- self.assertEquals(msg.get('subject'), u'bïjour €')
- self.assertEquals(msg.get('from'), u'oîm <oim@logilab.fr>')
- self.assertEquals(msg.get('to'), u'test@logilab.fr')
- self.assertEquals(msg.get('reply-to'), u'oîm <oim@logilab.fr>')
- self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou €')
-
-
- def test_format_mail_from_reply_to(self):
- # no sender-name, sender-addr in the configuration
- self.set_option('sender-name', '')
- self.set_option('sender-addr', '')
- msg = format_mail({'name': u'', 'email': u''},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- self.assertEquals(msg.get('from'), u'')
- self.assertEquals(msg.get('reply-to'), None)
- msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
- msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
- # set sender name and address as expected
- self.set_option('sender-name', 'cubicweb-test')
- self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
- # anonymous notification: no name and no email specified
- msg = format_mail({'name': u'', 'email': u''},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'cubicweb-test <cubicweb-test@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'cubicweb-test <cubicweb-test@logilab.fr>')
- # anonymous notification: only email specified
- msg = format_mail({'email': u'tutu@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'cubicweb-test <tutu@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'cubicweb-test <tutu@logilab.fr>, cubicweb-test <cubicweb-test@logilab.fr>')
- # anonymous notification: only name specified
- msg = format_mail({'name': u'tutu'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'tutu <cubicweb-test@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'tutu <cubicweb-test@logilab.fr>')
-
-
-
-if __name__ == '__main__':
- unittest_main()
-
--- a/common/test/unittest_uilib.py Tue Dec 08 10:40:20 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unittests for cubicweb.common.uilib
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-__docformat__ = "restructuredtext en"
-
-from logilab.common.testlib import TestCase, unittest_main
-from logilab.common.tree import Node
-
-from cubicweb.common import uilib
-
-class UILIBTC(TestCase):
-
- def test_remove_tags(self):
- """make sure remove_tags remove all tags"""
- data = [
- ('<h1>Hello</h1>', 'Hello'),
- ('<h1>Hello <a href="foo/bar"><b>s</b>pam</a></h1>', 'Hello spam'),
- ('<br>Hello<img src="doh.png"/>', 'Hello'),
- ('<p></p>', ''),
- ]
- for text, expected in data:
- got = uilib.remove_html_tags(text)
- self.assertEquals(got, expected)
-
- def test_fallback_safe_cut(self):
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 4), u'ab c...')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 5), u'ab <a href="hello">cd</a>')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a>', 4), u'ab &...')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a> ef', 5), u'ab &d...')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">ìd</a>', 4), u'ab ì...')
- self.assertEquals(uilib.fallback_safe_cut(u'& <a href="hello">&d</a> ef', 4), u'& &d...')
-
- def test_lxml_safe_cut(self):
- self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 4), u'<p>aaa</p><div>a...</div>')
- self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 7), u'<p>aaa</p><div>aaad</div>...')
- self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div>', 7), u'<p>aaa</p><div>aaad</div>')
- # Missing ellipsis due to space management but we don't care
- self.assertEquals(uilib.safe_cut(u'ab <a href="hello">&d</a>', 4), u'<p>ab <a href="hello">&...</a></p>')
-
- def test_cut(self):
- """tests uilib.cut() behaviour"""
- data = [
- ('hello', 'hello'),
- ('hello world', 'hello wo...'),
- ("hell<b>O'</b> world", "hell<b>O..."),
- ]
- for text, expected in data:
- got = uilib.cut(text, 8)
- self.assertEquals(got, expected)
-
- def test_text_cut(self):
- """tests uilib.text_cut() behaviour with no text"""
- data = [('',''),
- ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
-quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
-consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
-cillum dolore eu fugiat nulla pariatur.""",
- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
-tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
-quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo \
-consequat."),
- ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam,
-quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
-consequat Duis aute irure dolor in reprehenderit in voluptate velit esse
-cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat non
-proident, sunt in culpa qui officia deserunt mollit anim id est laborum
-""",
- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
-tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam, \
-quis nostrud exercitation ullamco laboris nisi"),
- ]
- for text, expected in data:
- got = uilib.text_cut(text, 30)
- self.assertEquals(got, expected)
-
-if __name__ == '__main__':
- unittest_main()
-
--- a/common/uilib.py Tue Dec 08 10:40:20 2009 +0100
+++ b/common/uilib.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,384 +1,5 @@
-# -*- coding: utf-8 -*-
-"""user interface libraries
-
-contains some functions designed to help implementation of cubicweb user interface
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import csv
-import re
-from StringIO import StringIO
-
-from logilab.mtconverter import xml_escape, html_unescape
-
-from cubicweb.utils import ustrftime
-
-def rql_for_eid(eid):
- """return the rql query necessary to fetch entity with the given eid. This
- function should only be used to generate link with rql inside, not to give
- to cursor.execute (in which case you won't benefit from rql cache).
-
- :Parameters:
- - `eid`: the eid of the entity we should search
- :rtype: str
- :return: the rql query
- """
- return 'Any X WHERE X eid %s' % eid
-
-
-def printable_value(req, attrtype, value, props=None, displaytime=True):
- """return a displayable value (i.e. unicode string)"""
- if value is None or attrtype == 'Bytes':
- return u''
- if attrtype == 'String':
- # don't translate empty value if you don't want strange results
- if props is not None and value and props.get('internationalizable'):
- return req._(value)
-
- return value
- if attrtype == 'Date':
- return ustrftime(value, req.property_value('ui.date-format'))
- if attrtype == 'Time':
- return ustrftime(value, req.property_value('ui.time-format'))
- if attrtype == 'Datetime':
- if displaytime:
- return ustrftime(value, req.property_value('ui.datetime-format'))
- return ustrftime(value, req.property_value('ui.date-format'))
- if attrtype == 'Boolean':
- if value:
- return req._('yes')
- return req._('no')
- if attrtype == 'Float':
- value = req.property_value('ui.float-format') % value
- return unicode(value)
-
-
-# text publishing #############################################################
-
-try:
- from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611
-except ImportError:
- def rest_publish(entity, data):
- """default behaviour if docutils was not found"""
- return xml_escape(data)
-
-TAG_PROG = re.compile(r'</?.*?>', re.U)
-def remove_html_tags(text):
- """Removes HTML tags from text
-
- >>> remove_html_tags('<td>hi <a href="http://www.google.fr">world</a></td>')
- 'hi world'
- >>>
- """
- return TAG_PROG.sub('', text)
-
-
-REF_PROG = re.compile(r"<ref\s+rql=([\'\"])([^\1]*?)\1\s*>([^<]*)</ref>", re.U)
-def _subst_rql(view, obj):
- delim, rql, descr = obj.groups()
- return u'<a href="%s">%s</a>' % (view._cw.build_url(rql=rql), descr)
-
-def html_publish(view, text):
- """replace <ref rql=''> links by <a href="...">"""
- if not text:
- return u''
- return REF_PROG.sub(lambda obj, view=view:_subst_rql(view, obj), text)
-
-# fallback implementation, nicer one defined below if lxml is available
-def soup2xhtml(data, encoding):
- # normalize line break
- # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
- return u'\n'.join(data.splitlines())
-
-# fallback implementation, nicer one defined below if lxml> 2.0 is available
-def safe_cut(text, length):
- """returns a string of length <length> based on <text>, removing any html
- tags from given text if cut is necessary."""
- if text is None:
- return u''
- noenttext = html_unescape(text)
- text_nohtml = remove_html_tags(noenttext)
- # try to keep html tags if text is short enough
- if len(text_nohtml) <= length:
- return text
- # else if un-tagged text is too long, cut it
- return xml_escape(text_nohtml[:length] + u'...')
-
-fallback_safe_cut = safe_cut
-
-
-try:
- from lxml import etree
-except (ImportError, AttributeError):
- # gae environment: lxml not available
- pass
-else:
-
- def soup2xhtml(data, encoding):
- """tidy (at least try) html soup and return the result
- Note: the function considers a string with no surrounding tag as valid
- if <div>`data`</div> can be parsed by an XML parser
- """
- # normalize line break
- # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
- data = u'\n'.join(data.splitlines())
- # XXX lxml 1.1 support still needed ?
- xmltree = etree.HTML('<div>%s</div>' % data)
- # NOTE: lxml 1.1 (etch platforms) doesn't recognize
- # the encoding=unicode parameter (lxml 2.0 does), this is
- # why we specify an encoding and re-decode to unicode later
- body = etree.tostring(xmltree[0], encoding=encoding)
- # remove <body> and </body> and decode to unicode
- return body[11:-13].decode(encoding)
-
- if hasattr(etree.HTML('<div>test</div>'), 'iter'):
-
- def safe_cut(text, length):
- """returns an html document of length <length> based on <text>,
- and cut is necessary.
- """
- if text is None:
- return u''
- dom = etree.HTML(text)
- curlength = 0
- add_ellipsis = False
- for element in dom.iter():
- if curlength >= length:
- parent = element.getparent()
- parent.remove(element)
- if curlength == length and (element.text or element.tail):
- add_ellipsis = True
- else:
- if element.text is not None:
- element.text = cut(element.text, length - curlength)
- curlength += len(element.text)
- if element.tail is not None:
- if curlength < length:
- element.tail = cut(element.tail, length - curlength)
- curlength += len(element.tail)
- elif curlength == length:
- element.tail = '...'
- else:
- element.tail = ''
- text = etree.tounicode(dom[0])[6:-7] # remove wrapping <body></body>
- if add_ellipsis:
- return text + u'...'
- return text
-
-def text_cut(text, nbwords=30, gotoperiod=True):
- """from the given plain text, return a text with at least <nbwords> words,
- trying to go to the end of the current sentence.
-
- :param nbwords: the minimum number of words required
- :param gotoperiod: specifies if the function should try to go to
- the first period after the cut (i.e. finish
- the sentence if possible)
-
- Note that spaces are normalized.
- """
- if text is None:
- return u''
- words = text.split()
- text = u' '.join(words) # normalize spaces
- textlength = minlength = len(' '.join(words[:nbwords]))
- if gotoperiod:
- textlength = text.find('.', minlength) + 1
- if textlength == 0: # no period found
- textlength = minlength
- return text[:textlength]
-
-def cut(text, length):
- """returns a string of a maximum length <length> based on <text>
- (approximatively, since if text has been cut, '...' is added to the end of the string,
- resulting in a string of len <length> + 3)
- """
- if text is None:
- return u''
- if len(text) <= length:
- return text
- # else if un-tagged text is too long, cut it
- return text[:length] + u'...'
-
-
-
-# HTML generation helper functions ############################################
-
-HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
- 'img', 'area', 'input', 'col'))
-
-def sgml_attributes(attrs):
- return u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
- for attr, value in sorted(attrs.items())
- if value is not None)
-
-def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
- """generation of a simple sgml tag (eg without children tags) easier
-
- content and attri butes will be escaped
- """
- value = u'<%s' % tag
- if attrs:
- try:
- attrs['class'] = attrs.pop('klass')
- except KeyError:
- pass
- value += u' ' + sgml_attributes(attrs)
- if content:
- if escapecontent:
- content = xml_escape(unicode(content))
- value += u'>%s</%s>' % (content, tag)
- else:
- if tag in HTML4_EMPTY_TAGS:
- value += u' />'
- else:
- value += u'></%s>' % tag
- return value
-
-def tooltipize(text, tooltip, url=None):
- """make an HTML tooltip"""
- url = url or '#'
- return u'<a href="%s" title="%s">%s</a>' % (url, tooltip, text)
-
-def toggle_action(nodeid):
- """builds a HTML link that uses the js toggleVisibility function"""
- return u"javascript: toggleVisibility('%s')" % nodeid
-
-def toggle_link(nodeid, label):
- """builds a HTML link that uses the js toggleVisibility function"""
- return u'<a href="%s">%s</a>' % (toggle_action(nodeid), label)
-
-
-def ureport_as_html(layout):
- from logilab.common.ureports import HTMLWriter
- formater = HTMLWriter(True)
- stream = StringIO() #UStringIO() don't want unicode assertion
- formater.format(layout, stream)
- res = stream.getvalue()
- if isinstance(res, str):
- res = unicode(res, 'UTF8')
- return res
-
-# traceback formatting ########################################################
-
-import traceback
-
-def rest_traceback(info, exception):
- """return a ReST formated traceback"""
- res = [u'Traceback\n---------\n::\n']
- for stackentry in traceback.extract_tb(info[2]):
- res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3]))
- if stackentry[3]:
- res.append(u'\t %s' % stackentry[3].decode('utf-8', 'replace'))
- res.append(u'\n')
- try:
- res.append(u'\t Error: %s\n' % exception)
- except:
- pass
- return u'\n'.join(res)
-
-
-def html_traceback(info, exception, title='',
- encoding='ISO-8859-1', body=''):
- """ return an html formatted traceback from python exception infos.
- """
- tcbk = info[2]
- stacktb = traceback.extract_tb(tcbk)
- strings = []
- if body:
- strings.append(u'<div class="error_body">')
- # FIXME
- strings.append(body)
- strings.append(u'</div>')
- if title:
- strings.append(u'<h1 class="error">%s</h1>'% xml_escape(title))
- try:
- strings.append(u'<p class="error">%s</p>' % xml_escape(str(exception)).replace("\n","<br />"))
- except UnicodeError:
- pass
- strings.append(u'<div class="error_traceback">')
- for index, stackentry in enumerate(stacktb):
- strings.append(u'<b>File</b> <b class="file">%s</b>, <b>line</b> '
- u'<b class="line">%s</b>, <b>function</b> '
- u'<b class="function">%s</b>:<br/>'%(
- xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
- if stackentry[3]:
- string = xml_escape(stackentry[3]).decode('utf-8', 'replace')
- strings.append(u'  %s<br/>\n' % (string))
- # add locals info for each entry
- try:
- local_context = tcbk.tb_frame.f_locals
- html_info = []
- chars = 0
- for name, value in local_context.iteritems():
- value = xml_escape(repr(value))
- info = u'<span class="name">%s</span>=%s, ' % (name, value)
- line_length = len(name) + len(value)
- chars += line_length
- # 150 is the result of *years* of research ;-) (CSS might be helpful here)
- if chars > 150:
- info = u'<br/>' + info
- chars = line_length
- html_info.append(info)
- boxid = 'ctxlevel%d' % index
- strings.append(u'[%s]' % toggle_link(boxid, '+'))
- strings.append(u'<div id="%s" class="pycontext hidden">%s</div>' %
- (boxid, ''.join(html_info)))
- tcbk = tcbk.tb_next
- except Exception:
- pass # doesn't really matter if we have no context info
- strings.append(u'</div>')
- return '\n'.join(strings)
-
-# csv files / unicode support #################################################
-
-class UnicodeCSVWriter:
- """proxies calls to csv.writer.writerow to be able to deal with unicode"""
-
- def __init__(self, wfunc, encoding, **kwargs):
- self.writer = csv.writer(self, **kwargs)
- self.wfunc = wfunc
- self.encoding = encoding
-
- def write(self, data):
- self.wfunc(data)
-
- def writerow(self, row):
- csvrow = []
- for elt in row:
- if isinstance(elt, unicode):
- csvrow.append(elt.encode(self.encoding))
- else:
- csvrow.append(str(elt))
- self.writer.writerow(csvrow)
-
- def writerows(self, rows):
- for row in rows:
- self.writerow(row)
-
-
-# some decorators #############################################################
-
-class limitsize(object):
- def __init__(self, maxsize):
- self.maxsize = maxsize
-
- def __call__(self, function):
- def newfunc(*args, **kwargs):
- ret = function(*args, **kwargs)
- if isinstance(ret, basestring):
- return ret[:self.maxsize]
- return ret
- return newfunc
-
-
-def htmlescape(function):
- def newfunc(*args, **kwargs):
- ret = function(*args, **kwargs)
- assert isinstance(ret, basestring)
- return xml_escape(ret)
- return newfunc
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.uilib', DeprecationWarning, stacklevel=2)
+from cubicweb.uilib import *
--- a/cwconfig.py Tue Dec 08 10:40:20 2009 +0100
+++ b/cwconfig.py Tue Dec 08 10:58:56 2009 +0100
@@ -556,6 +556,7 @@
return vregpath
def __init__(self):
+ register_stored_procedures()
ConfigurationMixIn.__init__(self)
self.adjust_sys_path()
self.load_defaults()
@@ -967,3 +968,53 @@
# alias to get a configuration instance from an instance id
instance_configuration = CubicWebConfiguration.config_for
application_configuration = deprecated('use instance_configuration')(instance_configuration)
+
+
+_EXT_REGISTERED = False
+def register_stored_procedures():
+ from logilab.common.adbh import FunctionDescr
+ from rql.utils import register_function, iter_funcnode_variables
+
+ global _EXT_REGISTERED
+ if _EXT_REGISTERED:
+ return
+ _EXT_REGISTERED = True
+
+ class COMMA_JOIN(FunctionDescr):
+ supported_backends = ('postgres', 'sqlite',)
+ rtype = 'String'
+
+ @classmethod
+ def st_description(cls, funcnode, mainindex, tr):
+ return ', '.join(sorted(term.get_description(mainindex, tr)
+ for term in iter_funcnode_variables(funcnode)))
+
+ register_function(COMMA_JOIN) # XXX do not expose?
+
+
+ class CONCAT_STRINGS(COMMA_JOIN):
+ aggregat = True
+
+ register_function(CONCAT_STRINGS) # XXX bw compat
+
+ class GROUP_CONCAT(CONCAT_STRINGS):
+ supported_backends = ('mysql', 'postgres', 'sqlite',)
+
+ register_function(GROUP_CONCAT)
+
+
+ class LIMIT_SIZE(FunctionDescr):
+ supported_backends = ('postgres', 'sqlite',)
+ rtype = 'String'
+
+ @classmethod
+ def st_description(cls, funcnode, mainindex, tr):
+ return funcnode.children[0].get_description(mainindex, tr)
+
+ register_function(LIMIT_SIZE)
+
+
+ class TEXT_LIMIT_SIZE(LIMIT_SIZE):
+ supported_backends = ('mysql', 'postgres', 'sqlite',)
+
+ register_function(TEXT_LIMIT_SIZE)
--- a/entities/wfobjs.py Tue Dec 08 10:40:20 2009 +0100
+++ b/entities/wfobjs.py Tue Dec 08 10:58:56 2009 +0100
@@ -15,7 +15,7 @@
from cubicweb.entities import AnyEntity, fetch_config
from cubicweb.interfaces import IWorkflowable
-from cubicweb.common.mixins import MI_REL_TRIGGERS
+from cubicweb.mixins import MI_REL_TRIGGERS
class WorkflowException(Exception): pass
--- a/entity.py Tue Dec 08 10:40:20 2009 +0100
+++ b/entity.py Tue Dec 08 10:58:56 2009 +0100
@@ -25,9 +25,9 @@
from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint
from cubicweb.rqlrewrite import RQLRewriter
-from cubicweb.common.uilib import printable_value, soup2xhtml
-from cubicweb.common.mixins import MI_REL_TRIGGERS
-from cubicweb.common.mttransforms import ENGINE
+from cubicweb.uilib import printable_value, soup2xhtml
+from cubicweb.mixins import MI_REL_TRIGGERS
+from cubicweb.mttransforms import ENGINE
_marker = object()
--- a/goa/__init__.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/__init__.py Tue Dec 08 10:58:56 2009 +0100
@@ -44,7 +44,7 @@
def rql_for_eid(eid):
return 'Any X WHERE X eid "%s"' % eid
- from cubicweb.common import uilib
+ from cubicweb import uilib
uilib.rql_for_eid = rql_for_eid
def typed_eid(eid):
--- a/goa/appobjects/components.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/appobjects/components.py Tue Dec 08 10:58:56 2009 +0100
@@ -12,7 +12,7 @@
from cubicweb import typed_eid
from cubicweb.selectors import one_line_rset, match_search_state, accept
from cubicweb.schema import display_name
-from cubicweb.common.view import StartupView, EntityView
+from cubicweb.view import StartupView, EntityView
from cubicweb.web import Redirect
from cubicweb.web.views import vid_from_rset
--- a/goa/appobjects/dbmgmt.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/appobjects/dbmgmt.py Tue Dec 08 10:58:56 2009 +0100
@@ -15,7 +15,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.selectors import none_rset, match_user_groups
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
from cubicweb.web import Redirect
from cubicweb.goa.dbinit import fix_entities, init_persistent_schema, insert_versions
--- a/goa/appobjects/sessions.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/appobjects/sessions.py Tue Dec 08 10:58:56 2009 +0100
@@ -251,7 +251,7 @@
set_log_methods(ConnectionProxy, logging.getLogger('cubicweb.web.goa.session'))
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
from cubicweb.web import application
class SessionsCleaner(StartupView):
--- a/goa/goactl.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/goactl.py Tue Dec 08 10:58:56 2009 +0100
@@ -60,21 +60,20 @@
'entity.py',
'interfaces.py',
'i18n.py',
+ 'mail.py',
'migration.py',
+ 'mixins.py',
+ 'mttransforms.py',
'rqlrewrite.py',
'rset.py',
'schema.py',
'schemaviewer.py',
'selectors.py',
+ 'uilib.py',
'utils.py',
'vregistry.py',
'view.py',
- 'common/mail.py',
- 'common/mixins.py',
- 'common/mttransforms.py',
- 'common/uilib.py',
-
'ext/html4zope.py',
'ext/rest.py',
@@ -166,7 +165,7 @@
OVERRIDEN_FILES = (
('toolsutils.py', 'toolsutils.py'),
- ('mttransforms.py', 'common/mttransforms.py'),
+ ('mttransforms.py', 'mttransforms.py'),
('server__init__.py', 'server/__init__.py'),
('rqlannotation.py', 'server/rqlannotation.py'),
)
@@ -210,7 +209,6 @@
create_dir(split(target)[0])
create_symlink(join(CW_SOFTWARE_ROOT, fpath), target)
# overriden files
- create_init_file(join(appldir, 'cubicweb/common'), 'cubicweb.common')
for fpath, subfpath in OVERRIDEN_FILES:
create_symlink(join(CW_SOFTWARE_ROOT, 'goa', 'overrides', fpath),
join(appldir, 'cubicweb', subfpath))
--- a/goa/overrides/mttransforms.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/overrides/mttransforms.py Tue Dec 08 10:58:56 2009 +0100
@@ -11,7 +11,7 @@
from logilab.mtconverter.engine import TransformEngine
from logilab.mtconverter.transform import Transform
-from cubicweb.common.uilib import rest_publish, html_publish, remove_html_tags
+from cubicweb.uilib import rest_publish, html_publish, remove_html_tags
HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
# CubicWeb specific transformations
--- a/goa/test/data/views.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/test/data/views.py Tue Dec 08 10:58:56 2009 +0100
@@ -20,7 +20,7 @@
template.VariableNode.encode_output = encode_output
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
INDEX_TEMPLATE = template.Template(u'''
<h1>hellô {{ user.login }}</h1>
--- a/goa/test/unittest_editcontroller.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/test/unittest_editcontroller.py Tue Dec 08 10:58:56 2009 +0100
@@ -9,8 +9,8 @@
from urllib import unquote
-from cubicweb.common import ValidationError
-from cubicweb.common.uilib import rql_for_eid
+from cubicweb import ValidationError
+from cubicweb.uilib import rql_for_eid
from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect
--- a/goa/tools/laxctl.py Tue Dec 08 10:40:20 2009 +0100
+++ b/goa/tools/laxctl.py Tue Dec 08 10:58:56 2009 +0100
@@ -18,7 +18,7 @@
from logilab.common.clcommands import Command, register_commands, main_run
-from cubicweb.common.uilib import remove_html_tags
+from cubicweb.uilib import remove_html_tags
from cubicweb.web.views.schema import SKIP_TYPES
APPLROOT = osp.abspath(osp.join(osp.dirname(osp.abspath(__file__)), '..'))
--- a/hooks/integrity.py Tue Dec 08 10:40:20 2009 +0100
+++ b/hooks/integrity.py Tue Dec 08 10:58:56 2009 +0100
@@ -11,7 +11,7 @@
from cubicweb import ValidationError
from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
from cubicweb.selectors import entity_implements
-from cubicweb.common.uilib import soup2xhtml
+from cubicweb.uilib import soup2xhtml
from cubicweb.server import hook
# special relations that don't have to be checked for integrity, usually
--- a/interfaces.py Tue Dec 08 10:40:20 2009 +0100
+++ b/interfaces.py Tue Dec 08 10:58:56 2009 +0100
@@ -56,7 +56,7 @@
class IProgress(Interface):
"""something that has a cost, a state and a progression
- Take a look at cubicweb.common.mixins.ProgressMixIn for some
+ Take a look at cubicweb.mixins.ProgressMixIn for some
default implementations
"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail.py Tue Dec 08 10:58:56 2009 +0100
@@ -0,0 +1,272 @@
+"""Common utilies to format / semd emails.
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from base64 import b64encode, b64decode
+from itertools import repeat
+from time import time
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEText import MIMEText
+from email.MIMEImage import MIMEImage
+from email.Header import Header
+try:
+ from socket import gethostname
+except ImportError:
+ def gethostname(): # gae
+ return 'XXX'
+
+from cubicweb.view import EntityView
+from cubicweb.entity import Entity
+
+def header(ustring):
+ return Header(ustring.encode('UTF-8'), 'UTF-8')
+
+def addrheader(uaddr, uname=None):
+ # even if an email address should be ascii, encode it using utf8 since
+ # automatic tests may generate non ascii email address
+ addr = uaddr.encode('UTF-8')
+ if uname:
+ return '%s <%s>' % (header(uname).encode(), addr)
+ return addr
+
+
+def construct_message_id(appid, eid, withtimestamp=True):
+ if withtimestamp:
+ addrpart = 'eid=%s×tamp=%.10f' % (eid, time())
+ else:
+ addrpart = 'eid=%s' % eid
+ # we don't want any equal sign nor trailing newlines
+ leftpart = b64encode(addrpart, '.-').rstrip().rstrip('=')
+ return '<%s@%s.%s>' % (leftpart, appid, gethostname())
+
+
+def parse_message_id(msgid, appid):
+ if msgid[0] == '<':
+ msgid = msgid[1:]
+ if msgid[-1] == '>':
+ msgid = msgid[:-1]
+ try:
+ values, qualif = msgid.split('@')
+ padding = len(values) % 4
+ values = b64decode(str(values + '='*padding), '.-')
+ values = dict(v.split('=') for v in values.split('&'))
+ fromappid, host = qualif.split('.', 1)
+ except:
+ return None
+ if appid != fromappid or host != gethostname():
+ return None
+ return values
+
+
+def format_mail(uinfo, to_addrs, content, subject="",
+ cc_addrs=(), msgid=None, references=(), config=None):
+ """Sends an Email to 'e_addr' with content 'content', and subject 'subject'
+
+ to_addrs and cc_addrs are expected to be a list of email address without
+ name
+ """
+ assert type(content) is unicode, repr(content)
+ msg = MIMEText(content.encode('UTF-8'), 'plain', 'UTF-8')
+ # safety: keep only the first newline
+ subject = subject.splitlines()[0]
+ msg['Subject'] = header(subject)
+ if uinfo.get('email'):
+ email = uinfo['email']
+ elif config and config['sender-addr']:
+ email = unicode(config['sender-addr'])
+ else:
+ email = u''
+ if uinfo.get('name'):
+ name = uinfo['name']
+ elif config and config['sender-addr']:
+ name = unicode(config['sender-name'])
+ else:
+ name = u''
+ msg['From'] = addrheader(email, name)
+ if config and config['sender-addr'] and config['sender-addr'] != email:
+ appaddr = addrheader(config['sender-addr'], config['sender-name'])
+ msg['Reply-to'] = '%s, %s' % (msg['From'], appaddr)
+ elif email:
+ msg['Reply-to'] = msg['From']
+ if config is not None:
+ msg['X-CW'] = config.appid
+ unique_addrs = lambda addrs: sorted(set(addr for addr in addrs if addr is not None))
+ msg['To'] = ', '.join(addrheader(addr) for addr in unique_addrs(to_addrs))
+ if cc_addrs:
+ msg['Cc'] = ', '.join(addrheader(addr) for addr in unique_addrs(cc_addrs))
+ if msgid:
+ msg['Message-id'] = msgid
+ if references:
+ msg['References'] = ', '.join(references)
+ return msg
+
+
+class HtmlEmail(MIMEMultipart):
+
+ def __init__(self, subject, textcontent, htmlcontent,
+ sendermail=None, sendername=None, recipients=None, ccrecipients=None):
+ MIMEMultipart.__init__(self, 'related')
+ self['Subject'] = header(subject)
+ self.preamble = 'This is a multi-part message in MIME format.'
+ # Attach alternative text message
+ alternative = MIMEMultipart('alternative')
+ self.attach(alternative)
+ msgtext = MIMEText(textcontent.encode('UTF-8'), 'plain', 'UTF-8')
+ alternative.attach(msgtext)
+ # Attach html message
+ msghtml = MIMEText(htmlcontent.encode('UTF-8'), 'html', 'UTF-8')
+ alternative.attach(msghtml)
+ if sendermail or sendername:
+ self['From'] = addrheader(sendermail, sendername)
+ if recipients:
+ self['To'] = ', '.join(addrheader(addr) for addr in recipients if addr is not None)
+ if ccrecipients:
+ self['Cc'] = ', '.join(addrheader(addr) for addr in ccrecipients if addr is not None)
+
+ def attach_image(self, data, htmlId):
+ image = MIMEImage(data)
+ image.add_header('Content-ID', '<%s>' % htmlId)
+ self.attach(image)
+
+
+class NotificationView(EntityView):
+ """abstract view implementing the "email" API (eg to simplify sending
+ notification)
+ """
+ # XXX refactor this class to work with len(rset) > 1
+
+ msgid_timestamp = True
+
+ # this is usually the method to call
+ def render_and_send(self, **kwargs):
+ """generate and send an email message for this view"""
+ delayed = kwargs.pop('delay_to_commit', None)
+ for recipients, msg in self.render_emails(**kwargs):
+ if delayed is None:
+ self.send(recipients, msg)
+ elif delayed:
+ self.send_on_commit(recipients, msg)
+ else:
+ self.send_now(recipients, msg)
+
+ def cell_call(self, row, col=0, **kwargs):
+ self.w(self._cw._(self.content) % self.context(**kwargs))
+
+ def render_emails(self, **kwargs):
+ """generate and send emails for this view (one per recipient)"""
+ self._kwargs = kwargs
+ recipients = self.recipients()
+ if not recipients:
+ self.info('skipping %s notification, no recipients', self.__regid__)
+ return
+ if self.cw_rset is not None:
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ # if the view is using timestamp in message ids, no way to reference
+ # previous email
+ if not self.msgid_timestamp:
+ refs = [self.construct_message_id(eid)
+ for eid in entity.notification_references(self)]
+ else:
+ refs = ()
+ msgid = self.construct_message_id(entity.eid)
+ else:
+ refs = ()
+ msgid = None
+ req = self._cw
+ self.user_data = req.user_data()
+ origlang = req.lang
+ for something in recipients:
+ if isinstance(something, Entity):
+ # hi-jack self._cw to get a session for the returned user
+ self._cw = self._cw.hijack_user(something)
+ emailaddr = something.get_email()
+ else:
+ emailaddr, lang = something
+ self._cw.set_language(lang)
+ # since the same view (eg self) may be called multiple time and we
+ # need a fresh stream at each iteration, reset it explicitly
+ self.w = None
+ # XXX call render before subject to set .row/.col attributes on the
+ # view
+ try:
+ content = self.render(row=0, col=0, **kwargs)
+ subject = self.subject()
+ except SkipEmail:
+ continue
+ except Exception, ex:
+ # shouldn't make the whole transaction fail because of rendering
+ # error (unauthorized or such)
+ self.exception(str(ex))
+ continue
+ msg = format_mail(self.user_data, [emailaddr], content, subject,
+ config=self._cw.vreg.config, msgid=msgid, references=refs)
+ yield [emailaddr], msg
+ # restore language
+ req.set_language(origlang)
+
+ # recipients / email sending ###############################################
+
+ def recipients(self):
+ """return a list of either 2-uple (email, language) or user entity to
+ who this email should be sent
+ """
+ # use super_session when available, we don't want to consider security
+ # when selecting recipients_finder
+ try:
+ req = self._cw.super_session
+ except AttributeError:
+ req = self._cw
+ finder = self._cw.vreg['components'].select('recipients_finder', req,
+ rset=self.cw_rset,
+ row=self.cw_row or 0,
+ col=self.cw_col or 0)
+ return finder.recipients()
+
+ def send_now(self, recipients, msg):
+ self._cw.vreg.config.sendmails([(msg, recipients)])
+
+ def send_on_commit(self, recipients, msg):
+ raise NotImplementedError
+
+ send = send_now
+
+ # email generation helpers #################################################
+
+ def construct_message_id(self, eid):
+ return construct_message_id(self._cw.vreg.config.appid, eid, self.msgid_timestamp)
+
+ def format_field(self, attr, value):
+ return ':%(attr)s: %(value)s' % {'attr': attr, 'value': value}
+
+ def format_section(self, attr, value):
+ return '%(attr)s\n%(ul)s\n%(value)s\n' % {
+ 'attr': attr, 'ul': '-'*len(attr), 'value': value}
+
+ def subject(self):
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ subject = self._cw._(self.message)
+ etype = entity.dc_type()
+ eid = entity.eid
+ login = self.user_data['login']
+ return self._cw._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
+
+ def context(self, **kwargs):
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ for key, val in kwargs.iteritems():
+ if val and isinstance(val, unicode) and val.strip():
+ kwargs[key] = self._cw._(val)
+ kwargs.update({'user': self.user_data['login'],
+ 'eid': entity.eid,
+ 'etype': entity.dc_type(),
+ 'url': entity.absolute_url(),
+ 'title': entity.dc_long_title(),})
+ return kwargs
+
+
+class SkipEmail(Exception):
+ """raise this if you decide to skip an email during its generation"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mixins.py Tue Dec 08 10:58:56 2009 +0100
@@ -0,0 +1,315 @@
+"""mixins of entity/views organized somewhat in a graph or tree structure
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common.deprecation import deprecated
+from logilab.common.decorators import cached
+
+from cubicweb import typed_eid
+from cubicweb.selectors import implements
+from cubicweb.interfaces import IEmailable, ITree
+
+
+class TreeMixIn(object):
+ """base tree-mixin providing the tree interface
+
+ This mixin has to be inherited explicitly and configured using the
+ tree_attribute, parent_target and children_target class attribute to
+ benefit from this default implementation
+ """
+ tree_attribute = None
+ # XXX misnamed
+ parent_target = 'subject'
+ children_target = 'object'
+
+ def different_type_children(self, entities=True):
+ """return children entities of different type as this entity.
+
+ according to the `entities` parameter, return entity objects or the
+ equivalent result set
+ """
+ res = self.related(self.tree_attribute, self.children_target,
+ entities=entities)
+ if entities:
+ return [e for e in res if e.e_schema != self.e_schema]
+ return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.cw_col)
+
+ def same_type_children(self, entities=True):
+ """return children entities of the same type as this entity.
+
+ according to the `entities` parameter, return entity objects or the
+ equivalent result set
+ """
+ res = self.related(self.tree_attribute, self.children_target,
+ entities=entities)
+ if entities:
+ return [e for e in res if e.e_schema == self.e_schema]
+ return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.cw_col)
+
+ def iterchildren(self, _done=None):
+ if _done is None:
+ _done = set()
+ for child in self.children():
+ if child.eid in _done:
+ self.error('loop in %s tree', self.__regid__.lower())
+ continue
+ yield child
+ _done.add(child.eid)
+
+ def prefixiter(self, _done=None):
+ if _done is None:
+ _done = set()
+ if self.eid in _done:
+ return
+ yield self
+ _done.add(self.eid)
+ for child in self.iterchildren(_done):
+ try:
+ for entity in child.prefixiter(_done):
+ yield entity
+ except AttributeError:
+ pass
+
+ @cached
+ def path(self):
+ """returns the list of eids from the root object to this object"""
+ path = []
+ parent = self
+ while parent:
+ if parent.eid in path:
+ self.error('loop in %s tree', self.__regid__.lower())
+ break
+ path.append(parent.eid)
+ try:
+ # check we are not leaving the tree
+ if (parent.tree_attribute != self.tree_attribute or
+ parent.parent_target != self.parent_target):
+ break
+ parent = parent.parent()
+ except AttributeError:
+ break
+
+ path.reverse()
+ return path
+
+ def iterparents(self):
+ def _uptoroot(self):
+ curr = self
+ while True:
+ curr = curr.parent()
+ if curr is None:
+ break
+ yield curr
+ return _uptoroot(self)
+
+ def notification_references(self, view):
+ """used to control References field of email send on notification
+ for this entity. `view` is the notification view.
+
+ Should return a list of eids which can be used to generate message ids
+ of previously sent email
+ """
+ return self.path()[:-1]
+
+
+ ## ITree interface ########################################################
+ def parent(self):
+ """return the parent entity if any, else None (e.g. if we are on the
+ root
+ """
+ try:
+ return self.related(self.tree_attribute, self.parent_target,
+ entities=True)[0]
+ except (KeyError, IndexError):
+ return None
+
+ def children(self, entities=True, sametype=False):
+ """return children entities
+
+ according to the `entities` parameter, return entity objects or the
+ equivalent result set
+ """
+ if sametype:
+ return self.same_type_children(entities)
+ else:
+ return self.related(self.tree_attribute, self.children_target,
+ entities=entities)
+
+ def children_rql(self):
+ return self.related_rql(self.tree_attribute, self.children_target)
+
+ def is_leaf(self):
+ return len(self.children()) == 0
+
+ def is_root(self):
+ return self.parent() is None
+
+ def root(self):
+ """return the root object"""
+ return self._cw.entity_from_eid(self.path()[0])
+
+
+class EmailableMixIn(object):
+ """base mixin providing the default get_email() method used by
+ the massmailing view
+
+ NOTE: The default implementation is based on the
+ primary_email / use_email scheme
+ """
+ __implements__ = (IEmailable,)
+
+ def get_email(self):
+ if getattr(self, 'primary_email', None):
+ return self.primary_email[0].address
+ if getattr(self, 'use_email', None):
+ return self.use_email[0].address
+ return None
+
+ @classmethod
+ def allowed_massmail_keys(cls):
+ """returns a set of allowed email substitution keys
+
+ The default is to return the entity's attribute list but an
+ entity class might override this method to allow extra keys.
+ For instance, the Person class might want to return a `companyname`
+ key.
+ """
+ return set(rschema.type
+ for rschema, attrtype in cls.e_schema.attribute_definitions()
+ if attrtype.type not in ('Password', 'Bytes'))
+
+ def as_email_context(self):
+ """returns the dictionary as used by the sendmail controller to
+ build email bodies.
+
+ NOTE: the dictionary keys should match the list returned by the
+ `allowed_massmail_keys` method.
+ """
+ return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
+
+
+"""pluggable mixins system: plug classes registered in MI_REL_TRIGGERS on entity
+classes which have the relation described by the dict's key.
+
+NOTE: pluggable mixins can't override any method of the 'explicit' user classes tree
+(eg without plugged classes). This includes bases Entity and AnyEntity classes.
+"""
+MI_REL_TRIGGERS = {
+ ('primary_email', 'subject'): EmailableMixIn,
+ ('use_email', 'subject'): EmailableMixIn,
+ }
+
+
+
+def _done_init(done, view, row, col):
+ """handle an infinite recursion safety belt"""
+ if done is None:
+ done = set()
+ entity = view.cw_rset.get_entity(row, col)
+ if entity.eid in done:
+ msg = entity._cw._('loop in %(rel)s relation (%(eid)s)') % {
+ 'rel': entity.tree_attribute,
+ 'eid': entity.eid
+ }
+ return None, msg
+ done.add(entity.eid)
+ return done, entity
+
+
+class TreeViewMixIn(object):
+ """a recursive tree view"""
+ __regid__ = 'tree'
+ item_vid = 'treeitem'
+ __select__ = implements(ITree)
+
+ def call(self, done=None, **kwargs):
+ if done is None:
+ done = set()
+ super(TreeViewMixIn, self).call(done=done, **kwargs)
+
+ def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
+ done, entity = _done_init(done, self, row, col)
+ if done is None:
+ # entity is actually an error message
+ self.w(u'<li class="badcontent">%s</li>' % entity)
+ return
+ self.open_item(entity)
+ entity.view(vid or self.item_vid, w=self.w, **kwargs)
+ relatedrset = entity.children(entities=False)
+ self.wview(self.__regid__, relatedrset, 'null', done=done, **kwargs)
+ self.close_item(entity)
+
+ def open_item(self, entity):
+ self.w(u'<li class="%s">\n' % entity.__regid__.lower())
+ def close_item(self, entity):
+ self.w(u'</li>\n')
+
+
+class TreePathMixIn(object):
+ """a recursive path view"""
+ __regid__ = 'path'
+ item_vid = 'oneline'
+ separator = u' > '
+
+ def call(self, **kwargs):
+ self.w(u'<div class="pathbar">')
+ super(TreePathMixIn, self).call(**kwargs)
+ self.w(u'</div>')
+
+ def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
+ done, entity = _done_init(done, self, row, col)
+ if done is None:
+ # entity is actually an error message
+ self.w(u'<span class="badcontent">%s</span>' % entity)
+ return
+ parent = entity.parent()
+ if parent:
+ parent.view(self.__regid__, w=self.w, done=done)
+ self.w(self.separator)
+ entity.view(vid or self.item_vid, w=self.w)
+
+
+class ProgressMixIn(object):
+ """provide default implementations for IProgress interface methods"""
+ # This is an adapter isn't it ?
+
+ @property
+ def cost(self):
+ return self.progress_info()['estimated']
+
+ @property
+ def revised_cost(self):
+ return self.progress_info().get('estimatedcorrected', self.cost)
+
+ @property
+ def done(self):
+ return self.progress_info()['done']
+
+ @property
+ def todo(self):
+ return self.progress_info()['todo']
+
+ @cached
+ def progress_info(self):
+ raise NotImplementedError()
+
+ def finished(self):
+ return not self.in_progress()
+
+ def in_progress(self):
+ raise NotImplementedError()
+
+ def progress(self):
+ try:
+ return 100. * self.done / self.revised_cost
+ except ZeroDivisionError:
+ # total cost is 0 : if everything was estimated, task is completed
+ if self.progress_info().get('notestimated'):
+ return 0.
+ return 100
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mttransforms.py Tue Dec 08 10:58:56 2009 +0100
@@ -0,0 +1,102 @@
+"""mime type transformation engine for cubicweb, based on mtconverter
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab import mtconverter
+
+from logilab.mtconverter.engine import TransformEngine
+from logilab.mtconverter.transform import Transform
+from logilab.mtconverter import (register_base_transforms,
+ register_pil_transforms,
+ register_pygments_transforms)
+
+from cubicweb.utils import UStringIO
+from cubicweb.uilib import rest_publish, html_publish
+
+HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
+
+# CubicWeb specific transformations
+
+class rest_to_html(Transform):
+ inputs = ('text/rest', 'text/x-rst')
+ output = 'text/html'
+ def _convert(self, trdata):
+ return rest_publish(trdata.appobject, trdata.decode())
+
+class html_to_html(Transform):
+ inputs = HTML_MIMETYPES
+ output = 'text/html'
+ def _convert(self, trdata):
+ return html_publish(trdata.appobject, trdata.data)
+
+
+# Instantiate and configure the transformation engine
+
+mtconverter.UNICODE_POLICY = 'replace'
+
+ENGINE = TransformEngine()
+ENGINE.add_transform(rest_to_html())
+ENGINE.add_transform(html_to_html())
+
+try:
+ from cubicweb.ext.tal import CubicWebContext, compile_template
+except ImportError:
+ HAS_TAL = False
+ from cubicweb import schema
+ schema.NEED_PERM_FORMATS.remove('text/cubicweb-page-template')
+
+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):
+ context = CubicWebContext()
+ appobject = trdata.appobject
+ context.update({'self': appobject, 'rset': appobject.cw_rset,
+ 'req': appobject._cw,
+ '_' : appobject._cw._,
+ 'user': appobject._cw.user})
+ output = UStringIO()
+ template = compile_template(trdata.encode(self.output_encoding))
+ template.expand(context, output)
+ return output.getvalue()
+
+ ENGINE.add_transform(ept_to_html())
+
+if register_pil_transforms(ENGINE, verb=False):
+ HAS_PIL_TRANSFORMS = True
+else:
+ HAS_PIL_TRANSFORMS = False
+
+try:
+ from logilab.mtconverter.transforms import pygmentstransforms
+ for mt in ('text/plain',) + HTML_MIMETYPES:
+ try:
+ pygmentstransforms.mimetypes.remove(mt)
+ except ValueError:
+ continue
+ register_pygments_transforms(ENGINE, verb=False)
+
+ def patch_convert(cls):
+ def _convert(self, trdata, origconvert=cls._convert):
+ try:
+ trdata.appobject._cw.add_css('pygments.css')
+ except AttributeError: # session has no add_css, only http request
+ pass
+ return origconvert(self, trdata)
+ cls._convert = _convert
+ patch_convert(pygmentstransforms.PygmentsHTMLTransform)
+
+ HAS_PYGMENTS_TRANSFORMS = True
+except ImportError:
+ HAS_PYGMENTS_TRANSFORMS = False
+
+register_base_transforms(ENGINE, verb=False)
--- a/rset.py Tue Dec 08 10:40:20 2009 +0100
+++ b/rset.py Tue Dec 08 10:58:56 2009 +0100
@@ -257,7 +257,7 @@
"""
# try to get page boundaries from the navigation component
# XXX we should probably not have a ref to this component here (eg in
- # cubicweb.common)
+ # cubicweb)
nav = self.vreg['components'].select_or_none('navigation', self.req,
rset=self)
if nav:
--- a/server/sqlutils.py Tue Dec 08 10:40:20 2009 +0100
+++ b/server/sqlutils.py Tue Dec 08 10:58:56 2009 +0100
@@ -22,7 +22,7 @@
from cubicweb import Binary, ConfigurationError
from cubicweb.utils import todate, todatetime
-from cubicweb.common.uilib import remove_html_tags
+from cubicweb.uilib import remove_html_tags
from cubicweb.toolsutils import restrict_perms_to_user
from cubicweb.schema import PURE_VIRTUAL_RTYPES
from cubicweb.server import SQL_CONNECT_HOOKS
--- a/sobjects/notification.py Tue Dec 08 10:40:20 2009 +0100
+++ b/sobjects/notification.py Tue Dec 08 10:58:56 2009 +0100
@@ -15,7 +15,7 @@
from cubicweb.selectors import yes
from cubicweb.view import Component
-from cubicweb.common.mail import NotificationView, SkipEmail
+from cubicweb.mail import NotificationView, SkipEmail
from cubicweb.server.hook import SendMailOp
@@ -185,9 +185,9 @@
from logilab.common.deprecation import class_renamed, class_moved, deprecated
from cubicweb.hooks.notification import RenderAndSendNotificationView
-from cubicweb.common.mail import parse_message_id
+from cubicweb.mail import parse_message_id
NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
RenderAndSendNotificationView = class_moved(RenderAndSendNotificationView)
-parse_message_id = deprecated('parse_message_id is now defined in cubicweb.common.mail')(parse_message_id)
+parse_message_id = deprecated('parse_message_id is now defined in cubicweb.mail')(parse_message_id)
--- a/sobjects/supervising.py Tue Dec 08 10:40:20 2009 +0100
+++ b/sobjects/supervising.py Tue Dec 08 10:58:56 2009 +0100
@@ -12,7 +12,7 @@
from cubicweb.selectors import none_rset
from cubicweb.schema import display_name
from cubicweb.view import Component
-from cubicweb.common.mail import format_mail
+from cubicweb.mail import format_mail
from cubicweb.server.hook import SendMailOp
--- a/sobjects/test/unittest_notification.py Tue Dec 08 10:40:20 2009 +0100
+++ b/sobjects/test/unittest_notification.py Tue Dec 08 10:58:56 2009 +0100
@@ -11,7 +11,7 @@
from logilab.common.testlib import unittest_main, TestCase
from cubicweb.devtools.testlib import CubicWebTC, MAILBOX
-from cubicweb.common.mail import construct_message_id, parse_message_id
+from cubicweb.mail import construct_message_id, parse_message_id
class MessageIdTC(TestCase):
def test_base(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tags.py Tue Dec 08 10:58:56 2009 +0100
@@ -0,0 +1,49 @@
+"""helper classes to generate simple (X)HTML tags
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.uilib import simple_sgml_tag, sgml_attributes
+
+class tag(object):
+ def __init__(self, name, escapecontent=True):
+ self.name = name
+ self.escapecontent = escapecontent
+
+ def __call__(self, __content=None, **attrs):
+ attrs.setdefault('escapecontent', self.escapecontent)
+ return simple_sgml_tag(self.name, __content, **attrs)
+
+button = tag('button')
+input = tag('input')
+textarea = tag('textarea')
+a = tag('a')
+span = tag('span')
+div = tag('div', False)
+img = tag('img')
+label = tag('label')
+option = tag('option')
+h1 = tag('h1')
+h2 = tag('h2')
+h3 = tag('h3')
+h4 = tag('h4')
+h5 = tag('h5')
+tr = tag('tr')
+th = tag('th')
+td = tag('td')
+
+def select(name, id=None, multiple=False, options=[], **attrs):
+ if multiple:
+ attrs['multiple'] = 'multiple'
+ if id:
+ attrs['id'] = id
+ attrs['name'] = name
+ html = [u'<select %s>' % sgml_attributes(attrs)]
+ html += options
+ html.append(u'</select>')
+ return u'\n'.join(html)
+
--- a/test/unittest_entity.py Tue Dec 08 10:40:20 2009 +0100
+++ b/test/unittest_entity.py Tue Dec 08 10:58:56 2009 +0100
@@ -11,7 +11,7 @@
from cubicweb import Binary, Unauthorized
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.common.mttransforms import HAS_TAL
+from cubicweb.mttransforms import HAS_TAL
from cubicweb.entities import fetch_config
class EntityTC(CubicWebTC):
@@ -327,7 +327,7 @@
def test_printable_value_bytes(self):
e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python',
data_encoding=u'ascii', data_name=u'toto.py')
- from cubicweb.common import mttransforms
+ from cubicweb import mttransforms
if mttransforms.HAS_PYGMENTS_TRANSFORMS:
self.assertEquals(e.printable_value('data'),
'''<div class="highlight"><pre><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="mi">1</span>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_mail.py Tue Dec 08 10:58:56 2009 +0100
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+"""unit tests for module cubicweb.mail
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+import os
+import sys
+
+from logilab.common.testlib import unittest_main
+from logilab.common.umessage import message_from_string
+
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.mail import format_mail
+
+
+def getlogin():
+ """avoid usinng os.getlogin() because of strange tty / stdin problems
+ (man 3 getlogin)
+ Another solution would be to use $LOGNAME, $USER or $USERNAME
+ """
+ if sys.platform != 'win32':
+ import pwd
+ return pwd.getpwuid(os.getuid())[0]
+ else:
+ return os.environ.get('USERNAME')
+
+
+class EmailTC(CubicWebTC):
+
+ def test_format_mail(self):
+ self.set_option('sender-addr', 'bim@boum.fr')
+ self.set_option('sender-name', 'BimBam')
+
+ mail = format_mail({'name': 'oim', 'email': 'oim@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou', u'bïjour',
+ config=self.config)
+ self.assertLinesEquals(mail.as_string(), """\
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+Subject: =?utf-8?q?b=C3=AFjour?=
+From: =?utf-8?q?oim?= <oim@logilab.fr>
+Reply-to: =?utf-8?q?oim?= <oim@logilab.fr>, =?utf-8?q?BimBam?= <bim@boum.fr>
+X-CW: data
+To: test@logilab.fr
+
+dW4gcGV0aXQgY8O2dWNvdQ==
+""")
+ msg = message_from_string(mail.as_string())
+ self.assertEquals(msg.get('subject'), u'bïjour')
+ self.assertEquals(msg.get('from'), u'oim <oim@logilab.fr>')
+ self.assertEquals(msg.get('to'), u'test@logilab.fr')
+ self.assertEquals(msg.get('reply-to'), u'oim <oim@logilab.fr>, BimBam <bim@boum.fr>')
+ self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou')
+
+
+ def test_format_mail_euro(self):
+ mail = format_mail({'name': u'oîm', 'email': u'oim@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
+ self.assertLinesEquals(mail.as_string(), """\
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+Subject: =?utf-8?b?YsOvam91ciDigqw=?=
+From: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
+Reply-to: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
+To: test@logilab.fr
+
+dW4gcGV0aXQgY8O2dWNvdSDigqw=
+""")
+ msg = message_from_string(mail.as_string())
+ self.assertEquals(msg.get('subject'), u'bïjour €')
+ self.assertEquals(msg.get('from'), u'oîm <oim@logilab.fr>')
+ self.assertEquals(msg.get('to'), u'test@logilab.fr')
+ self.assertEquals(msg.get('reply-to'), u'oîm <oim@logilab.fr>')
+ self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou €')
+
+
+ def test_format_mail_from_reply_to(self):
+ # no sender-name, sender-addr in the configuration
+ self.set_option('sender-name', '')
+ self.set_option('sender-addr', '')
+ msg = format_mail({'name': u'', 'email': u''},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ self.assertEquals(msg.get('from'), u'')
+ self.assertEquals(msg.get('reply-to'), None)
+ msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
+ msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
+ # set sender name and address as expected
+ self.set_option('sender-name', 'cubicweb-test')
+ self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
+ # anonymous notification: no name and no email specified
+ msg = format_mail({'name': u'', 'email': u''},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'cubicweb-test <cubicweb-test@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'cubicweb-test <cubicweb-test@logilab.fr>')
+ # anonymous notification: only email specified
+ msg = format_mail({'email': u'tutu@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'cubicweb-test <tutu@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'cubicweb-test <tutu@logilab.fr>, cubicweb-test <cubicweb-test@logilab.fr>')
+ # anonymous notification: only name specified
+ msg = format_mail({'name': u'tutu'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'tutu <cubicweb-test@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'tutu <cubicweb-test@logilab.fr>')
+
+
+
+if __name__ == '__main__':
+ unittest_main()
+
--- a/test/unittest_migration.py Tue Dec 08 10:40:20 2009 +0100
+++ b/test/unittest_migration.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,4 +1,4 @@
-"""cubicweb.common.migration unit tests
+"""cubicweb.migration unit tests
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
--- a/test/unittest_rset.py Tue Dec 08 10:40:20 2009 +0100
+++ b/test/unittest_rset.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,5 +1,5 @@
# coding: utf-8
-"""unit tests for module cubicweb.common.utils
+"""unit tests for module cubicweb.utils
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_uilib.py Tue Dec 08 10:58:56 2009 +0100
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""unittests for cubicweb.uilib
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+__docformat__ = "restructuredtext en"
+
+from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.tree import Node
+
+from cubicweb import uilib
+
+class UILIBTC(TestCase):
+
+ def test_remove_tags(self):
+ """make sure remove_tags remove all tags"""
+ data = [
+ ('<h1>Hello</h1>', 'Hello'),
+ ('<h1>Hello <a href="foo/bar"><b>s</b>pam</a></h1>', 'Hello spam'),
+ ('<br>Hello<img src="doh.png"/>', 'Hello'),
+ ('<p></p>', ''),
+ ]
+ for text, expected in data:
+ got = uilib.remove_html_tags(text)
+ self.assertEquals(got, expected)
+
+ def test_fallback_safe_cut(self):
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 4), u'ab c...')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 5), u'ab <a href="hello">cd</a>')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a>', 4), u'ab &...')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a> ef', 5), u'ab &d...')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">ìd</a>', 4), u'ab ì...')
+ self.assertEquals(uilib.fallback_safe_cut(u'& <a href="hello">&d</a> ef', 4), u'& &d...')
+
+ def test_lxml_safe_cut(self):
+ self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 4), u'<p>aaa</p><div>a...</div>')
+ self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 7), u'<p>aaa</p><div>aaad</div>...')
+ self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div>', 7), u'<p>aaa</p><div>aaad</div>')
+ # Missing ellipsis due to space management but we don't care
+ self.assertEquals(uilib.safe_cut(u'ab <a href="hello">&d</a>', 4), u'<p>ab <a href="hello">&...</a></p>')
+
+ def test_cut(self):
+ """tests uilib.cut() behaviour"""
+ data = [
+ ('hello', 'hello'),
+ ('hello world', 'hello wo...'),
+ ("hell<b>O'</b> world", "hell<b>O..."),
+ ]
+ for text, expected in data:
+ got = uilib.cut(text, 8)
+ self.assertEquals(got, expected)
+
+ def test_text_cut(self):
+ """tests uilib.text_cut() behaviour with no text"""
+ data = [('',''),
+ ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur.""",
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo \
+consequat."),
+ ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum
+""",
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
+tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam, \
+quis nostrud exercitation ullamco laboris nisi"),
+ ]
+ for text, expected in data:
+ got = uilib.text_cut(text, 30)
+ self.assertEquals(got, expected)
+
+if __name__ == '__main__':
+ unittest_main()
+
--- a/test/unittest_utils.py Tue Dec 08 10:40:20 2009 +0100
+++ b/test/unittest_utils.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,4 +1,4 @@
-"""unit tests for module cubicweb.common.utils
+"""unit tests for module cubicweb.utils
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/uilib.py Tue Dec 08 10:58:56 2009 +0100
@@ -0,0 +1,384 @@
+# -*- coding: utf-8 -*-
+"""user interface libraries
+
+contains some functions designed to help implementation of cubicweb user interface
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+import csv
+import re
+from StringIO import StringIO
+
+from logilab.mtconverter import xml_escape, html_unescape
+
+from cubicweb.utils import ustrftime
+
+def rql_for_eid(eid):
+ """return the rql query necessary to fetch entity with the given eid. This
+ function should only be used to generate link with rql inside, not to give
+ to cursor.execute (in which case you won't benefit from rql cache).
+
+ :Parameters:
+ - `eid`: the eid of the entity we should search
+ :rtype: str
+ :return: the rql query
+ """
+ return 'Any X WHERE X eid %s' % eid
+
+
+def printable_value(req, attrtype, value, props=None, displaytime=True):
+ """return a displayable value (i.e. unicode string)"""
+ if value is None or attrtype == 'Bytes':
+ return u''
+ if attrtype == 'String':
+ # don't translate empty value if you don't want strange results
+ if props is not None and value and props.get('internationalizable'):
+ return req._(value)
+
+ return value
+ if attrtype == 'Date':
+ return ustrftime(value, req.property_value('ui.date-format'))
+ if attrtype == 'Time':
+ return ustrftime(value, req.property_value('ui.time-format'))
+ if attrtype == 'Datetime':
+ if displaytime:
+ return ustrftime(value, req.property_value('ui.datetime-format'))
+ return ustrftime(value, req.property_value('ui.date-format'))
+ if attrtype == 'Boolean':
+ if value:
+ return req._('yes')
+ return req._('no')
+ if attrtype == 'Float':
+ value = req.property_value('ui.float-format') % value
+ return unicode(value)
+
+
+# text publishing #############################################################
+
+try:
+ from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611
+except ImportError:
+ def rest_publish(entity, data):
+ """default behaviour if docutils was not found"""
+ return xml_escape(data)
+
+TAG_PROG = re.compile(r'</?.*?>', re.U)
+def remove_html_tags(text):
+ """Removes HTML tags from text
+
+ >>> remove_html_tags('<td>hi <a href="http://www.google.fr">world</a></td>')
+ 'hi world'
+ >>>
+ """
+ return TAG_PROG.sub('', text)
+
+
+REF_PROG = re.compile(r"<ref\s+rql=([\'\"])([^\1]*?)\1\s*>([^<]*)</ref>", re.U)
+def _subst_rql(view, obj):
+ delim, rql, descr = obj.groups()
+ return u'<a href="%s">%s</a>' % (view._cw.build_url(rql=rql), descr)
+
+def html_publish(view, text):
+ """replace <ref rql=''> links by <a href="...">"""
+ if not text:
+ return u''
+ return REF_PROG.sub(lambda obj, view=view:_subst_rql(view, obj), text)
+
+# fallback implementation, nicer one defined below if lxml is available
+def soup2xhtml(data, encoding):
+ # normalize line break
+ # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+ return u'\n'.join(data.splitlines())
+
+# fallback implementation, nicer one defined below if lxml> 2.0 is available
+def safe_cut(text, length):
+ """returns a string of length <length> based on <text>, removing any html
+ tags from given text if cut is necessary."""
+ if text is None:
+ return u''
+ noenttext = html_unescape(text)
+ text_nohtml = remove_html_tags(noenttext)
+ # try to keep html tags if text is short enough
+ if len(text_nohtml) <= length:
+ return text
+ # else if un-tagged text is too long, cut it
+ return xml_escape(text_nohtml[:length] + u'...')
+
+fallback_safe_cut = safe_cut
+
+
+try:
+ from lxml import etree
+except (ImportError, AttributeError):
+ # gae environment: lxml not available
+ pass
+else:
+
+ def soup2xhtml(data, encoding):
+ """tidy (at least try) html soup and return the result
+ Note: the function considers a string with no surrounding tag as valid
+ if <div>`data`</div> can be parsed by an XML parser
+ """
+ # normalize line break
+ # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+ data = u'\n'.join(data.splitlines())
+ # XXX lxml 1.1 support still needed ?
+ xmltree = etree.HTML('<div>%s</div>' % data)
+ # NOTE: lxml 1.1 (etch platforms) doesn't recognize
+ # the encoding=unicode parameter (lxml 2.0 does), this is
+ # why we specify an encoding and re-decode to unicode later
+ body = etree.tostring(xmltree[0], encoding=encoding)
+ # remove <body> and </body> and decode to unicode
+ return body[11:-13].decode(encoding)
+
+ if hasattr(etree.HTML('<div>test</div>'), 'iter'):
+
+ def safe_cut(text, length):
+ """returns an html document of length <length> based on <text>,
+ and cut is necessary.
+ """
+ if text is None:
+ return u''
+ dom = etree.HTML(text)
+ curlength = 0
+ add_ellipsis = False
+ for element in dom.iter():
+ if curlength >= length:
+ parent = element.getparent()
+ parent.remove(element)
+ if curlength == length and (element.text or element.tail):
+ add_ellipsis = True
+ else:
+ if element.text is not None:
+ element.text = cut(element.text, length - curlength)
+ curlength += len(element.text)
+ if element.tail is not None:
+ if curlength < length:
+ element.tail = cut(element.tail, length - curlength)
+ curlength += len(element.tail)
+ elif curlength == length:
+ element.tail = '...'
+ else:
+ element.tail = ''
+ text = etree.tounicode(dom[0])[6:-7] # remove wrapping <body></body>
+ if add_ellipsis:
+ return text + u'...'
+ return text
+
+def text_cut(text, nbwords=30, gotoperiod=True):
+ """from the given plain text, return a text with at least <nbwords> words,
+ trying to go to the end of the current sentence.
+
+ :param nbwords: the minimum number of words required
+ :param gotoperiod: specifies if the function should try to go to
+ the first period after the cut (i.e. finish
+ the sentence if possible)
+
+ Note that spaces are normalized.
+ """
+ if text is None:
+ return u''
+ words = text.split()
+ text = u' '.join(words) # normalize spaces
+ textlength = minlength = len(' '.join(words[:nbwords]))
+ if gotoperiod:
+ textlength = text.find('.', minlength) + 1
+ if textlength == 0: # no period found
+ textlength = minlength
+ return text[:textlength]
+
+def cut(text, length):
+ """returns a string of a maximum length <length> based on <text>
+ (approximatively, since if text has been cut, '...' is added to the end of the string,
+ resulting in a string of len <length> + 3)
+ """
+ if text is None:
+ return u''
+ if len(text) <= length:
+ return text
+ # else if un-tagged text is too long, cut it
+ return text[:length] + u'...'
+
+
+
+# HTML generation helper functions ############################################
+
+HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
+ 'img', 'area', 'input', 'col'))
+
+def sgml_attributes(attrs):
+ return u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
+ for attr, value in sorted(attrs.items())
+ if value is not None)
+
+def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
+ """generation of a simple sgml tag (eg without children tags) easier
+
+ content and attri butes will be escaped
+ """
+ value = u'<%s' % tag
+ if attrs:
+ try:
+ attrs['class'] = attrs.pop('klass')
+ except KeyError:
+ pass
+ value += u' ' + sgml_attributes(attrs)
+ if content:
+ if escapecontent:
+ content = xml_escape(unicode(content))
+ value += u'>%s</%s>' % (content, tag)
+ else:
+ if tag in HTML4_EMPTY_TAGS:
+ value += u' />'
+ else:
+ value += u'></%s>' % tag
+ return value
+
+def tooltipize(text, tooltip, url=None):
+ """make an HTML tooltip"""
+ url = url or '#'
+ return u'<a href="%s" title="%s">%s</a>' % (url, tooltip, text)
+
+def toggle_action(nodeid):
+ """builds a HTML link that uses the js toggleVisibility function"""
+ return u"javascript: toggleVisibility('%s')" % nodeid
+
+def toggle_link(nodeid, label):
+ """builds a HTML link that uses the js toggleVisibility function"""
+ return u'<a href="%s">%s</a>' % (toggle_action(nodeid), label)
+
+
+def ureport_as_html(layout):
+ from logilab.common.ureports import HTMLWriter
+ formater = HTMLWriter(True)
+ stream = StringIO() #UStringIO() don't want unicode assertion
+ formater.format(layout, stream)
+ res = stream.getvalue()
+ if isinstance(res, str):
+ res = unicode(res, 'UTF8')
+ return res
+
+# traceback formatting ########################################################
+
+import traceback
+
+def rest_traceback(info, exception):
+ """return a ReST formated traceback"""
+ res = [u'Traceback\n---------\n::\n']
+ for stackentry in traceback.extract_tb(info[2]):
+ res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3]))
+ if stackentry[3]:
+ res.append(u'\t %s' % stackentry[3].decode('utf-8', 'replace'))
+ res.append(u'\n')
+ try:
+ res.append(u'\t Error: %s\n' % exception)
+ except:
+ pass
+ return u'\n'.join(res)
+
+
+def html_traceback(info, exception, title='',
+ encoding='ISO-8859-1', body=''):
+ """ return an html formatted traceback from python exception infos.
+ """
+ tcbk = info[2]
+ stacktb = traceback.extract_tb(tcbk)
+ strings = []
+ if body:
+ strings.append(u'<div class="error_body">')
+ # FIXME
+ strings.append(body)
+ strings.append(u'</div>')
+ if title:
+ strings.append(u'<h1 class="error">%s</h1>'% xml_escape(title))
+ try:
+ strings.append(u'<p class="error">%s</p>' % xml_escape(str(exception)).replace("\n","<br />"))
+ except UnicodeError:
+ pass
+ strings.append(u'<div class="error_traceback">')
+ for index, stackentry in enumerate(stacktb):
+ strings.append(u'<b>File</b> <b class="file">%s</b>, <b>line</b> '
+ u'<b class="line">%s</b>, <b>function</b> '
+ u'<b class="function">%s</b>:<br/>'%(
+ xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
+ if stackentry[3]:
+ string = xml_escape(stackentry[3]).decode('utf-8', 'replace')
+ strings.append(u'  %s<br/>\n' % (string))
+ # add locals info for each entry
+ try:
+ local_context = tcbk.tb_frame.f_locals
+ html_info = []
+ chars = 0
+ for name, value in local_context.iteritems():
+ value = xml_escape(repr(value))
+ info = u'<span class="name">%s</span>=%s, ' % (name, value)
+ line_length = len(name) + len(value)
+ chars += line_length
+ # 150 is the result of *years* of research ;-) (CSS might be helpful here)
+ if chars > 150:
+ info = u'<br/>' + info
+ chars = line_length
+ html_info.append(info)
+ boxid = 'ctxlevel%d' % index
+ strings.append(u'[%s]' % toggle_link(boxid, '+'))
+ strings.append(u'<div id="%s" class="pycontext hidden">%s</div>' %
+ (boxid, ''.join(html_info)))
+ tcbk = tcbk.tb_next
+ except Exception:
+ pass # doesn't really matter if we have no context info
+ strings.append(u'</div>')
+ return '\n'.join(strings)
+
+# csv files / unicode support #################################################
+
+class UnicodeCSVWriter:
+ """proxies calls to csv.writer.writerow to be able to deal with unicode"""
+
+ def __init__(self, wfunc, encoding, **kwargs):
+ self.writer = csv.writer(self, **kwargs)
+ self.wfunc = wfunc
+ self.encoding = encoding
+
+ def write(self, data):
+ self.wfunc(data)
+
+ def writerow(self, row):
+ csvrow = []
+ for elt in row:
+ if isinstance(elt, unicode):
+ csvrow.append(elt.encode(self.encoding))
+ else:
+ csvrow.append(str(elt))
+ self.writer.writerow(csvrow)
+
+ def writerows(self, rows):
+ for row in rows:
+ self.writerow(row)
+
+
+# some decorators #############################################################
+
+class limitsize(object):
+ def __init__(self, maxsize):
+ self.maxsize = maxsize
+
+ def __call__(self, function):
+ def newfunc(*args, **kwargs):
+ ret = function(*args, **kwargs)
+ if isinstance(ret, basestring):
+ return ret[:self.maxsize]
+ return ret
+ return newfunc
+
+
+def htmlescape(function):
+ def newfunc(*args, **kwargs):
+ ret = function(*args, **kwargs)
+ assert isinstance(ret, basestring)
+ return xml_escape(ret)
+ return newfunc
--- a/web/form.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/form.py Tue Dec 08 10:58:56 2009 +0100
@@ -11,7 +11,7 @@
from cubicweb.appobject import AppObject
from cubicweb.view import NOINDEX, NOFOLLOW
-from cubicweb.common import tags
+from cubicweb import tags
from cubicweb.web import stdmsgs, httpcache, formfields
--- a/web/formfields.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/formfields.py Tue Dec 08 10:58:56 2009 +0100
@@ -15,7 +15,7 @@
FormatConstraint)
from cubicweb.utils import ustrftime
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
from cubicweb.web import INTERNAL_FIELD_VALUE
from cubicweb.web.formwidgets import (
HiddenInput, TextInput, FileInput, PasswordInput, TextArea, FCKEditor,
--- a/web/formwidgets.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/formwidgets.py Tue Dec 08 10:58:56 2009 +0100
@@ -10,7 +10,7 @@
from datetime import date
from warnings import warn
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
from logilab.mtconverter import xml_escape
@@ -334,7 +334,7 @@
@classmethod
def add_localized_infos(cls, req):
"""inserts JS variables defining localized months and days"""
- # import here to avoid dependancy from cubicweb-common to simplejson
+ # import here to avoid dependancy from cubicweb to simplejson
_ = req._
monthnames = [_(mname) for mname in cls.monthnames]
daynames = [_(dname) for dname in cls.daynames]
--- a/web/htmlwidgets.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/htmlwidgets.py Tue Dec 08 10:58:56 2009 +0100
@@ -1,6 +1,6 @@
"""html widgets
-those are in cubicweb.common since we need to know available widgets at schema
+those are in cubicweb since we need to know available widgets at schema
serialization time
:organization: Logilab
@@ -12,7 +12,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.utils import UStringIO
-from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
+from cubicweb.uilib import toggle_action, limitsize, htmlescape
from cubicweb.web import jsonize
# XXX HTMLWidgets should have access to req (for datadir / static urls,
--- a/web/request.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/request.py Tue Dec 08 10:58:56 2009 +0100
@@ -25,8 +25,8 @@
from logilab.mtconverter import xml_escape
from cubicweb.dbapi import DBAPIRequest
-from cubicweb.common.mail import header
-from cubicweb.common.uilib import remove_html_tags
+from cubicweb.mail import header
+from cubicweb.uilib import remove_html_tags
from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
--- a/web/test/unittest_views_basecontrollers.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/test/unittest_views_basecontrollers.py Tue Dec 08 10:58:56 2009 +0100
@@ -12,7 +12,7 @@
from cubicweb import Binary, NoSelectableObject, ValidationError
from cubicweb.view import STRICT_DOCTYPE
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.common.uilib import rql_for_eid
+from cubicweb.uilib import rql_for_eid
from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError
from cubicweb.entities.authobjs import CWUser
--- a/web/views/basecomponents.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/basecomponents.py Tue Dec 08 10:58:56 2009 +0100
@@ -17,7 +17,7 @@
from cubicweb.selectors import yes, two_etypes_rset, match_form_params
from cubicweb.schema import display_name
-from cubicweb.common.uilib import toggle_action
+from cubicweb.uilib import toggle_action
from cubicweb.web import component
from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
BoxLink)
--- a/web/views/basecontrollers.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/basecontrollers.py Tue Dec 08 10:58:56 2009 +0100
@@ -19,7 +19,7 @@
from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
from cubicweb.utils import strptime, CubicWebJsonEncoder
from cubicweb.selectors import yes, match_user_groups
-from cubicweb.common.mail import format_mail
+from cubicweb.mail import format_mail
from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps
from cubicweb.web.controller import Controller
from cubicweb.web.views import vid_from_rset
--- a/web/views/baseviews.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/baseviews.py Tue Dec 08 10:58:56 2009 +0100
@@ -25,7 +25,7 @@
from cubicweb.selectors import yes, empty_rset, one_etype_rset
from cubicweb.schema import display_name
from cubicweb.view import EntityView, AnyRsetView, View
-from cubicweb.common.uilib import cut, printable_value
+from cubicweb.uilib import cut, printable_value
class NullView(AnyRsetView):
--- a/web/views/csvexport.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/csvexport.py Tue Dec 08 10:58:56 2009 +0100
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
from cubicweb.schema import display_name
-from cubicweb.common.uilib import UnicodeCSVWriter
+from cubicweb.uilib import UnicodeCSVWriter
from cubicweb.view import EntityView, AnyRsetView
class CSVMixIn(object):
--- a/web/views/editforms.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/editforms.py Tue Dec 08 10:58:56 2009 +0100
@@ -20,7 +20,7 @@
from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
specified_etype_implements, yes)
from cubicweb.view import EntityView
-from cubicweb.common import tags
+from cubicweb import tags
from cubicweb.web import stdmsgs, eid_param
from cubicweb.web import uicfg
from cubicweb.web.form import FormViewMixIn, FieldNotFound
--- a/web/views/editviews.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/editviews.py Tue Dec 08 10:58:56 2009 +0100
@@ -17,7 +17,7 @@
from cubicweb.view import EntityView
from cubicweb.selectors import (one_line_rset, non_final_entity,
match_search_state, match_form_params)
-from cubicweb.common.uilib import cut
+from cubicweb.uilib import cut
from cubicweb.web.views import linksearch_select_url
from cubicweb.web.views.editforms import relation_id
from cubicweb.web.views.baseviews import FinalView
--- a/web/views/emailaddress.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/emailaddress.py Tue Dec 08 10:58:56 2009 +0100
@@ -11,7 +11,7 @@
from cubicweb.schema import display_name
from cubicweb.selectors import implements
-from cubicweb.common import Unauthorized
+from cubicweb import Unauthorized
from cubicweb.web.views import baseviews, primary
class EmailAddressPrimaryView(primary.PrimaryView):
--- a/web/views/embedding.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/embedding.py Tue Dec 08 10:58:56 2009 +0100
@@ -20,7 +20,7 @@
match_search_state, implements)
from cubicweb.interfaces import IEmbedable
from cubicweb.view import NOINDEX, NOFOLLOW
-from cubicweb.common.uilib import soup2xhtml
+from cubicweb.uilib import soup2xhtml
from cubicweb.web.controller import Controller
from cubicweb.web.action import Action
from cubicweb.web.views import basetemplates
--- a/web/views/formrenderers.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/formrenderers.py Tue Dec 08 10:58:56 2009 +0100
@@ -14,7 +14,7 @@
from simplejson import dumps
-from cubicweb.common import tags
+from cubicweb import tags
from cubicweb.appobject import AppObject
from cubicweb.selectors import entity_implements, yes
from cubicweb.web import eid_param, formwidgets as fwdgs
--- a/web/views/ibreadcrumbs.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/ibreadcrumbs.py Tue Dec 08 10:58:56 2009 +0100
@@ -16,7 +16,7 @@
from cubicweb.view import EntityView, Component
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
from cubicweb.entity import Entity
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
class BreadCrumbEntityVComponent(Component):
--- a/web/views/idownloadable.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/idownloadable.py Tue Dec 08 10:58:56 2009 +0100
@@ -14,7 +14,7 @@
from cubicweb.selectors import (one_line_rset, score_entity,
implements, match_context_prop)
from cubicweb.interfaces import IDownloadable
-from cubicweb.common.mttransforms import ENGINE
+from cubicweb.mttransforms import ENGINE
from cubicweb.web.box import EntityBoxTemplate
from cubicweb.web.views import primary, baseviews
--- a/web/views/management.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/management.py Tue Dec 08 10:58:56 2009 +0100
@@ -13,7 +13,7 @@
from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView
-from cubicweb.common.uilib import html_traceback, rest_traceback
+from cubicweb.uilib import html_traceback, rest_traceback
from cubicweb.web import formwidgets as wdgs
from cubicweb.web.formfields import guess_field
--- a/web/views/navigation.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/navigation.py Tue Dec 08 10:58:56 2009 +0100
@@ -17,7 +17,7 @@
from cubicweb.selectors import (paginated_rset, sorted_rset,
primary_view, match_context_prop,
one_line_rset, implements)
-from cubicweb.common.uilib import cut
+from cubicweb.uilib import cut
from cubicweb.web.component import EntityVComponent, NavigationComponent
--- a/web/views/schema.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/schema.py Tue Dec 08 10:58:56 2009 +0100
@@ -17,7 +17,7 @@
from cubicweb.schema import META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES
from cubicweb.schemaviewer import SchemaViewer
from cubicweb.view import EntityView, StartupView
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
from cubicweb.web import action, facet, uicfg
from cubicweb.web.views import TmpFileViewMixin
from cubicweb.web.views import primary, baseviews, tabs, management
--- a/web/views/tableview.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/tableview.py Tue Dec 08 10:58:56 2009 +0100
@@ -15,8 +15,8 @@
from cubicweb.selectors import nonempty_rset, match_form_params
from cubicweb.utils import make_uid
from cubicweb.view import EntityView, AnyRsetView
-from cubicweb.common import tags
-from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
+from cubicweb import tags
+from cubicweb.uilib import toggle_action, limitsize, htmlescape
from cubicweb.web import jsonize
from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
PopupBoxMenu, BoxLink)
--- a/web/views/tabs.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/tabs.py Tue Dec 08 10:58:56 2009 +0100
@@ -13,7 +13,7 @@
from cubicweb import NoSelectableObject, role
from cubicweb.selectors import partial_has_related_entities
from cubicweb.view import EntityView
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
from cubicweb.utils import make_uid
from cubicweb.web.views import primary
--- a/web/views/urlpublishing.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/urlpublishing.py Tue Dec 08 10:58:56 2009 +0100
@@ -75,7 +75,7 @@
:type path: str
:param path: the path of the resource to publish
- :rtype: tuple(str, `cubicweb.common.utils.ResultSet` or None)
+ :rtype: tuple(str, `cubicweb.utils.ResultSet` or None)
:return: the publishing method identifier and an optional result set
:raise NotFound: if no handler is able to decode the given path
--- a/web/views/wdoc.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/wdoc.py Tue Dec 08 10:58:56 2009 +0100
@@ -18,7 +18,7 @@
from cubicweb.selectors import match_form_params, yes
from cubicweb.view import StartupView
from cubicweb.utils import strptime, todate
-from cubicweb.common.uilib import rest_publish
+from cubicweb.uilib import rest_publish
from cubicweb.web import NotFound, action
_ = unicode
--- a/web/views/xmlrss.py Tue Dec 08 10:40:20 2009 +0100
+++ b/web/views/xmlrss.py Tue Dec 08 10:58:56 2009 +0100
@@ -14,7 +14,7 @@
from cubicweb.selectors import non_final_entity, one_line_rset, appobject_selectable
from cubicweb.view import EntityView, AnyRsetView, Component
-from cubicweb.common.uilib import simple_sgml_tag
+from cubicweb.uilib import simple_sgml_tag
from cubicweb.web import httpcache, box