backport 3.5 branch
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 31 Aug 2009 19:09:54 +0200
changeset 3072 6fb42c53f6df
parent 3035 2e4a381ea5b7 (current diff)
parent 3071 6ad4e6d4df86 (diff)
child 3083 3084bc9ccc64
backport 3.5 branch
cwvreg.py
entity.py
hooks/notification.py
hooks/workflow.py
server/repository.py
sobjects/notification.py
vregistry.py
web/uicfg.py
web/views/basecomponents.py
web/views/basetemplates.py
web/views/editforms.py
web/views/idownloadable.py
web/views/primary.py
--- a/.hgtags	Thu Aug 27 13:02:45 2009 +0200
+++ b/.hgtags	Mon Aug 31 19:09:54 2009 +0200
@@ -60,3 +60,5 @@
 e244a0fd7d719c25f4267470342ff8334b2dc8b3 cubicweb-debian-version-3.4.4-1
 3a65f9b3367c7297dc540a53f84e6507cb309892 cubicweb-version-3.4.5
 7fd294cbf6ff3cf34475cc50e972f650a34ae6e8 cubicweb-debian-version-3.4.5-1
+921fdbf8b3038dc27a2ec5398a0fbcbc5b9ba4be cubicweb-version-3.4.6
+52dba800ca4d4b82c47f3befb824bd91ef015368 cubicweb-debian-version-3.4.6-1
--- a/_exceptions.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/_exceptions.py	Mon Aug 31 19:09:54 2009 +0200
@@ -65,9 +65,8 @@
     """no source support an entity type"""
     msg = 'No source supports %r entity\'s type'
 
-class RTypeNotSupportedBySources(RepositoryError, InternalError):
-    """no source support a relation type"""
-    msg = 'No source supports %r relation\'s type'
+class MultiSourcesError(RepositoryError, InternalError):
+    """usually due to bad multisources configuration or rql query"""
 
 
 # security exceptions #########################################################
--- a/common/mail.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/common/mail.py	Mon Aug 31 19:09:54 2009 +0200
@@ -141,60 +141,28 @@
 
     msgid_timestamp = True
 
-    def user_login(self):
-        try:
-            # if req is actually a session (we are on the server side), and we
-            # have to prevent nested internal session
-            return self.req.actual_session().user.login
-        except AttributeError:
-            return self.req.user.login
-
-    def recipients(self):
-        finder = self.vreg['components'].select('recipients_finder', self.req,
-                                                rset=self.rset,
-                                                row=self.row or 0,
-                                                col=self.col or 0)
-        return finder.recipients()
-
-    def subject(self):
-        entity = self.entity(self.row or 0, self.col or 0)
-        subject = self.req._(self.message)
-        etype = entity.dc_type()
-        eid = entity.eid
-        login = self.user_login()
-        return self.req._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
-
-    def context(self, **kwargs):
-        entity = self.entity(self.row or 0, self.col or 0)
-        for key, val in kwargs.iteritems():
-            if val and isinstance(val, unicode) and val.strip():
-               kwargs[key] = self.req._(val)
-        kwargs.update({'user': self.user_login(),
-                       'eid': entity.eid,
-                       'etype': entity.dc_type(),
-                       'url': entity.absolute_url(),
-                       'title': entity.dc_long_title(),})
-        return kwargs
+    # 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.req._(self.content) % self.context(**kwargs))
 
-    def construct_message_id(self, eid):
-        return construct_message_id(self.config.appid, eid, self.msgid_timestamp)
-
     def render_emails(self, **kwargs):
