entity.py
branchtls-sprint
changeset 1154 9b23a6836c32
parent 1138 22f634977c95
child 1177 7074698c6522
equal deleted inserted replaced
1153:6a7636b32a97 1154:9b23a6836c32
     3 :organization: Logilab
     3 :organization: Logilab
     4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 """
     6 """
     7 __docformat__ = "restructuredtext en"
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 from warnings import warn
     8 
    10 
     9 from logilab.common import interface
    11 from logilab.common import interface
    10 from logilab.common.compat import all
    12 from logilab.common.compat import all
    11 from logilab.common.decorators import cached
    13 from logilab.common.decorators import cached
    12 from logilab.common.deprecation import obsolete
    14 from logilab.common.deprecation import obsolete
    37             if card in '+*':
    39             if card in '+*':
    38                 return card
    40                 return card
    39     return '1'
    41     return '1'
    40 
    42 
    41 
    43 
    42 class RelationTags(object):
    44 MODE_TAGS = set(('link', 'create'))
    43     
    45 CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
    44     MODE_TAGS = frozenset(('link', 'create'))
    46 
    45     CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated',
    47 try:
    46                                'inlineview'))
    48     from cubicweb.web.views.editforms import AutomaticEntityForm
    47 
    49     from cubicweb.web.views.boxes import EditBox
    48     def __init__(self, eclass, tagdefs):
    50 
    49         # XXX if a rtag is redefined in a subclass,
    51     def dispatch_rtags(tags, rtype, role, stype, otype):
    50         # the rtag of the base class overwrite the rtag of the subclass
    52         for tag in tags:
    51         self.eclass = eclass
    53             if tag in MODE_TAGS:
    52         self._tagdefs = {}
    54                 EditBox.rmode.set_rtag(tag, rtype, role, stype, otype)
    53         for relation, tags in tagdefs.iteritems():
    55             elif tag in CATEGORY_TAGS:
    54             # tags must become a set
    56                 AutomaticEntityForm.rcategories.set_rtag(tag, rtype, role, stype, otype)
    55             if isinstance(tags, basestring):
    57             elif tag == 'inlined':
    56                 tags = set((tags,))
    58                 AutomaticEntityForm.rinline.set_rtag(True, rtype, role, stype, otype)
    57             elif not isinstance(tags, set):
       
    58                 tags = set(tags)
       
    59             # relation must become a 3-uple (rtype, targettype, role)
       
    60             if isinstance(relation, basestring):
       
    61                 self._tagdefs[(relation, '*', 'subject')] = tags
       
    62                 self._tagdefs[(relation, '*', 'object')] = tags
       
    63             elif len(relation) == 1: # useful ?
       
    64                 self._tagdefs[(relation[0], '*', 'subject')] = tags
       
    65                 self._tagdefs[(relation[0], '*', 'object')] = tags
       
    66             elif len(relation) == 2:
       
    67                 rtype, ttype = relation
       
    68                 ttype = bw_normalize_etype(ttype) # XXX bw compat
       
    69                 self._tagdefs[rtype, ttype, 'subject'] = tags
       
    70                 self._tagdefs[rtype, ttype, 'object'] = tags
       
    71             elif len(relation) == 3:
       
    72                 relation = list(relation)  # XXX bw compat
       
    73                 relation[1] = bw_normalize_etype(relation[1])
       
    74                 self._tagdefs[tuple(relation)] = tags
       
    75             else:
    59             else:
    76                 raise ValueError('bad rtag definition (%r)' % (relation,))
    60                 raise ValueError(tag)
    77         
    61             
    78 
    62 except ImportError:
    79     def __initialize__(self):
    63     AutomaticEntityForm = None
    80         # eclass.[*]schema are only set when registering
    64     
    81         self.schema = self.eclass.schema
    65     def dispatch_rtags(*args):
    82         eschema = self.eschema = self.eclass.e_schema
    66         pass
    83         rtags = self._tagdefs
    67     
    84         # expand wildcards in rtags and add automatic tags
       
    85         for rschema, tschemas, role in sorted(eschema.relation_definitions(True)):
       
    86             rtype = rschema.type
       
    87             star_tags = rtags.pop((rtype, '*', role), set())
       
    88             for tschema in tschemas:
       
    89                 tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags))
       
    90                 if role == 'subject':
       
    91                     X, Y = eschema, tschema
       
    92                     card = rschema.rproperty(X, Y, 'cardinality')[0]
       
    93                     composed = rschema.rproperty(X, Y, 'composite') == 'object'
       
    94                 else:
       
    95                     X, Y = tschema, eschema
       
    96                     card = rschema.rproperty(X, Y, 'cardinality')[1]
       
    97                     composed = rschema.rproperty(X, Y, 'composite') == 'subject'
       
    98                 # set default category tags if needed
       
    99                 if not tags & self.CATEGORY_TAGS:
       
   100                     if card in '1+':
       
   101                         if not rschema.is_final() and composed:
       
   102                             category = 'generated'
       
   103                         elif rschema.is_final() and (
       
   104                             rschema.type.endswith('_format')
       
   105                             or rschema.type.endswith('_encoding')):
       
   106                             category = 'generated'
       
   107                         else:
       
   108                             category = 'primary'
       
   109                     elif rschema.is_final():
       
   110                         if (rschema.type.endswith('_format')
       
   111                             or rschema.type.endswith('_encoding')):
       
   112                             category = 'generated'
       
   113                         else:
       
   114                             category = 'secondary'
       
   115                     else: 
       
   116                         category = 'generic'
       
   117                     tags.add(category)
       
   118                 if not tags & self.MODE_TAGS:
       
   119                     if card in '?1':
       
   120                         # by default, suppose link mode if cardinality doesn't allow
       
   121                         # more than one relation
       
   122                         mode = 'link'
       
   123                     elif rschema.rproperty(X, Y, 'composite') == role:
       
   124                         # if self is composed of the target type, create mode
       
   125                         mode = 'create'
       
   126                     else:
       
   127                         # link mode by default
       
   128                         mode = 'link'
       
   129                     tags.add(mode)
       
   130 
       
   131     def _default_target(self, rschema, role='subject'):
       
   132         eschema = self.eschema
       
   133         if role == 'subject':
       
   134             return eschema.subject_relation(rschema).objects(eschema)[0]
       
   135         else:
       
   136             return eschema.object_relation(rschema).subjects(eschema)[0]
       
   137 
       
   138     # dict compat
       
   139     def __getitem__(self, key):
       
   140         if isinstance(key, basestring):
       
   141             key = (key,)
       
   142         return self.get_tags(*key)
       
   143 
       
   144     __contains__ = __getitem__
       
   145     
       
   146     def get_tags(self, rtype, targettype=None, role='subject'):
       
   147         rschema = self.schema.rschema(rtype)
       
   148         if targettype is None:
       
   149             tschema = self._default_target(rschema, role)
       
   150         else:
       
   151             tschema = self.schema.eschema(targettype)
       
   152         return self._tagdefs[(rtype, tschema.type, role)]
       
   153 
       
   154     __call__ = get_tags
       
   155     
       
   156     def get_mode(self, rtype, targettype=None, role='subject'):
       
   157         # XXX: should we make an assertion on rtype not being final ?
       
   158         # assert not rschema.is_final()
       
   159         tags = self.get_tags(rtype, targettype, role)
       
   160         # do not change the intersection order !
       
   161         modes = tags & self.MODE_TAGS
       
   162         assert len(modes) == 1
       
   163         return modes.pop()
       
   164 
       
   165     def get_category(self, rtype, targettype=None, role='subject'):
       
   166         tags = self.get_tags(rtype, targettype, role)
       
   167         categories = tags & self.CATEGORY_TAGS
       
   168         assert len(categories) == 1
       
   169         return categories.pop()
       
   170 
       
   171     def is_inlined(self, rtype, targettype=None, role='subject'):
       
   172         # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype)
       
   173         return 'inlineview' in self.get_tags(rtype, targettype, role)
       
   174 
       
   175 
       
   176 class metaentity(type):
    68 class metaentity(type):
   177     """this metaclass sets the relation tags on the entity class
    69     """this metaclass sets the relation tags on the entity class
   178     and deals with the `widgets` attribute
    70     and deals with the `widgets` attribute
   179     """
    71     """
   180     def __new__(mcs, name, bases, classdict):
    72     def __new__(mcs, name, bases, classdict):
   181         # collect baseclass' rtags
    73         # collect baseclass' rtags
   182         tagdefs = {}
    74         if '__rtags__' in classdict:
   183         widgets = {}
    75             etype = classdict['id']
   184         for base in bases:
    76             warn('%s: __rtags__ is deprecated' % name, DeprecationWarning)
   185             tagdefs.update(getattr(base, '__rtags__', {}))
    77             for relation, tags in classdict.pop('__rtags__').iteritems():
   186             widgets.update(getattr(base, 'widgets', {}))
    78                 # tags must become an iterable
   187         # update with the class' own rtgas
    79                 if isinstance(tags, basestring):
   188         tagdefs.update(classdict.get('__rtags__', {}))
    80                     tags = (tags,)
   189         widgets.update(classdict.get('widgets', {}))
    81                 # relation must become a 3-uple (rtype, targettype, role)
   190         # XXX decide whether or not it's a good idea to replace __rtags__
    82                 if isinstance(relation, basestring):
   191         #     good point: transparent support for inheritance levels >= 2
    83                     dispatch_rtags(tags, relation, 'subject', etype, '*')
   192         #     bad point: we loose the information of which tags are specific
    84                     dispatch_rtags(tags, relation, 'object', '*', etype)
   193         #                to this entity class
    85                 elif len(relation) == 1: # useful ?
   194         classdict['__rtags__'] = tagdefs
    86                     dispatch_rtags(tags, relation[0], 'subject', etype, '*')
   195         classdict['widgets'] = widgets
    87                     dispatch_rtags(tags, relation[0], 'object', '*', etype)
   196         eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict)
    88                 elif len(relation) == 2:
   197         # adds the "rtags" attribute
    89                     rtype, ttype = relation
   198         eclass.rtags = RelationTags(eclass, tagdefs)
    90                     ttype = bw_normalize_etype(ttype) # XXX bw compat
   199         return eclass
    91                     dispatch_rtags(tags, rtype, 'subject', etype, ttype)
       
    92                     dispatch_rtags(tags, rtype, 'object', ttype, etype)
       
    93                 elif len(relation) == 3:
       
    94                     rtype, ttype, role = relation
       
    95                     ttype = bw_normalize_etype(ttype)
       
    96                     if role == 'subject':
       
    97                         dispatch_rtags(tags, rtype, 'subject', etype, ttype)
       
    98                     else:
       
    99                         dispatch_rtags(tags, rtype, 'object', ttype, etype)
       
   100                 else:
       
   101                     raise ValueError('bad rtag definition (%r)' % (relation,))
       
   102         if 'widgets' in classdict and AutomaticEntityForm is not None:
       
   103             etype = classdict['id']
       
   104             warn('%s: widgets is deprecated' % name, DeprecationWarning)
       
   105             for relation, wdgname in classdict.pop('widgets').iteritems():
       
   106                 AutomaticEntityForm.rwidgets.set_rtag(wdgname, rtype, 'subject', etype)
       
   107         return super(metaentity, mcs).__new__(mcs, name, bases, classdict)
   200 
   108 
   201 
   109 
   202 class Entity(AppRsetObject, dict):
   110 class Entity(AppRsetObject, dict):
   203     """an entity instance has e_schema automagically set on
   111     """an entity instance has e_schema automagically set on
   204     the class and instances has access to their issuing cursor.
   112     the class and instances has access to their issuing cursor.
   271                 attr = 'reverse_%s' % rschema.type
   179                 attr = 'reverse_%s' % rschema.type
   272                 setattr(cls, attr, ObjectRelation(rschema))
   180                 setattr(cls, attr, ObjectRelation(rschema))
   273         if mixins:
   181         if mixins:
   274             cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
   182             cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
   275             cls.debug('plugged %s mixins on %s', mixins, etype)
   183             cls.debug('plugged %s mixins on %s', mixins, etype)
   276         cls.rtags.__initialize__()
       
   277     
   184     
   278     @classmethod
   185     @classmethod
   279     def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
   186     def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
   280                   settype=True, ordermethod='fetch_order'):
   187                   settype=True, ordermethod='fetch_order'):
   281         """return a rql to fetch all entities of the class type"""
   188         """return a rql to fetch all entities of the class type"""