rtags.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """
       
    19 A RelationTag object is an object which allows to link a configuration
       
    20 information to a relation definition. For instance, the standard
       
    21 primary view uses a RelationTag object (uicfg.primaryview_section) to
       
    22 get the section to display relations.
       
    23 
       
    24 .. sourcecode:: python
       
    25 
       
    26    # display ``entry_of`` relations in the ``relations`` section in the ``BlogEntry`` primary view
       
    27    uicfg.primaryview_section.tag_subject_of(('BlogEntry', 'entry_of', '*'),
       
    28                                              'relations')
       
    29 
       
    30    # hide every relation ``entry_of`` in the ``Blog`` primary view
       
    31    uicfg.primaryview_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden')
       
    32 
       
    33 Three primitives are defined:
       
    34    * ``tag_subject_of`` tag a relation in the subject's context
       
    35    * ``tag_object_of`` tag a relation in the object's context
       
    36    * ``tag_attribute`` shortcut for tag_subject_of
       
    37 """
       
    38 __docformat__ = "restructuredtext en"
       
    39 
       
    40 import logging
       
    41 from warnings import warn
       
    42 
       
    43 from six import string_types
       
    44 
       
    45 from logilab.common.logging_ext import set_log_methods
       
    46 from logilab.common.registry import RegistrableInstance, yes
       
    47 
       
    48 def _ensure_str_key(key):
       
    49     return tuple(str(k) for k in key)
       
    50 
       
    51 class RegistrableRtags(RegistrableInstance):
       
    52     __registry__ = 'uicfg'
       
    53     __select__ = yes()
       
    54 
       
    55 
       
    56 class RelationTags(RegistrableRtags):
       
    57     """a tag store for full relation definitions :
       
    58 
       
    59          (subject type, relation type, object type, tagged)
       
    60 
       
    61     allowing to set tags using wildcard (eg '*') as subject type / object type
       
    62 
       
    63     This class associates a single tag to each key.
       
    64     """
       
    65     _allowed_values = None
       
    66     # _init expected to be a method (introduced in 3.17), while _initfunc a
       
    67     # function given as __init__ argument and kept for bw compat
       
    68     _init = _initfunc = None
       
    69 
       
    70     def __init__(self):
       
    71         self._tagdefs = {}
       
    72 
       
    73     def __repr__(self):
       
    74         # find a way to have more infos but keep it readable
       
    75         # (in error messages in case of an ambiguity for instance)
       
    76         return '%s (%s): %s' % (id(self), self.__regid__, self.__class__)
       
    77 
       
    78     # dict compat
       
    79     def __getitem__(self, key):
       
    80         return self.get(*key)
       
    81     __contains__ = __getitem__
       
    82 
       
    83     def clear(self):
       
    84         self._tagdefs.clear()
       
    85 
       
    86     def _get_keys(self, stype, rtype, otype, tagged):
       
    87         keys = []
       
    88         if '*' not in (stype, otype):
       
    89             keys.append(('*', rtype, '*', tagged))
       
    90         if '*' != stype:
       
    91             keys.append(('*', rtype, otype, tagged))
       
    92         if '*' != otype:
       
    93             keys.append((stype, rtype, '*', tagged))
       
    94         keys.append((stype, rtype, otype, tagged))
       
    95         return keys
       
    96 
       
    97     def init(self, schema, check=True):
       
    98         # XXX check existing keys against schema
       
    99         if check:
       
   100             for (stype, rtype, otype, tagged), value in list(self._tagdefs.items()):
       
   101                 for ertype in (stype, rtype, otype):
       
   102                     if ertype != '*' and not ertype in schema:
       
   103                         self.warning('removing rtag %s: %s, %s undefined in schema',
       
   104                                      (stype, rtype, otype, tagged), value, ertype)
       
   105                         self.del_rtag(stype, rtype, otype, tagged)
       
   106                         break
       
   107         if self._init is not None:
       
   108             self.apply(schema, self._init)
       
   109 
       
   110     def apply(self, schema, func):
       
   111         for eschema in schema.entities():
       
   112             if eschema.final:
       
   113                 continue
       
   114             for rschema, tschemas, role in eschema.relation_definitions(True):
       
   115                 for tschema in tschemas:
       
   116                     if role == 'subject':
       
   117                         sschema, oschema = eschema, tschema
       
   118                     else:
       
   119                         sschema, oschema = tschema, eschema
       
   120                     func(sschema, rschema, oschema, role)
       
   121 
       
   122     # rtag declaration api ####################################################
       
   123 
       
   124     def tag_attribute(self, key, *args, **kwargs):
       
   125         key = list(key)
       
   126         key.append('*')
       
   127         key.append('subject')
       
   128         self.tag_relation(key, *args, **kwargs)
       
   129 
       
   130     def tag_subject_of(self, key, *args, **kwargs):
       
   131         key = list(key)
       
   132         key.append('subject')
       
   133         self.tag_relation(key, *args, **kwargs)
       
   134 
       
   135     def tag_object_of(self, key, *args, **kwargs):
       
   136         key = list(key)
       
   137         key.append('object')
       
   138         self.tag_relation(key, *args, **kwargs)
       
   139 
       
   140     def tag_relation(self, key, tag):
       
   141         assert len(key) == 4, 'bad key: %s' % list(key)
       
   142         if self._allowed_values is not None:
       
   143             assert tag in self._allowed_values, \
       
   144                    '%r is not an allowed tag (should be in %s)' % (
       
   145                 tag, self._allowed_values)
       
   146         self._tagdefs[_ensure_str_key(key)] = tag
       
   147         return tag
       
   148 
       
   149     def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs):
       
   150         if isinstance(attr, string_types):
       
   151             attr, role = attr, 'subject'
       
   152         else:
       
   153             attr, role = attr
       
   154         if role == 'subject':
       
   155             self.tag_subject_of((etype, attr, desttype), *args, **kwargs)
       
   156         else:
       
   157             self.tag_object_of((desttype, attr, etype), *args, **kwargs)
       
   158 
       
   159 
       
   160     # rtag runtime api ########################################################
       
   161 
       
   162     def del_rtag(self, *key):
       
   163         del self._tagdefs[key]
       
   164 
       
   165     def get(self, *key):
       
   166         for key in reversed(self._get_keys(*key)):
       
   167             try:
       
   168                 return self._tagdefs[key]
       
   169             except KeyError:
       
   170                 continue
       
   171         return None
       
   172 
       
   173     def etype_get(self, etype, rtype, role, ttype='*'):
       
   174         if role == 'subject':
       
   175             return self.get(etype, rtype, ttype, role)
       
   176         return self.get(ttype, rtype, etype, role)
       
   177 
       
   178     # these are overridden by set_log_methods below
       
   179     # only defining here to prevent pylint from complaining
       
   180     info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
       
   181 
       
   182 
       
   183 class RelationTagsSet(RelationTags):
       
   184     """This class associates a set of tags to each key.
       
   185     """
       
   186     tag_container_cls = set
       
   187 
       
   188     def tag_relation(self, key, tag):
       
   189         rtags = self._tagdefs.setdefault(_ensure_str_key(key),
       
   190                                          self.tag_container_cls())
       
   191         rtags.add(tag)
       
   192         return rtags
       
   193 
       
   194     def get(self, stype, rtype, otype, tagged):
       
   195         rtags = self.tag_container_cls()
       
   196         for key in self._get_keys(stype, rtype, otype, tagged):
       
   197             try:
       
   198                 rtags.update(self._tagdefs[key])
       
   199             except KeyError:
       
   200                 continue
       
   201         return rtags
       
   202 
       
   203 
       
   204 class RelationTagsDict(RelationTagsSet):
       
   205     """This class associates a set of tags to each key."""
       
   206     tag_container_cls = dict
       
   207 
       
   208     def tag_relation(self, key, tag):
       
   209         key = _ensure_str_key(key)
       
   210         try:
       
   211             rtags = self._tagdefs[key]
       
   212             rtags.update(tag)
       
   213             return rtags
       
   214         except KeyError:
       
   215             self._tagdefs[key] = tag
       
   216             return tag
       
   217 
       
   218     def setdefault(self, key, tagkey, tagvalue):
       
   219         key = _ensure_str_key(key)
       
   220         try:
       
   221             rtags = self._tagdefs[key]
       
   222             rtags.setdefault(tagkey, tagvalue)
       
   223             return rtags
       
   224         except KeyError:
       
   225             self._tagdefs[key] = {tagkey: tagvalue}
       
   226             return self._tagdefs[key]
       
   227 
       
   228 
       
   229 class RelationTagsBool(RelationTags):
       
   230     _allowed_values = frozenset((True, False))
       
   231 
       
   232 
       
   233 class NoTargetRelationTagsDict(RelationTagsDict):
       
   234 
       
   235     @property
       
   236     def name(self):
       
   237         return self.__class__.name
       
   238 
       
   239     # tag_subject_of / tag_object_of issue warning if '*' is not given as target
       
   240     # type, while tag_relation handle it silently since it may be used during
       
   241     # initialization
       
   242     def tag_subject_of(self, key, tag):
       
   243         subj, rtype, obj = key
       
   244         if obj != '*':
       
   245             self.warning('using explict target type in %s.tag_subject_of() '
       
   246                          'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)',
       
   247                          self.name, subj, rtype, subj, rtype, obj)
       
   248         super(NoTargetRelationTagsDict, self).tag_subject_of((subj, rtype, '*'), tag)
       
   249 
       
   250     def tag_object_of(self, key, tag):
       
   251         subj, rtype, obj = key
       
   252         if subj != '*':
       
   253             self.warning('using explict subject type in %s.tag_object_of() '
       
   254                          'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)',
       
   255                          self.name, rtype, obj, subj, rtype, obj)
       
   256         super(NoTargetRelationTagsDict, self).tag_object_of(('*', rtype, obj), tag)
       
   257 
       
   258     def tag_relation(self, key, tag):
       
   259         if key[-1] == 'subject' and key[-2] != '*':
       
   260             if isinstance(key, tuple):
       
   261                 key = list(key)
       
   262             key[-2] = '*'
       
   263         elif key[-1] == 'object' and key[0] != '*':
       
   264             if isinstance(key, tuple):
       
   265                 key = list(key)
       
   266             key[0] = '*'
       
   267         super(NoTargetRelationTagsDict, self).tag_relation(key, tag)
       
   268 
       
   269 
       
   270 set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags'))