-        """generate and send an email message for this view"""
+        """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.id)
             return
-        if not isinstance(recipients[0], tuple):
-            from warnings import warn
-            warn('recipients should now return a list of 2-uple (email, language)',
-                 DeprecationWarning, stacklevel=1)
-            lang = self.vreg.property_value('ui.language')
-            recipients = zip(recipients, repeat(lang))
         if self.rset is not None:
             entity = self.entity(self.row or 0, self.col or 0)
             # if the view is using timestamp in message ids, no way to reference
@@ -225,16 +193,17 @@
         # restore language
         self.req.set_language(origlang)
 
-    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)
+    # recipients / email sending ###############################################
+
+    def recipients(self):
+        """return a list of 2-uple (email, language) to who this email should be
+        sent
+        """
+        finder = self.vreg['components'].select('recipients_finder', self.req,
+                                                rset=self.rset,
+                                                row=self.row or 0,
+                                                col=self.col or 0)
+        return finder.recipients()
 
     def send_now(self, recipients, msg):
         self.config.sendmails([(msg, recipients)])
@@ -243,3 +212,43 @@
         raise NotImplementedError
 
     send = send_now
+
+    # email generation helpers #################################################
+
+    def construct_message_id(self, eid):
+        return construct_message_id(self.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.entity(self.row or 0, self.col or 0)
+        subject = self.req._(self.message)
+        etype = entity.dc_type()
+        eid = entity.eid
+        login = self.user_login()
+        return self.req._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
+
+    def context(self, **kwargs):
+        entity = self.entity(self.row or 0, self.col or 0)
+        for key, val in kwargs.iteritems():
+            if val and isinstance(val, unicode) and val.strip():
+               kwargs[key] = self.req._(val)
+        kwargs.update({'user': self.user_login(),
+                       'eid': entity.eid,
+                       'etype': entity.dc_type(),
+                       'url': entity.absolute_url(),
+                       'title': entity.dc_long_title(),})
+        return kwargs
+
+    def user_login(self):
+        try:
+            # if req is actually a session (we are on the server side), and we
+            # have to prevent nested internal session
+            return self.req.actual_session().user.login
+        except AttributeError:
+            return self.req.user.login
--- a/cwconfig.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/cwconfig.py	Mon Aug 31 19:09:54 2009 +0200
@@ -134,6 +134,9 @@
     'float' : 'Float',
     }
 
+_forced_mode = os.environ.get('CW_MODE')
+assert _forced_mode in (None, 'system', 'user')
+
 class CubicWebNoAppConfiguration(ConfigurationMixIn):
     """base class for cubicweb configuration without a specific instance directory
     """
@@ -150,7 +153,7 @@
         CUBES_DIR = '%(APYCOT_ROOT)s/local/share/cubicweb/cubes/' % os.environ
         # create __init__ file
         file(join(CUBES_DIR, '__init__.py'), 'w').close()
-    elif exists(join(CW_SOFTWARE_ROOT, '.hg')) or os.environ.get('CW_MODE') == 'user':
+    elif (exists(join(CW_SOFTWARE_ROOT, '.hg')) and _forced_mode != 'system') or _forced_mode == 'user':
         mode = 'dev'
         CUBES_DIR = abspath(normpath(join(CW_SOFTWARE_ROOT, '../cubes')))
     else:
--- a/cwvreg.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/cwvreg.py	Mon Aug 31 19:09:54 2009 +0200
@@ -16,12 +16,18 @@
 
 from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid,
                       ObjectNotFound, NoSelectableObject, RegistryNotFound,
-                      RegistryOutOfDate, CW_EVENT_MANAGER)
+                      RegistryOutOfDate, CW_EVENT_MANAGER, onevent)
 from cubicweb.utils import dump_class
 from cubicweb.vregistry import VRegistry, Registry, class_regid
 from cubicweb.rtags import RTAGS
 
 
+@onevent('before-registry-reload')
+def clear_rtag_objects():
+    for rtag in RTAGS:
+        rtag.clear()
+
+
 def use_interfaces(obj):
     """return interfaces used by the given object by searchinf for implements
     selectors, with a bw compat fallback to accepts_interfaces attribute
@@ -318,8 +324,8 @@
         else:
             self._needs_iface[obj] = ifaces
 
-    def register(self, obj, **kwargs):
-        super(CubicWebVRegistry, self).register(obj, **kwargs)
+    def register(self, obj, *args, **kwargs):
+        super(CubicWebVRegistry, self).register(obj, *args, **kwargs)
         # XXX bw compat
         ifaces = use_interfaces(obj)
         if ifaces:
--- a/debian/changelog	Thu Aug 27 13:02:45 2009 +0200
+++ b/debian/changelog	Mon Aug 31 19:09:54 2009 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.4.6-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 31 Aug 2009 14:12:30 +0200
+
 cubicweb (3.4.5-1) unstable; urgency=low
 
   * new upstream release
