diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/entities/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/entities/__init__.py Sat Jan 16 13:48:51 2016 +0100 @@ -0,0 +1,208 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""base application's entities class implementation: `AnyEntity`""" + +__docformat__ = "restructuredtext en" + +from warnings import warn + +from six import text_type, string_types + +from logilab.common.decorators import classproperty +from logilab.common.deprecation import deprecated + +from cubicweb import Unauthorized +from cubicweb.entity import Entity + + +class AnyEntity(Entity): + """an entity instance has e_schema automagically set on the class and + instances have access to their issuing cursor + """ + __regid__ = 'Any' + + @classproperty + def cw_etype(cls): + """entity type as a unicode string""" + return text_type(cls.__regid__) + + @classmethod + def cw_create_url(cls, req, **kwargs): + """ return the url of the entity creation form for this entity type""" + return req.build_url('add/%s' % cls.__regid__, **kwargs) + + @classmethod + @deprecated('[3.22] use cw_fti_index_rql_limit instead') + def cw_fti_index_rql_queries(cls, req): + """return the list of rql queries to fetch entities to FT-index + + The default is to fetch all entities at once and to prefetch + indexable attributes but one could imagine iterating over + "smaller" resultsets if the table is very big or returning + a subset of entities that match some business-logic condition. + """ + restrictions = ['X is %s' % cls.__regid__] + selected = ['X'] + for attrschema in sorted(cls.e_schema.indexable_attributes()): + varname = attrschema.type.upper() + restrictions.append('X %s %s' % (attrschema, varname)) + selected.append(varname) + return ['Any %s WHERE %s' % (', '.join(selected), + ', '.join(restrictions))] + + @classmethod + def cw_fti_index_rql_limit(cls, req, limit=1000): + """generate rsets of entities to FT-index + + By default, each successive result set is limited to 1000 entities + """ + if cls.cw_fti_index_rql_queries.__func__ != AnyEntity.cw_fti_index_rql_queries.__func__: + warn("[3.22] cw_fti_index_rql_queries is replaced by cw_fti_index_rql_limit", + DeprecationWarning) + for rql in cls.cw_fti_index_rql_queries(req): + yield req.execute(rql) + return + restrictions = ['X is %s' % cls.__regid__] + selected = ['X'] + start = 0 + for attrschema in sorted(cls.e_schema.indexable_attributes()): + varname = attrschema.type.upper() + restrictions.append('X %s %s' % (attrschema, varname)) + selected.append(varname) + while True: + q_restrictions = restrictions + ['X eid > %s' % start] + rset = req.execute('Any %s ORDERBY X LIMIT %s WHERE %s' % + (', '.join(selected), + limit, + ', '.join(q_restrictions))) + if rset: + start = rset[-1][0] + yield rset + else: + break + + # meta data api ########################################################### + + def dc_title(self): + """return a suitable *unicode* title for this entity""" + for rschema, attrschema in self.e_schema.attribute_definitions(): + if rschema.meta: + continue + value = self.cw_attr_value(rschema.type) + if value is not None: + # make the value printable (dates, floats, bytes, etc.) + return self.printable_value(rschema.type, value, attrschema.type, + format='text/plain') + return u'%s #%s' % (self.dc_type(), self.eid) + + def dc_long_title(self): + """return a more detailled title for this entity""" + return self.dc_title() + + def dc_description(self, format='text/plain'): + """return a suitable description for this entity""" + if 'description' in self.e_schema.subjrels: + return self.printable_value('description', format=format) + return u'' + + def dc_authors(self): + """return a suitable description for the author(s) of the entity""" + try: + return ', '.join(u.name() for u in self.owned_by) + except Unauthorized: + return u'' + + def dc_creator(self): + """return a suitable description for the creator of the entity""" + if self.creator: + return self.creator.name() + return u'' + + def dc_date(self, date_format=None):# XXX default to ISO 8601 ? + """return latest modification date of this entity""" + return self._cw.format_date(self.modification_date, date_format=date_format) + + def dc_type(self, form=''): + """return the display name for the type of this entity (translated)""" + return self.e_schema.display_name(self._cw, form) + + def dc_language(self): + """return language used by this entity (translated)""" + # check if entities has internationalizable attributes + # XXX one is enough or check if all String attributes are internationalizable? + for rschema, attrschema in self.e_schema.attribute_definitions(): + if rschema.rdef(self.e_schema, attrschema).internationalizable: + return self._cw._(self._cw.user.property_value('ui.language')) + return self._cw._(self._cw.vreg.property_value('ui.language')) + + @property + def creator(self): + """return the CWUser entity which has created this entity, or None if + unknown or if the curent user doesn't has access to this euser + """ + try: + return self.created_by[0] + except (Unauthorized, IndexError): + return None + + # abstractions making the whole things (well, some at least) working ###### + + def sortvalue(self, rtype=None): + """return a value which can be used to sort this entity or given + entity's attribute + """ + if rtype is None: + return self.dc_title().lower() + value = self.cw_attr_value(rtype) + # do not restrict to `unicode` because Bytes will return a `str` value + if isinstance(value, string_types): + return self.printable_value(rtype, format='text/plain').lower() + return value + + +def fetch_config(fetchattrs, mainattr=None, pclass=AnyEntity, order='ASC'): + """function to ease basic configuration of an entity class ORM. Basic usage + is: + + .. sourcecode:: python + + class MyEntity(AnyEntity): + + fetch_attrs, cw_fetch_order = fetch_config(['attr1', 'attr2']) + # uncomment line below if you want the same sorting for 'unrelated' entities + # cw_fetch_unrelated_order = cw_fetch_order + + Using this, when using ORM methods retrieving this type of entity, 'attr1' + and 'attr2' will be automatically prefetched and results will be sorted on + 'attr1' ascending (ie the first attribute in the list). + + This function will automatically add to fetched attributes those defined in + parent class given using the `pclass` argument. + + Also, You can use `mainattr` and `order` argument to have a different + sorting. + """ + if pclass is not None: + fetchattrs += pclass.fetch_attrs + if mainattr is None: + mainattr = fetchattrs[0] + @classmethod + def fetch_order(cls, select, attr, var): + if attr == mainattr: + select.add_sort_var(var, order=='ASC') + return fetchattrs, fetch_order