--- a/doc/book/en/development/devcore/vreg.rst	Thu Aug 27 13:02:45 2009 +0200
+++ b/doc/book/en/development/devcore/vreg.rst	Mon Aug 31 19:09:54 2009 +0200
@@ -1,4 +1,4 @@
-.. -*- coding: utf-8 -*-
+. -*- coding: utf-8 -*-
 
 The VRegistry
 --------------
@@ -94,69 +94,94 @@
 
 XXX this part needs to be translated
 
-Le but final : quand on est sur un Blog, on veut que le lien rss de celui-ci pointe
-vers les entrées de ce blog, non vers l'entité blog elle-même.
+The goal : when on a Blog, one wants the RSS link to refer to blog
+entries, not to the blog entity itself.
 
-L'idée générale pour résoudre ça : on définit une méthode sur les classes d'entité
-qui renvoie l'url du flux rss pour l'entité en question. Avec une implémentation
-par défaut sur AnyEntity et une implémentation particulière sur Blog qui fera ce
-qu'on veut.
+To do that, one defines a method on entity classes that returns the
+RSS stream url for a given entity. With a default implementation on
+AnyEntity and a specific implementation on Blog, which will do what we
+want.
 
-La limitation : on est embêté dans le cas ou par ex. on a un result set qui contient
-plusieurs entités Blog (ou autre chose), car on ne sait pas sur quelle entité appeler
-la méthode sus-citée. Dans ce cas, on va conserver le comportement actuel (eg appel
-à limited_rql)
+There's a limitation to this schema : when we have a result set
+containing several Blog entities (or different entities), we don't
+know on which entity to call the aforementioned method. In this case,
+we keep the current behaviour (e.g : call to limited_rql).
 
-Donc : on veut deux cas ici, l'un pour un rset qui contient une et une seule entité,
-l'autre pour un rset qui contient plusieurs entité.
+Hence we want two cases here, one for a single-entity rsets, the other
+for multi-entities rsets.
 
-Donc... On a déja dans web/views/boxes.py la classe RSSIconBox qui fonctionne. Son
-sélecteur ::
+In web/views/boxes.py lies the RSSIconBox class. Look at its selector ::
 
   class RSSIconBox(ExtResourcesBoxTemplate):
     """just display the RSS icon on uniform result set"""
     __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity()
 
+It takes into account :
 
-indique qu'il prend en compte :
+* the inherited selection criteria (one has to look them up in the
+  class hierarchy to know the details)
 
-* les conditions d'apparition de la boite (faut remonter dans les classes parentes
-  pour voir le détail)
-* non_final_entity, qui filtre sur des rset contenant une liste d'entité non finale
+* non_final_entity, which filters on rsets containing non final
+  entities (a 'final entity' being synonym for entity attribute)
 
-ça correspond donc à notre 2eme cas. Reste à fournir un composant plus spécifique
-pour le 1er cas ::
+This matches our second case. Hence we have to provide a specific
+component for the first case ::
 
   class EntityRSSIconBox(RSSIconBox):
     """just display the RSS icon on uniform result set for a single entity"""
     __select__ = RSSIconBox.__select__ & one_line_rset()
 
+Here, one adds the one_line_rset selector, which filters result sets
+of size 1. When one chains selectors, the final score is the sum of
+the score of each individual selector (unless one of them returns 0,
+in which case the object is non selectable). Thus, on a multiple
+entities selector, one_line_rset makes the EntityRSSIconBox class non
+selectable. For an rset with one entity, the EntityRSSIconBox class
+will have a higher score then RSSIconBox, which is what we wanted.
 
-Ici, on ajoute le selector one_line_rset, qui filtre sur des result set de taille 1. Il faut
-savoir que quand on chaine des selecteurs, le score final est la somme des scores
-renvoyés par chaque sélecteur (sauf si l'un renvoie zéro, auquel cas l'objet est
-non sélectionnable). Donc ici, sur un rset avec plusieurs entités, onelinerset_selector
-rendra la classe EntityRSSIconBox non sélectionnable, et on obtiendra bien la
-classe RSSIconBox. Pour un rset avec une entité, la classe EntityRSSIconBox aura un
-score supérieur à RSSIconBox et c'est donc bien elle qui sera sélectionnée.
+Of course, once this is done, you have to ::
+
+* fill in the call method of EntityRSSIconBox
 
-Voili voilou, il reste donc pour finir tout ça :
+* provide the default implementation of the method returning the RSS
+  stream url on AnyEntity
 
-* à définir le contenu de la méthode call de EntityRSSIconBox
-* fournir l'implémentation par défaut de la méthode renvoyant l'url du flux rss sur
-  AnyEntity
-* surcharger cette methode dans blog.Blog
-
+* redefine this method on Blog.
 
 When to use selectors?
 ```````````````````````
 
-Il faut utiliser les sélecteurs pour faire des choses différentes en
-fonction de ce qu'on a en entrée. Dès qu'on a un "if" qui teste la
-nature de `self.rset` dans un objet, il faut très sérieusement se
-poser la question s'il ne vaut pas mieux avoir deux objets différent
-avec des sélecteurs approprié.
+Selectors are to be used whenever arises the need of dispatching on
+the shape or content of a result set.
 
 Debugging
 `````````
-XXX explain traced_selection context manager
+
+Once in a while, one needs to understand why a view (or any AppObject)
+is, or is not selected appropriately. Looking at which selectors fired
+(or did not) is the way. There exists a traced_selection context
+manager to help with that.
+
+Here is an example ::
+
+.. sourcecode:: python
+
+    def possible_objects(self, registry, *args, **kwargs):
+        """return an iterator on possible objects in a registry for this result set
+
+        actions returned are classes, not instances
+        """
+        from cubicweb.selectors import traced_selection
+        with traced_selection():
+            for vobjects in self.registry(registry).values():
+                try:
+                    yield self.select(vobjects, *args, **kwargs)
+                except NoSelectableObject:
+                    continue
+
+Don't forget the 'from __future__ import with_statement' at the module
+top-level.
+
+This will yield additional WARNINGs in the logs, like this::
+
+    2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
--- a/entities/wfobjs.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/entities/wfobjs.py	Mon Aug 31 19:09:54 2009 +0200
@@ -303,7 +303,8 @@
 
     @property
     def workflow(self):
-        return self.state_of[0]
+        # take care, may be missing in multi-sources configuration
+        return self.state_of and self.state_of[0]
 
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
--- a/entity.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/entity.py	Mon Aug 31 19:09:54 2009 +0200
@@ -209,7 +209,7 @@
     def __hash__(self):
         return id(self)
 
-    def __cmp__(self):
+    def __cmp__(self, other):
         raise NotImplementedError('comparison not implemented for %s' % self.__class__)
 
     def pre_add_hook(self):
--- a/hooks/notification.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/hooks/notification.py	Mon Aug 31 19:09:54 2009 +0200
@@ -18,6 +18,8 @@
     """delay rendering of notification view until precommit"""
     def precommit_event(self):
         view = self.view
+        if view.rset is not None and not view.rset:
+            return # entity added and deleted in the same transaction (cache effect)
         if view.cw_rset and self.session.deleted_in_transaction(view.cw_rset[cw_rset.cw_row or 0][cw_rset.cw_col or 0]):
             return # entity added and deleted in the same transaction
         self.view.render_and_send(**getattr(self, 'viewargs', {}))
--- a/hooks/workflow.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/hooks/workflow.py	Mon Aug 31 19:09:54 2009 +0200
@@ -20,8 +20,12 @@
     nocheck = session.transaction_data.setdefault('skip-security', set())
     nocheck.add((x, 'in_state', oldstate))
     nocheck.add((x, 'in_state', newstate))
-    # delete previous state first in case we're using a super session
-    session.delete_relation(x, 'in_state', oldstate)
+    # delete previous state first in case we're using a super session,
+    # unless in_state isn't stored in the system source
+    fromsource = session.describe(x)[1]
+    if fromsource == 'system' or \
+           not session.repo.sources_by_uri[fromsource].support_relation('in_state'):
+        session.delete_relation(x, 'in_state', oldstate)
     session.add_relation(x, 'in_state', newstate)
 
 
--- a/server/msplanner.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/server/msplanner.py	Mon Aug 31 19:09:54 2009 +0200
@@ -97,9 +97,6 @@
 # str() Constant.value to ensure generated table name won't be unicode
 Constant._ms_table_key = lambda x: str(x.value)
 
-AbstractSource.dont_cross_relations = ()
-AbstractSource.cross_relations = ()
-
 def need_source_access_relation(vargraph):
     if not vargraph:
         return False
--- a/server/repository.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/server/repository.py	Mon Aug 31 19:09:54 2009 +0200
@@ -30,7 +30,7 @@
 
 from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, CW_EVENT_MANAGER,
                       UnknownEid, AuthenticationError, ExecutionError,
-                      ETypeNotSupportedBySources, RTypeNotSupportedBySources,
+                      ETypeNotSupportedBySources, MultiSourcesError,
                       BadConnectionId, Unauthorized, ValidationError,
                       typed_eid)
 from cubicweb import cwvreg, schema, server
@@ -927,12 +927,21 @@
     def locate_relation_source(self, session, subject, rtype, object):
         subjsource = self.source_from_eid(subject, session)
         objsource = self.source_from_eid(object, session)
-        if not (subjsource is objsource and subjsource.support_relation(rtype, 1)):
+        if not subjsource is objsource:
             source = self.system_source
-            if not source.support_relation(rtype, 1):
-                raise RTypeNotSupportedBySources(rtype)
+            if not (subjsource.may_cross_relation(rtype) 
+                    and objsource.may_cross_relation(rtype)):
+                raise MultiSourcesError(
+                    "relation %s can't be crossed among sources"
+                    % rtype)
+        elif not subjsource.support_relation(rtype):
+            source = self.system_source
         else:
             source = subjsource
+        if not source.support_relation(rtype, True):
+            raise MultiSourcesError(
+                "source %s doesn't support write of %s relation"
+                % (source.uri, rtype))
         return source
 
     def locate_etype_source(self, etype):
--- a/server/sources/__init__.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/server/sources/__init__.py	Mon Aug 31 19:09:54 2009 +0200
@@ -80,6 +80,11 @@
     # a reference to the instance'schema (may differs from the source'schema)
     schema = None
 
+    # multi-sources planning control
+    dont_cross_relations = ()
+    cross_relations = ()
+
+
     def __init__(self, repo, appschema, source_config, *args, **kwargs):
         self.repo = repo
         self.uri = source_config['uri']
@@ -177,6 +182,19 @@
             return wsupport
         return True
 
+    def may_cross_relation(self, rtype):
+        """return True if the relation may be crossed among sources. Rules are:
+
+        * if this source support the relation, can't be crossed unless explicitly
+          specified in .cross_relations
+
+        * if this source doesn't support the relation, can be crossed unless
+          explicitly specified in .dont_cross_relations
+        """
+        if self.support_relation(rtype):
+            return rtype in self.cross_relations
+        return rtype not in self.dont_cross_relations
+
     def eid2extid(self, eid, session=None):
         return self.repo.eid2extid(self, eid, session)
 
--- a/server/sources/native.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/server/sources/native.py	Mon Aug 31 19:09:54 2009 +0200
@@ -268,6 +268,9 @@
         # can't claim not supporting a relation
         return True #not rtype == 'content_for'
 
+    def may_cross_relation(self, rtype):
+        return True
+
     def authenticate(self, session, login, password):
         """return CWUser eid for the given login/password if this account is
         defined in this source, else raise `AuthenticationError`
--- a/server/sources/pyrorql.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/server/sources/pyrorql.py	Mon Aug 31 19:09:54 2009 +0200
@@ -345,6 +345,7 @@
         cu.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
                    kwargs, 'x')
         self._query_cache.clear()
+        entity.clear_all_caches()
 
     def delete_entity(self, session, etype, eid):
         """delete an entity from the source"""
@@ -360,6 +361,8 @@
                    {'x': self.eid2extid(subject, session),
                     'y': self.eid2extid(object, session)}, ('x', 'y'))
         self._query_cache.clear()
+        session.entity_from_eid(subject).clear_all_caches()
+        session.entity_from_eid(object).clear_all_caches()
 
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
@@ -368,6 +371,8 @@
                    {'x': self.eid2extid(subject, session),
                     'y': self.eid2extid(object, session)}, ('x', 'y'))
         self._query_cache.clear()
+        session.entity_from_eid(subject).clear_all_caches()
+        session.entity_from_eid(object).clear_all_caches()
 
 
 class RQL2RQL(object):
--- a/sobjects/notification.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/sobjects/notification.py	Mon Aug 31 19:09:54 2009 +0200
@@ -19,6 +19,8 @@
 from cubicweb.common.mail import NotificationView
 from cubicweb.server.hookhelper import SendMailOp
 
+parse_message_id = deprecated('parse_message_id is now defined in cubicweb.common.mail')(parse_message_id)
+
 
 class RecipientsFinder(Component):
     """this component is responsible to find recipients of a notification
--- a/vregistry.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/vregistry.py	Mon Aug 31 19:09:54 2009 +0200
@@ -187,7 +187,7 @@
         raise `NoSelectableObject` if not object apply
         """
         if len(args) > 1:
-            warn('only the request param can not be named when calling select',
+            warn('[3.5] only the request param can not be named when calling select*',
                  DeprecationWarning, stacklevel=3)
         score, winners = 0, []
         for appobject in appobjects:
@@ -210,8 +210,10 @@
                                    [repr(v) for v in winners]))
         # return the result of calling the appobject
         return winners[0](*args, **kwargs)
+
     select_best = deprecated('[3.6] select_best is now private')(_select_best)
 
+
 class VRegistry(dict):
     """class responsible to register, propose and select the various
     elements used to build the web interface. Currently, we have templates,
@@ -300,10 +302,11 @@
                 if obj.__module__ != modname or obj in butclasses:
                     continue
                 oid = class_regid(obj)
+                registryname = obj.__registry__
             except AttributeError:
                 continue
             if oid and not '__abstract__' in obj.__dict__:
-                self.register(obj)
+                self.register(obj, registryname)
 
     def register(self, obj, registryname=None, oid=None, clear=False):
         """base method to add an object in the registry"""
--- a/web/data/cubicweb.ajax.js	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/data/cubicweb.ajax.js	Mon Aug 31 19:09:54 2009 +0200
@@ -51,7 +51,7 @@
     }
     // find textareas and wrap them if there are some
     if (typeof(FCKeditor) != 'undefined') {
-	buildWysiwygEditors(node);
+	buildWysiwygEditors();
     }
     if (typeof initFacetBoxEvents != 'undefined') {
 	initFacetBoxEvents(node);
@@ -351,7 +351,10 @@
  */
 function buildWysiwygEditors(parent) {
     jQuery('textarea').each(function () {
-	if (this.getAttribute('cubicweb:type', 'wysiwyg')) {
+	if (this.getAttribute('cubicweb:type') == 'wysiwyg') {
+            // mark editor as instanciated, we may be called a number of times
+            // (see postAjaxLoad)
+            this.setAttribute('cubicweb:type', 'fckeditor');
 	    if (typeof FCKeditor != "undefined") {
 		var fck = new FCKeditor(this.id);
 		fck.Config['CustomConfigurationsPath'] = fckconfigpath;
--- a/web/data/cubicweb.css	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/data/cubicweb.css	Mon Aug 31 19:09:54 2009 +0200
@@ -256,13 +256,17 @@
 }
 
 /* Popup on login box and userActionBox */
+div.popupWrapper{
+ position:relative;
+ z-index:100;
+}
+
 div.popup {
   position: absolute;
-  z-index: 400;
   background: #fff;
   border: 1px solid black;
   text-align: left;
-  float:left;
+  z-index:400;
 }
 
 div.popup ul li a {
@@ -840,7 +844,6 @@
   background: #fffff8 url("button.png") bottom left repeat-x;
 }
 
-
 /********************************/
 /* placement of alt. view icons */
 /********************************/
--- a/web/uicfg.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/uicfg.py	Mon Aug 31 19:09:54 2009 +0200
@@ -67,7 +67,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb import neg_role, onevent
+from cubicweb import neg_role
 from cubicweb.rtags import (RelationTags, RelationTagsBool,
                             RelationTagsSet, RelationTagsDict, register_rtag)
 from cubicweb.web import formwidgets
@@ -231,14 +231,3 @@
 
 actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
                                                init_actionbox_appearsin_addmenu)
-
-@onevent('before-registry-reload')
-def clear_rtag_objects():
-    primaryview_section.clear()
-    primaryview_display_ctrl.clear()
-    autoform_section.clear()
-    autoform_field.clear()
-    autoform_field_kwargs.clear()
-    autoform_is_inlined.clear()
-    autoform_permissions_overrides.clear()
-    actionbox_appearsin_addmenu.clear()
--- a/web/views/basecomponents.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/views/basecomponents.py	Mon Aug 31 19:09:54 2009 +0200
@@ -226,10 +226,10 @@
     }
 
     def call(self, vid):
-        self.req.add_css('cubes.confman.css')
         entity = self.entity(0,0)
-        self.w(u'<a href="%s" class="otherView"><img src="data/pdf_icon.gif"/></a>' %
-               (xml_escape(entity.absolute_url() + '?vid=%s&__template=pdf-main-template' % vid)))
+        url = entity.absolute_url(vid=vid, __template='pdf-main-template')
+        self.w(u'<a href="%s" class="otherView"><img src="data/pdf_icon.gif" alt="%s"/></a>' %
+               (xml_escape(url), self.req._('download page as pdf')))
 
 
 
--- a/web/views/basetemplates.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/views/basetemplates.py	Mon Aug 31 19:09:54 2009 +0200
@@ -352,6 +352,7 @@
 class HTMLPageHeader(View):
     """default html page header"""
     id = 'header'
+    main_cell_components = ('appliname', 'breadcrumbs')
 
     def call(self, view, **kwargs):
         self.main_header(view)
@@ -373,7 +374,7 @@
         self.w(u'</td>\n')
         # appliname and breadcrumbs
         self.w(u'<td id="headtext">')
-        for cid in ('appliname', 'breadcrumbs'):
+        for cid in self.main_cell_components:
             comp = self.vreg['components'].select_or_none(
                 cid, self.req, rset=self.rset)
             if comp and comp.cw_propval('visible'):
--- a/web/views/editforms.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/views/editforms.py	Mon Aug 31 19:09:54 2009 +0200
@@ -371,6 +371,8 @@
     entity
     """
     id = 'copy'
+    warning_message = _('Please note that this is only a shallow copy')
+
     def render_form(self, entity):
         """fetch and render the form"""
         # make a copy of entity to avoid altering the entity in the
@@ -381,7 +383,7 @@
         self.initialize_varmaker()
         self.newentity.eid = self.varmaker.next()
         self.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'
-               % self.req._('Please note that this is only a shallow copy'))
+               % self.req._(self.warning_message))
         super(CopyFormView, self).render_form(self.newentity)
         del self.newentity
 
--- a/web/views/idownloadable.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/views/idownloadable.py	Mon Aug 31 19:09:54 2009 +0200
@@ -147,17 +147,17 @@
     def cell_call(self, row, col, width=None, height=None, link=False):
         entity = self.rset.get_entity(row, col)
         #if entity.data_format.startswith('image/'):
-        imgtag = u'<img src="%s" alt="%s" ' % (xml_escape(entity.download_url()),
-                                               xml_escape(entity.download_file_name()))
+        imgtag = u'<img src="%s" alt="%s" ' % (
+            xml_escape(entity.download_url()),
+            (self.req._('download %s')  % xml_escape(entity.download_file_name())))
         if width:
             imgtag += u'width="%i" ' % width
         if height:
             imgtag += u'height="%i" ' % height
         imgtag += u'/>'
         if link:
-            self.w(u'<a href="%s" alt="%s">%s</a>' % (entity.absolute_url(vid='download'),
-                                                      self.req._('download image'),
-                                                      imgtag))
+            self.w(u'<a href="%s">%s</a>' % (entity.absolute_url(vid='download'),
+                                             imgtag))
         else:
             self.w(imgtag)
 
--- a/web/views/primary.py	Thu Aug 27 13:02:45 2009 +0200
+++ b/web/views/primary.py	Mon Aug 31 19:09:54 2009 +0200
@@ -52,6 +52,7 @@
         boxes = self._prepare_side_boxes(entity)
         if boxes or hasattr(self, 'render_side_related'):
             self.w(u'<table width="100%"><tr><td style="width: 75%">')
+        self.render_entity_summary(entity)
         self.w(u'<div class="mainInfo">')
         self.content_navigation_components('navcontenttop')
         self.render_entity_attributes(entity)
@@ -91,6 +92,8 @@
 
     def render_entity_metadata(self, entity):
         entity.view('metadata', w=self.w)
+
+    def render_entity_summary(self, entity):
         summary = self.summary(entity) # deprecate summary?
         if summary:
             self.w(u'<div class="summary">%s</div>' % summary)