[pkg] merge 3.27
Require python >= 3.6 since recent typing notations require >= 3.6
--- a/cubicweb/__pkginfo__.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/__pkginfo__.py Tue Mar 17 13:34:54 2020 +0100
@@ -22,8 +22,8 @@
modname = distname = "cubicweb"
-numversion = (3, 27, 2)
-version = '.'.join(str(num) for num in numversion)
+numversion = (3, 28, 0)
+version = '.'.join(str(num) for num in numversion) + '.dev0'
description = "a repository of entities / relations for knowledge management"
author = "Logilab"
--- a/cubicweb/cwvreg.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/cwvreg.py Tue Mar 17 13:34:54 2020 +0100
@@ -550,7 +550,7 @@
return RQLHelper(self.schema,
special_relations={'eid': 'uid', 'has_text': 'fti'})
- def solutions(self, req, rqlst, args):
+ def compute_var_types(self, req, rqlst, args):
def type_from_eid(eid, req=req):
return req.entity_type(eid)
return self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args)
@@ -558,7 +558,7 @@
def parse(self, req, rql, args=None):
rqlst = self.rqlhelper.parse(rql)
try:
- self.solutions(req, rqlst, args)
+ self.compute_var_types(req, rqlst, args)
except UnknownEid:
for select in rqlst.children:
select.solutions = []
--- a/cubicweb/devtools/__init__.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/devtools/__init__.py Tue Mar 17 13:34:54 2020 +0100
@@ -624,14 +624,14 @@
def init_test_database(self):
"""initialize a fresh postgresql database used for testing purpose"""
from cubicweb.server import init_repository
- from cubicweb.server.serverctl import system_source_cnx, createdb
+ from cubicweb.server.serverctl import source_cnx, createdb
# connect on the dbms system base to create our base
try:
self._drop(self.system_source['db-name'])
createdb(self.helper, self.system_source, self.dbcnx, self.cursor)
self.dbcnx.commit()
- cnx = system_source_cnx(self.system_source, special_privs='LANGUAGE C',
- interactive=False)
+ cnx = source_cnx(self.system_source, special_privs='LANGUAGE C',
+ interactive=False)
templcursor = cnx.cursor()
try:
# XXX factorize with db-create code
--- a/cubicweb/devtools/repotest.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/devtools/repotest.py Tue Mar 17 13:34:54 2020 +0100
@@ -209,7 +209,7 @@
def _prepare_plan(self, cnx, rql, kwargs=None):
rqlst = self.repo.vreg.rqlhelper.parse(rql, annotate=True)
- self.repo.vreg.solutions(cnx, rqlst, kwargs)
+ self.repo.vreg.compute_var_types(cnx, rqlst, kwargs)
if rqlst.TYPE == 'select':
self.repo.vreg.rqlhelper.annotate(rqlst)
for select in rqlst.children:
--- a/cubicweb/entities/adapters.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/entities/adapters.py Tue Mar 17 13:34:54 2020 +0100
@@ -20,18 +20,123 @@
"""
from cubicweb import _
+from hashlib import sha1
from itertools import chain
+from rdflib import URIRef, Literal, namespace as rdflib_namespace
from logilab.mtconverter import TransformError
-from logilab.common.decorators import cached
+from logilab.common.decorators import cached, cachedproperty
-from cubicweb import (Unauthorized, ValidationError, view, ViolatedConstraint,
+from cubicweb import rdf
+from cubicweb import (Unauthorized, ValidationError, ViolatedConstraint,
UniqueTogetherError)
-from cubicweb.schema import constraint_name_for
+from cubicweb.entity import EntityAdapter
+from cubicweb.schema import constraint_name_for, VIRTUAL_RTYPES
from cubicweb.predicates import is_instance, relation_possible, match_exception
-class IDublinCoreAdapter(view.EntityAdapter):
+class EntityRDFAdapter(EntityAdapter):
+ """EntityRDFAdapter is to be specialized for each entity that wants to
+ be converted to RDF using the mechanism from cubicweb.rdf
+ """
+ __regid__ = "rdf"
+
+ SKIP_RTYPES = VIRTUAL_RTYPES | set(['cwuri', 'is', 'is_instance_of'])
+
+ def __init__(self, _cw, **kwargs):
+ super().__init__(_cw, **kwargs)
+ self.entity.complete()
+ self.used_namespaces = {}
+
+ def _use_namespace(self, prefix, base_url=None):
+ if prefix not in self.used_namespaces:
+ if base_url is not None:
+ if prefix in rdf.NAMESPACES:
+ raise KeyError('prefix redefinition not allowed: '
+ f'"{prefix}" already exists as "{rdf.NAMESPACES[prefix]}"')
+ ns = rdflib_namespace.Namespace(base_url)
+ else:
+ ns = rdf.NAMESPACES[prefix]
+ self.used_namespaces[prefix] = ns
+ elif base_url is not None:
+ if self.used_namespaces[prefix] != rdflib_namespace.Namespace(base_url):
+ raise ValueError('prefix redefinition not allowed: '
+ f'"{prefix}" already exists as "{self.used_namespaces[prefix]}"')
+ return self.used_namespaces[prefix]
+
+ @cachedproperty
+ def uri(self):
+ return URIRef(self.entity.cwuri)
+
+ def triples(self):
+ """return sequence of 3-tuple of rdflib identifiers"""
+ yield from self.cw_triples()
+ yield from self.dc_triples()
+
+ def cw_triples(self):
+ RDF = self._use_namespace('rdf')
+ CW = self._use_namespace('cubicweb')
+
+ yield (self.uri, RDF.type, CW[self.entity.e_schema.type])
+
+ for rschema, targettypes, role in self.entity.e_schema.relation_definitions('relation'):
+ rtype = rschema.type
+ if rtype in self.SKIP_RTYPES or rtype.endswith('_permission'):
+ continue
+ for targetype in targettypes:
+ # if rschema is an attribute
+ if targetype.final:
+ try:
+ value = self.entity.cw_attr_cache[rtype]
+ except KeyError:
+ continue
+ if value is not None:
+ yield (self.uri, CW[rtype], Literal(value))
+ # else if rschema is a relation
+ else:
+ for related in self.entity.related(rtype, role, entities=True, safe=True):
+ if role == 'subject':
+ yield (self.uri, CW[rtype], URIRef(related.cwuri))
+ else:
+ yield (URIRef(related.cwuri), CW[rtype], self.uri)
+
+ def dc_triples(self):
+ dc_entity = self.entity.cw_adapt_to('IDublinCore')
+ DC = self._use_namespace('dc')
+ yield (self.uri, DC.title, Literal(dc_entity.long_title())) # or title() ?
+ desc = dc_entity.description()
+ if desc:
+ yield (self.uri, DC.description, Literal(desc))
+ creator = dc_entity.creator() # use URI instead of Literal ?
+ if creator:
+ yield (self.uri, DC.creator, Literal(creator))
+ yield (self.uri, DC.date, Literal(dc_entity.date()))
+ yield (self.uri, DC.type, Literal(dc_entity.type()))
+ yield (self.uri, DC.language, Literal(dc_entity.language()))
+
+
+class CWUserRDFAdapter(EntityRDFAdapter):
+ __select__ = is_instance("CWUser")
+
+ def triples(self):
+ yield from super().triples()
+ yield from self.foaf_triples()
+
+ def foaf_triples(self):
+ RDF = self._use_namespace('rdf')
+ FOAF = self._use_namespace('foaf')
+ yield (self.uri, RDF.type, FOAF.Person)
+ if self.entity.surname:
+ yield (self.uri, FOAF.familyName, Literal(self.entity.surname))
+ if self.entity.firstname:
+ yield (self.uri, FOAF.givenName, Literal(self.entity.firstname))
+ emailaddr = self.entity.cw_adapt_to("IEmailable").get_email()
+ if emailaddr:
+ email_digest = sha1(emailaddr.encode("utf-8")).hexdigest()
+ yield (self.uri, FOAF.mbox_sha1sum, Literal(email_digest))
+
+
+class IDublinCoreAdapter(EntityAdapter):
__regid__ = 'IDublinCore'
__select__ = is_instance('Any')
@@ -60,7 +165,7 @@
return self.entity.printable_value('description', format=format)
return u''
- def authors(self):
+ def authors(self): # XXX is this part of DC ?
"""Return a suitable description for the author(s) of the entity"""
try:
return u', '.join(u.name() for u in self.entity.owned_by)
@@ -88,12 +193,12 @@
# check if entities has internationalizable attributes
# XXX one is enough or check if all String attributes are internationalizable?
for rschema, attrschema in eschema.attribute_definitions():
- if rschema.rdef(eschema, attrschema).internationalizable:
+ if getattr(rschema.rdef(eschema, attrschema), 'internationalizable', False):
return self._cw._(self._cw.user.property_value('ui.language'))
return self._cw._(self._cw.vreg.property_value('ui.language'))
-class IEmailableAdapter(view.EntityAdapter):
+class IEmailableAdapter(EntityAdapter):
__regid__ = 'IEmailable'
__select__ = relation_possible('primary_email') | relation_possible('use_email')
@@ -126,7 +231,7 @@
for attr in self.allowed_massmail_keys())
-class INotifiableAdapter(view.EntityAdapter):
+class INotifiableAdapter(EntityAdapter):
__regid__ = 'INotifiable'
__select__ = is_instance('Any')
@@ -145,7 +250,7 @@
return ()
-class IFTIndexableAdapter(view.EntityAdapter):
+class IFTIndexableAdapter(EntityAdapter):
"""standard adapter to handle fulltext indexing
.. automethod:: cubicweb.entities.adapters.IFTIndexableAdapter.fti_containers
@@ -226,7 +331,7 @@
maindict.setdefault(weight, []).extend(words)
-class IDownloadableAdapter(view.EntityAdapter):
+class IDownloadableAdapter(EntityAdapter):
"""interface for downloadable entities"""
__regid__ = 'IDownloadable'
__abstract__ = True
@@ -256,7 +361,7 @@
# XXX should propose to use two different relations for children/parent
-class ITreeAdapter(view.EntityAdapter):
+class ITreeAdapter(EntityAdapter):
"""This adapter provides a tree interface.
It has to be overriden to be configured using the tree_relation,
@@ -412,7 +517,7 @@
return path
-class ISerializableAdapter(view.EntityAdapter):
+class ISerializableAdapter(EntityAdapter):
"""Adapter to serialize an entity to a bare python structure that may be
directly serialized to e.g. JSON.
"""
@@ -441,7 +546,7 @@
# error handling adapters ######################################################
-class IUserFriendlyError(view.EntityAdapter):
+class IUserFriendlyError(EntityAdapter):
__regid__ = 'IUserFriendlyError'
__abstract__ = True
--- a/cubicweb/entities/wfobjs.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/entities/wfobjs.py Tue Mar 17 13:34:54 2020 +0100
@@ -24,7 +24,7 @@
from logilab.common.decorators import cached, clear_cache
from cubicweb.entities import AnyEntity, fetch_config
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.predicates import relation_possible
--- a/cubicweb/entity.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/entity.py Tue Mar 17 13:34:54 2020 +0100
@@ -1118,7 +1118,7 @@
etypecls.fetch_rqlst(self._cw.user, select, searchedvar,
ordermethod=ordermethod)
# from now on, we need variable type resolving
- self._cw.vreg.solutions(self._cw, select, args)
+ self._cw.vreg.compute_var_types(self._cw, select, args)
# insert RQL expressions for schema constraints into the rql syntax tree
if vocabconstraints:
cstrcls = (RQLVocabularyConstraint, RQLConstraint)
@@ -1314,6 +1314,28 @@
def __set__(self, eobj, value):
raise NotImplementedError
+# entity adapters #############################################################
+
+class Adapter(AppObject):
+ """base class for adapters"""
+ __registry__ = 'adapters'
+
+
+class EntityAdapter(Adapter):
+ """base class for entity adapters (eg adapt an entity to an interface)
+
+ An example would be:
+
+ >>> some_entity.cw_adapt_to('IDownloadable')
+ """
+ def __init__(self, _cw, **kwargs):
+ try:
+ self.entity = kwargs.pop('entity')
+ except KeyError:
+ self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
+ kwargs.get('col') or 0)
+ Adapter.__init__(self, _cw, **kwargs)
+
from logging import getLogger
from cubicweb import set_log_methods
--- a/cubicweb/pyramid/__init__.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/pyramid/__init__.py Tue Mar 17 13:34:54 2020 +0100
@@ -231,6 +231,8 @@
cwconfig = config.registry.get('cubicweb.config')
repo = config.registry.get('cubicweb.repository')
+ config.include('cubicweb.pyramid.rest_api')
+
if repo is not None:
if cwconfig is None:
config.registry['cubicweb.config'] = cwconfig = repo.config
--- a/cubicweb/pyramid/predicates.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/pyramid/predicates.py Tue Mar 17 13:34:54 2020 +0100
@@ -21,22 +21,60 @@
"""Contains predicates used in Pyramid views.
"""
+from cubicweb._exceptions import UnknownEid
-class MatchIsETypePredicate(object):
- """A predicate that match if a given etype exist in schema.
+
+class MatchIsEIDPredicate(object):
+ """A predicate that match if a given eid exist in the database.
"""
def __init__(self, matchname, config):
self.matchname = matchname
def text(self):
- return 'match_is_etype = %s' % self.matchname
+ return 'match_is_eid = %s' % self.matchname
phash = text
def __call__(self, info, request):
- return info['match'][self.matchname].lower() in \
- request.registry['cubicweb.registry'].case_insensitive_etypes
+ try:
+ eid = int(info['match'][self.matchname])
+ except ValueError:
+ return False
+
+ try:
+ request.cw_cnx.entity_from_eid(eid)
+ except UnknownEid:
+ return False
+ return True
+
+
+class MatchIsETypeAndEIDPredicate(object):
+ """A predicate that match if a given eid exist in the database and if the
+ etype of the entity same as the one given in the URL
+ """
+ def __init__(self, matchnames, config):
+ self.match_etype, self.match_eid = matchnames
+
+ def text(self):
+ return f"match_is_etype_and_eid = {self.match_etype}/{self.match_eid}"
+
+ phash = text
+
+ def __call__(self, info, request):
+ try:
+ eid = int(info['match'][self.match_eid])
+ except ValueError:
+ return False
+
+ try:
+ entity = request.cw_cnx.entity_from_eid(eid)
+ except UnknownEid:
+ return False
+
+ etype = info['match'][self.match_etype]
+ return entity.__regid__.lower() == etype.lower()
def includeme(config):
- config.add_route_predicate('match_is_etype', MatchIsETypePredicate)
+ config.add_route_predicate('match_is_eid', MatchIsEIDPredicate)
+ config.add_route_predicate('match_is_etype_and_eid', MatchIsETypeAndEIDPredicate)
--- a/cubicweb/pyramid/resources.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/pyramid/resources.py Tue Mar 17 13:34:54 2020 +0100
@@ -18,84 +18,33 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Pyramid resource definitions for CubicWeb."""
+from pyramid.httpexceptions import HTTPNotFound
-from rql import TypeResolverException
-
-from pyramid.decorator import reify
-from pyramid.httpexceptions import HTTPNotFound
+from cubicweb import rdf
-class EntityResource(object):
-
- """A resource class for an entity. It provide method to retrieve an entity
- by eid.
- """
-
- @classmethod
- def from_eid(cls):
- def factory(request):
- return cls(request, None, None, request.matchdict['eid'])
- return factory
-
- def __init__(self, request, cls, attrname, value):
- self.request = request
- self.cls = cls
- self.attrname = attrname
- self.value = value
-
- @reify
- def rset(self):
- req = self.request.cw_request
- if self.cls is None:
- return req.execute('Any X WHERE X eid %(x)s',
- {'x': int(self.value)})
- st = self.cls.fetch_rqlst(self.request.cw_cnx.user, ordermethod=None)
- st.add_constant_restriction(st.get_variable('X'), self.attrname,
- 'x', 'Substitute')
- if self.attrname == 'eid':
- try:
- rset = req.execute(st.as_string(), {'x': int(self.value)})
- except (ValueError, TypeResolverException):
- # conflicting eid/type
- raise HTTPNotFound()
- else:
- rset = req.execute(st.as_string(), {'x': self.value})
- return rset
+def negociate_mime_type(request, possible_mimetypes):
+ accepted_headers_by_weight = sorted(
+ request.accept.parsed or [], key=lambda h: h[1], reverse=True
+ )
+ mime_type_negociated = None
+ for parsed_header in accepted_headers_by_weight:
+ accepted_mime_type = parsed_header[0]
+ if accepted_mime_type in possible_mimetypes:
+ mime_type_negociated = accepted_mime_type
+ break
+ return mime_type_negociated
-class ETypeResource(object):
-
- """A resource for etype.
- """
- @classmethod
- def from_match(cls, matchname):
- def factory(request):
- return cls(request, request.matchdict[matchname])
- return factory
-
- def __init__(self, request, etype):
- vreg = request.registry['cubicweb.registry']
-
- self.request = request
- self.etype = vreg.case_insensitive_etypes[etype.lower()]
- self.cls = vreg['etypes'].etype_class(self.etype)
+def rdf_context_from_eid(request):
+ mime_type = negociate_mime_type(request, rdf.RDF_MIMETYPE_TO_FORMAT)
+ if mime_type is None:
+ raise HTTPNotFound()
+ entity = request.cw_request.entity_from_eid(request.matchdict['eid'])
+ return RDFResource(entity, mime_type)
- def __getitem__(self, value):
- # Try eid first, then rest attribute as for URL path evaluation
- # mecanism in cubicweb.web.views.urlpublishing.
- for attrname in ('eid', self.cls.cw_rest_attr_info()[0]):
- resource = EntityResource(self.request, self.cls, attrname, value)
- try:
- rset = resource.rset
- except HTTPNotFound:
- continue
- if rset.rowcount:
- return resource
- raise KeyError(value)
- @reify
- def rset(self):
- rql = self.cls.fetch_rql(self.request.cw_cnx.user)
- rset = self.request.cw_request.execute(rql)
- return rset
+class RDFResource:
+ def __init__(self, entity, mime_type):
+ self.entity = entity
+ self.mime_type = mime_type
--- a/cubicweb/pyramid/rest_api.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/pyramid/rest_api.py Tue Mar 17 13:34:54 2020 +0100
@@ -20,26 +20,44 @@
"""Experimental REST API for CubicWeb using Pyramid."""
-from __future__ import absolute_import
-
+import rdflib
from pyramid.view import view_config
-from cubicweb.pyramid.resources import EntityResource, ETypeResource
+from pyramid.response import Response
+
+from cubicweb import rdf
+from cubicweb.pyramid.resources import rdf_context_from_eid, RDFResource
@view_config(
- route_name='cwentities',
- context=EntityResource,
- request_method='DELETE')
-def delete_entity(context, request):
- context.rset.one().cw_delete()
- request.response.status_int = 204
- return request.response
+ route_name='one_entity',
+ context=RDFResource,
+)
+@view_config(
+ route_name='one_entity_eid',
+ context=RDFResource,
+)
+def view_entity_as_rdf(context, request):
+ graph = rdflib.ConjunctiveGraph()
+ rdf.add_entity_to_graph(graph, context.entity)
+ rdf_format = rdf.RDF_MIMETYPE_TO_FORMAT[context.mime_type]
+ response = Response(graph.serialize(format=rdf_format))
+ response.content_type = context.mime_type
+ return response
def includeme(config):
config.include('.predicates')
config.add_route(
- 'cwentities', '/{etype}/*traverse',
- factory=ETypeResource.from_match('etype'), match_is_etype='etype')
+ 'one_entity',
+ '/{etype}/{eid}',
+ factory=rdf_context_from_eid,
+ match_is_etype_and_eid=('etype', 'eid'),
+ )
+ config.add_route(
+ 'one_entity_eid',
+ '/{eid}',
+ factory=rdf_context_from_eid,
+ match_is_eid='eid'
+ )
config.scan(__name__)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/rdf.py Tue Mar 17 13:34:54 2020 +0100
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# copyright 2019 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+from rdflib import plugin, namespace
+import rdflib_jsonld # noqa
+
+plugin.register("jsonld", plugin.Serializer, "rdflib_jsonld.serializer", "JsonLDSerializer")
+
+RDF_MIMETYPE_TO_FORMAT = {
+ 'application/rdf+xml': 'xml',
+ 'text/turtle': 'turtle',
+ 'text/n3': 'n3',
+ 'application/n-quads': 'nquads',
+ 'application/n-triples': 'nt',
+ 'application/trig': 'trig',
+ 'application/ld+json': 'json-ld',
+}
+
+NAMESPACES = {
+ "rdf": namespace.RDF,
+ "rdfs": namespace.RDFS,
+ "owl": namespace.OWL,
+ "xsd": namespace.XSD,
+ "skos": namespace.SKOS,
+ "void": namespace.VOID,
+ "dc": namespace.DC,
+ "dcterms": namespace.DCTERMS,
+ "foaf": namespace.FOAF,
+ "doap": namespace.DOAP,
+ "schema": namespace.Namespace("http://schema.org/"),
+ "cubicweb": namespace.Namespace("http://ns.cubicweb.org/cubicweb/0.0/")
+}
+
+
+def add_entity_to_graph(graph, entity):
+ adapter = entity.cw_adapt_to("rdf")
+ if adapter:
+ for triple in adapter.triples():
+ graph.add(triple)
+ for prefix, rdfns in adapter.used_namespaces.items():
+ graph.bind(prefix, rdfns)
--- a/cubicweb/rqlrewrite.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/rqlrewrite.py Tue Mar 17 13:34:54 2020 +0100
@@ -245,7 +245,7 @@
vreg = session.vreg
self.schema = vreg.schema
self.annotate = vreg.rqlhelper.annotate
- self._compute_solutions = vreg.solutions
+ self._compute_solutions = vreg.compute_var_types
def compute_solutions(self):
self.annotate(self.select)
--- a/cubicweb/server/hook.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/hook.py Tue Mar 17 13:34:54 2020 +0100
@@ -248,6 +248,7 @@
from logging import getLogger
from itertools import chain
+from typing import Union, Tuple
from logilab.common.decorators import classproperty, cached
from logilab.common.logging_ext import set_log_methods
@@ -521,8 +522,8 @@
"""
__select__ = enabled_category()
# set this in derivated classes
- events = None
- category = None
+ events: Union[None, Tuple[str], Tuple[str, str]] = None
+ category: Union[None, str] = None
order = 0
# stop pylint from complaining about missing attributes in Hooks classes
eidfrom = eidto = entity = rtype = repo = None
--- a/cubicweb/server/querier.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/querier.py Tue Mar 17 13:34:54 2020 +0100
@@ -36,9 +36,9 @@
from cubicweb.utils import QueryCache, RepeatList
from cubicweb.misc.source_highlight import highlight_terminal
-from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
+from cubicweb.server.rqlannotation import RQLAnnotator, set_qdata
from cubicweb.server.ssplanner import (READ_ONLY_RTYPES, add_types_restriction,
- SSPlanner)
+ prepare_plan)
from cubicweb.server.edition import EditedEntity
from cubicweb.statsd_logger import statsd_timeit, statsd_c
@@ -154,20 +154,15 @@
class ExecutionPlan(object):
"""the execution model of a rql query, composed of querier steps"""
- def __init__(self, querier, rqlst, args, cnx):
+ def __init__(self, schema, rqlst, args, cnx):
+ self.schema = schema
# original rql syntax tree
self.rqlst = rqlst
self.args = args or {}
# cnx executing the query
self.cnx = cnx
- # quick reference to the system source
- self.syssource = cnx.repo.system_source
# execution steps
self.steps = []
- # various resource accesors
- self.querier = querier
- self.schema = querier.schema
- self.rqlhelper = cnx.vreg.rqlhelper
# tracing token for debugging
self.rql_query_tracing_token = None
@@ -176,7 +171,7 @@
self.steps.append(step)
def sqlexec(self, sql, args=None):
- return self.syssource.sqlexec(self.cnx, sql, args)
+ return self.cnx.repo.system_source.sqlexec(self.cnx, sql, args)
def execute(self):
"""execute a plan and return resulting rows"""
@@ -215,8 +210,8 @@
else:
noinvariant = ()
if cached is None:
- self.rqlhelper.simplify(union)
- self.querier.sqlgen_annotate(union)
+ self.cnx.vreg.rqlhelper.simplify(union)
+ RQLAnnotator(self.schema).annotate(union)
set_qdata(self.schema.rschema, union, noinvariant)
if union.has_text_query:
self.cache_key = None
@@ -314,7 +309,7 @@
sol[newvarname] = nvartype
select.clean_solutions(solutions)
add_types_restriction(self.schema, select)
- self.rqlhelper.annotate(rqlst)
+ self.cnx.vreg.rqlhelper.annotate(rqlst)
self.preprocess(rqlst, security=False)
return rqlst
@@ -323,8 +318,8 @@
"""an execution model specific to the INSERT rql query
"""
- def __init__(self, querier, rqlst, args, cnx):
- ExecutionPlan.__init__(self, querier, rqlst, args, cnx)
+ def __init__(self, schema, rqlst, args, cnx):
+ ExecutionPlan.__init__(self, schema, rqlst, args, cnx)
# save originally selected variable, we may modify this
# dictionary for substitution (query parameters)
self.selected = rqlst.selection
@@ -478,10 +473,6 @@
def set_schema(self, schema):
self.schema = schema
self.clear_caches()
- # rql planner
- self._planner = SSPlanner(schema, self._repo.vreg.rqlhelper)
- # sql generation annotator
- self.sqlgen_annotate = SQLGenAnnotator(schema).annotate
def clear_caches(self, eids=None, etypes=None):
if eids is None:
@@ -496,8 +487,8 @@
def plan_factory(self, rqlst, args, cnx):
"""create an execution plan for an INSERT RQL query"""
if rqlst.TYPE == 'insert':
- return InsertPlan(self, rqlst, args, cnx)
- return ExecutionPlan(self, rqlst, args, cnx)
+ return InsertPlan(self.schema, rqlst, args, cnx)
+ return ExecutionPlan(self.schema, rqlst, args, cnx)
@statsd_timeit
def execute(self, cnx, rql, args=None, build_descr=True):
@@ -555,7 +546,7 @@
plan = self.plan_factory(rqlst, args, cnx)
plan.cache_key = cachekey
plan.rql_query_tracing_token = str(uuid.uuid4())
- self._planner.build_plan(plan)
+ prepare_plan(plan, self.schema, self._repo.vreg.rqlhelper)
query_debug_informations = {
"rql": rql,
@@ -567,7 +558,6 @@
}
start = time.time()
-
# execute the plan
try:
results = plan.execute()
@@ -634,7 +624,7 @@
# some cache usage stats
self.cache_hit, self.cache_miss = 0, 0
# rql parsing / analysing helper
- self.solutions = repo.vreg.solutions
+ self.compute_var_types = repo.vreg.compute_var_types
rqlhelper = repo.vreg.rqlhelper
# set backend on the rql helper, will be used for function checking
rqlhelper.backend = repo.config.system_source_config['db-driver']
@@ -677,7 +667,7 @@
# which are eids. Notice that if you may not need `eidkeys`, we
# have to compute solutions anyway (kept as annotation on the
# tree)
- eidkeys = self.solutions(cnx, rqlst, args)
+ eidkeys = self.compute_var_types(cnx, rqlst, args)
if args and rql not in self._ck_cache:
self._ck_cache[rql] = eidkeys
if eidkeys:
--- a/cubicweb/server/rqlannotation.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/rqlannotation.py Tue Mar 17 13:34:54 2020 +0100
@@ -117,7 +117,8 @@
var._q_invariant = False
-class SQLGenAnnotator(object):
+class RQLAnnotator(object):
+ """Annotate the RQL abstract syntax tree to inform the SQL generation"""
def __init__(self, schema):
self.schema = schema
--- a/cubicweb/server/serverctl.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/serverctl.py Tue Mar 17 13:34:54 2020 +0100
@@ -26,6 +26,7 @@
from logilab.common.configuration import Configuration, merge_options
from logilab.common.shellutils import ASK, generate_password
+from logilab.common.deprecation import deprecated
from logilab.database import get_db_helper, get_connection
@@ -96,6 +97,9 @@
return cnx
+@deprecated('[3.28] instead of system_source_cnx(source, True, **args) use '
+ 'source_cnx(source, get_db_helper(source[\'db-driver\']).system_database(), '
+ '**args)')
def system_source_cnx(source, dbms_system_base=False,
special_privs='CREATE/DROP DATABASE', interactive=True):
"""shortcut to get a connextion to the instance system database
@@ -118,8 +122,9 @@
import logilab.common as lgp
lgp.USE_MX_DATETIME = False
# connect on the dbms system base to create our base
- cnx = system_source_cnx(source, True, special_privs=special_privs,
- interactive=interactive)
+ system_db = get_db_helper(source['db-driver']).system_database()
+ cnx = source_cnx(source, system_db, special_privs=special_privs,
+ interactive=interactive)
# disable autocommit (isolation_level(1)) because DROP and
# CREATE DATABASE can't be executed in a transaction
set_isolation_level = getattr(cnx, 'set_isolation_level', None)
@@ -201,7 +206,7 @@
@contextmanager
def db_transaction(source, privilege):
"""Open a transaction to the instance database"""
- cnx = system_source_cnx(source, special_privs=privilege)
+ cnx = source_cnx(source, special_privs=privilege)
cursor = cnx.cursor()
try:
yield cursor
@@ -379,8 +384,8 @@
except BaseException:
dbcnx.rollback()
raise
- cnx = system_source_cnx(source, special_privs='CREATE LANGUAGE/SCHEMA',
- interactive=not automatic)
+ cnx = source_cnx(source, special_privs='CREATE LANGUAGE/SCHEMA',
+ interactive=not automatic)
cursor = cnx.cursor()
helper.init_fti_extensions(cursor)
namespace = source.get('db-namespace')
@@ -572,7 +577,7 @@
config = ServerConfiguration.config_for(appid)
source = config.system_source_config
set_owner = self.config.set_owner
- cnx = system_source_cnx(source, special_privs='GRANT')
+ cnx = source_cnx(source, special_privs='GRANT')
cursor = cnx.cursor()
schema = config.load_schema()
try:
--- a/cubicweb/server/sources/native.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/sources/native.py Tue Mar 17 13:34:54 2020 +0100
@@ -48,11 +48,10 @@
from cubicweb.server import schema2sql as y2sql
from cubicweb.server.utils import crypt_password, verify_and_update
from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn
-from cubicweb.server.rqlannotation import set_qdata
+from cubicweb.server.rqlannotation import RQLAnnotator, set_qdata
from cubicweb.server.hook import CleanupDeletedEidsCacheOp
from cubicweb.server.edition import EditedEntity
-from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results
-from cubicweb.server.sources.rql2sql import SQLGenerator
+from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results, rql2sql
from cubicweb.misc.source_highlight import highlight_terminal
from cubicweb.statsd_logger import statsd_timeit
@@ -260,7 +259,7 @@
class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
"""adapter for source using the native cubicweb schema (see below)
"""
- sqlgen_class = SQLGenerator
+
options = (
('db-driver',
{'type': 'string',
@@ -333,8 +332,8 @@
self.authentifiers.insert(0, EmailPasswordAuthentifier(self))
AbstractSource.__init__(self, repo, source_config, *args, **kwargs)
# sql generator
- self._rql_sqlgen = self.sqlgen_class(self.schema, self.dbhelper,
- ATTR_MAP.copy())
+ self._rql_sqlgen = rql2sql.SQLGenerator(self.schema, self.dbhelper,
+ ATTR_MAP.copy())
# full text index helper
self.do_fti = not repo.config['delay-full-text-indexation']
# sql queries cache
@@ -473,7 +472,7 @@
rqlst = self.repo.vreg.rqlhelper.parse(rql)
rqlst.restricted_vars = ()
rqlst.children[0].solutions = sols
- self.repo.querier.sqlgen_annotate(rqlst)
+ RQLAnnotator(self.repo.querier.schema).annotate(rqlst)
set_qdata(self.schema.rschema, rqlst, ())
return rqlst
--- a/cubicweb/server/ssplanner.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/ssplanner.py Tue Mar 17 13:34:54 2020 +0100
@@ -307,6 +307,11 @@
return self.build_select_plan(plan, union)
+def prepare_plan(plan, schema, rqlhelper):
+ """Add steps to a plan to prepare it for execution"""
+ return SSPlanner(schema, rqlhelper).build_plan(plan)
+
+
# execution steps and helper functions ########################################
class Step(object):
--- a/cubicweb/server/test/unittest_rqlannotation.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/test/unittest_rqlannotation.py Tue Mar 17 13:34:54 2020 +0100
@@ -22,14 +22,14 @@
from cubicweb.devtools.repotest import BaseQuerierTC
-class SQLGenAnnotatorTC(BaseQuerierTC):
+class RQLAnnotatorTC(BaseQuerierTC):
def setUp(self):
handler = devtools.get_test_db_handler(devtools.TestServerConfiguration('data', __file__))
handler.build_db_cache()
repo, _cnx = handler.get_repo_and_cnx()
self.__class__.repo = repo
- super(SQLGenAnnotatorTC, self).setUp()
+ super(RQLAnnotatorTC, self).setUp()
def test_0_1(self):
with self.admin_access.cnx() as cnx:
--- a/cubicweb/server/test/unittest_server_security.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/server/test/unittest_server_security.py Tue Mar 17 13:34:54 2020 +0100
@@ -44,7 +44,7 @@
nom = self.repo.schema['Personne'].rdef('nom')
with self.temporary_permissions((nom, {'read': ('users', 'managers')})):
with self.admin_access.repo_cnx() as cnx:
- self.repo.vreg.solutions(cnx, rqlst, None)
+ self.repo.vreg.compute_var_types(cnx, rqlst, None)
check_relations_read_access(cnx, rqlst, {})
with self.new_access(u'anon').repo_cnx() as cnx:
self.assertRaises(Unauthorized,
@@ -57,7 +57,7 @@
rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0]
with self.temporary_permissions(Personne={'read': ('users', 'managers')}):
with self.admin_access.repo_cnx() as cnx:
- self.repo.vreg.solutions(cnx, rqlst, None)
+ self.repo.vreg.compute_var_types(cnx, rqlst, None)
solution = rqlst.solutions[0]
localchecks = get_local_checks(cnx, rqlst, solution)
self.assertEqual({}, localchecks)
@@ -520,7 +520,7 @@
ERQLExpression('X owned_by U'))}):
with self.new_access(u'iaminusersgrouponly').repo_cnx() as cnx:
rqlst = self.repo.vreg.rqlhelper.parse('Any X WHERE X is_instance_of Societe')
- self.repo.vreg.solutions(cnx, rqlst, {})
+ self.repo.vreg.compute_var_types(cnx, rqlst, {})
self.repo.vreg.rqlhelper.annotate(rqlst)
plan = cnx.repo.querier.plan_factory(rqlst, {}, cnx)
plan.preprocess(rqlst)
--- a/cubicweb/test/unittest_predicates.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/test/unittest_predicates.py Tue Mar 17 13:34:54 2020 +0100
@@ -29,7 +29,7 @@
multi_lines_rset, score_entity, is_in_state,
rql_condition, relation_possible, match_form_params,
paginated_rset)
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.web import action
--- a/cubicweb/test/unittest_rqlrewrite.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/test/unittest_rqlrewrite.py Tue Mar 17 13:34:54 2020 +0100
@@ -58,7 +58,7 @@
schema = schema
@staticmethod
- def solutions(sqlcursor, rqlst, kwargs):
+ def compute_var_types(sqlcursor, rqlst, kwargs):
rqlhelper.compute_solutions(rqlst, {'eid': eid_func_map}, kwargs=kwargs)
class rqlhelper:
@@ -602,7 +602,7 @@
args = {}
union = parse(rql) # self.vreg.parse(rql, annotate=True)
with self.admin_access.repo_cnx() as cnx:
- self.vreg.solutions(cnx, union, args)
+ self.vreg.compute_var_types(cnx, union, args)
self.vreg.rqlhelper.annotate(union)
plan = self.repo.querier.plan_factory(union, args, cnx)
plan.preprocess(union)
--- a/cubicweb/test/unittest_vregistry.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/test/unittest_vregistry.py Tue Mar 17 13:34:54 2020 +0100
@@ -23,7 +23,7 @@
from cubicweb import CW_SOFTWARE_ROOT as BASE, devtools
from cubicweb.cwvreg import CWRegistryStore, UnknownProperty
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
class YesSchema:
--- a/cubicweb/view.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/view.py Tue Mar 17 13:34:54 2020 +0100
@@ -554,18 +554,7 @@
def domid(self):
return '%sComponent' % domid(self.__regid__)
-
-class Adapter(AppObject):
- """base class for adapters"""
- __registry__ = 'adapters'
-
-
-class EntityAdapter(Adapter):
- """base class for entity adapters (eg adapt an entity to an interface)"""
- def __init__(self, _cw, **kwargs):
- try:
- self.entity = kwargs.pop('entity')
- except KeyError:
- self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
- kwargs.get('col') or 0)
- Adapter.__init__(self, _cw, **kwargs)
+# EntityAdapter moved to cubicweb.entity ######################################
+from logilab.common.deprecation import class_moved
+from cubicweb import entity
+EntityAdapter = class_moved(entity.EntityAdapter) # cubicweb 3.28
--- a/cubicweb/web/action.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/action.py Tue Mar 17 13:34:54 2020 +0100
@@ -32,6 +32,7 @@
Many examples are available in :mod:`cubicweb.web.views.actions`.
"""
+from typing import Optional
from cubicweb import _
@@ -48,7 +49,7 @@
__registry__ = 'actions'
__select__ = match_search_state('normal')
order = 99
- category = 'moreactions'
+ category: Optional[str] = 'moreactions'
# actions in category 'moreactions' can specify a sub-menu in which they should be filed
submenu = None
--- a/cubicweb/web/test/unittest_idownloadable.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/test/unittest_idownloadable.py Tue Mar 17 13:34:54 2020 +0100
@@ -23,12 +23,12 @@
from pytz import utc
from cubicweb.devtools.testlib import CubicWebTC, real_error_handling
-from cubicweb import view
+from cubicweb.entity import EntityAdapter
from cubicweb.predicates import is_instance
from cubicweb.web import http_headers
-class IDownloadableUser(view.EntityAdapter):
+class IDownloadableUser(EntityAdapter):
__regid__ = 'IDownloadable'
__select__ = is_instance('CWUser')
--- a/cubicweb/web/views/basecontrollers.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/views/basecontrollers.py Tue Mar 17 13:34:54 2020 +0100
@@ -23,7 +23,7 @@
from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError,
AuthenticationError, UndoTransactionException,
- Forbidden)
+ Forbidden, rdf)
from cubicweb.utils import json_dumps
from cubicweb.predicates import (authenticated_user, anonymous_user,
match_form_params)
@@ -101,6 +101,10 @@
else:
rset = None
vid = req.form.get('vid') or vid_from_rset(req, rset, self._cw.vreg.schema)
+ if rset and len(rset) == 1:
+ for mimetype in rdf.RDF_MIMETYPE_TO_FORMAT:
+ req.headers_out.addRawHeader(
+ 'Link', "<%s>;rel=alternate;type=%s" % (rset.one().cwuri, mimetype))
try:
view = self._cw.vreg['views'].select(vid, req, rset=rset)
except ObjectNotFound:
--- a/cubicweb/web/views/calendar.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/views/calendar.py Tue Mar 17 13:34:54 2020 +0100
@@ -28,7 +28,8 @@
from cubicweb.utils import json_dumps, make_uid
from cubicweb.predicates import adaptable
-from cubicweb.view import EntityView, EntityAdapter
+from cubicweb.view import EntityView
+from cubicweb.entity import EntityAdapter
# useful constants & functions ################################################
--- a/cubicweb/web/views/editcontroller.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/views/editcontroller.py Tue Mar 17 13:34:54 2020 +0100
@@ -27,7 +27,7 @@
from rql.utils import rqlvar_maker
from cubicweb import _, ValidationError, UnknownEid
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.predicates import is_instance
from cubicweb.web import RequestError, NothingToEdit, ProcessFormError
from cubicweb.web.views import basecontrollers, autoform
--- a/cubicweb/web/views/ibreadcrumbs.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/views/ibreadcrumbs.py Tue Mar 17 13:34:54 2020 +0100
@@ -25,11 +25,11 @@
from logilab.mtconverter import xml_escape
from cubicweb import tags, uilib
-from cubicweb.entity import Entity
+from cubicweb.entity import Entity, EntityAdapter
from cubicweb.predicates import (is_instance, one_line_rset, adaptable,
one_etype_rset, multi_lines_rset, any_rset,
match_form_params)
-from cubicweb.view import EntityView, EntityAdapter
+from cubicweb.view import EntityView
from cubicweb.web.views import basecomponents
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
--- a/cubicweb/web/views/magicsearch.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/views/magicsearch.py Tue Mar 17 13:34:54 2020 +0100
@@ -454,7 +454,7 @@
user_rql = variables
select = parse(user_rql, print_errors=False).children[0]
req.vreg.rqlhelper.annotate(select)
- req.vreg.solutions(req, select, {})
+ req.vreg.compute_var_types(req, select, {})
if restrictions:
return ['%s, %s' % (user_rql, suggestion)
for suggestion in self.rql_build_suggestions(select, incomplete_part)]
--- a/cubicweb/web/views/navigation.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/views/navigation.py Tue Mar 17 13:34:54 2020 +0100
@@ -56,7 +56,7 @@
from cubicweb.predicates import paginated_rset, sorted_rset, adaptable
from cubicweb.uilib import cut
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.web.component import EmptyComponent, EntityCtxComponent, NavigationComponent
--- a/cubicweb/web/views/xmlrss.py Tue Mar 17 13:31:50 2020 +0100
+++ b/cubicweb/web/views/xmlrss.py Tue Mar 17 13:34:54 2020 +0100
@@ -26,7 +26,8 @@
from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset,
appobject_selectable, adaptable)
-from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
+from cubicweb.view import EntityView, AnyRsetView, Component
+from cubicweb.entity import EntityAdapter
from cubicweb.uilib import simple_sgml_tag
from cubicweb.web import httpcache, component
--- a/doc/book/devrepo/datamodel/definition.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/book/devrepo/datamodel/definition.rst Tue Mar 17 13:34:54 2020 +0100
@@ -466,7 +466,7 @@
__permissions__ = {'read': ('managers', 'users'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('U has_update_permission S'))
- }
+ }
In the above example, user will be allowed to add/delete `my_relation` if he has
the `update` permission on the subject of the relation.
--- a/doc/book/devrepo/repo/hooks.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/book/devrepo/repo/hooks.rst Tue Mar 17 13:34:54 2020 +0100
@@ -35,11 +35,11 @@
events = ('before_add_entity', 'before_update_entity')
def __call__(self):
- if 'age' in self.entity.cw_edited:
+ if 'age' in self.entity.cw_edited:
if 0 <= self.entity.age <= 120:
return
- msg = self._cw._('age must be between 0 and 120')
- raise ValidationError(self.entity.eid, {'age': msg})
+ msg = self._cw._('age must be between 0 and 120')
+ raise ValidationError(self.entity.eid, {'age': msg})
In our example the base `__select__` is augmented with an `is_instance` selector
matching the desired entity type.
--- a/doc/book/devrepo/vreg.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/book/devrepo/vreg.rst Tue Mar 17 13:34:54 2020 +0100
@@ -300,18 +300,18 @@
.. sourcecode:: python
class UserLink(component.Component):
- '''if the user is the anonymous user, build a link to login else a link
- to the connected user object with a logout link
- '''
- __regid__ = 'loggeduserlink'
+ '''if the user is the anonymous user, build a link to login else a link
+ to the connected user object with a logout link
+ '''
+ __regid__ = 'loggeduserlink'
- def call(self):
- if self._cw.session.anonymous_session:
- # display login link
- ...
- else:
- # display a link to the connected user object with a loggout link
- ...
+ def call(self):
+ if self._cw.session.anonymous_session:
+ # display login link
+ ...
+ else:
+ # display a link to the connected user object with a loggout link
+ ...
The proper way to implement this with |cubicweb| is two have two different
classes sharing the same identifier but with different selectors so you'll get
@@ -320,21 +320,21 @@
.. sourcecode:: python
class UserLink(component.Component):
- '''display a link to the connected user object with a loggout link'''
- __regid__ = 'loggeduserlink'
- __select__ = component.Component.__select__ & authenticated_user()
+ '''display a link to the connected user object with a loggout link'''
+ __regid__ = 'loggeduserlink'
+ __select__ = component.Component.__select__ & authenticated_user()
- def call(self):
+ def call(self):
# display useractions and siteactions
- ...
+ ...
class AnonUserLink(component.Component):
- '''build a link to login'''
- __regid__ = 'loggeduserlink'
- __select__ = component.Component.__select__ & anonymous_user()
+ '''build a link to login'''
+ __regid__ = 'loggeduserlink'
+ __select__ = component.Component.__select__ & anonymous_user()
- def call(self):
- # display login link
+ def call(self):
+ # display login link
...
The big advantage, aside readability once you're familiar with the
--- a/doc/book/devweb/edition/examples.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/book/devweb/edition/examples.rst Tue Mar 17 13:34:54 2020 +0100
@@ -56,42 +56,42 @@
.. sourcecode:: python
def sender_value(form, field):
- return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
+ return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
def recipient_choices(form, field):
- return [(e.get_email(), e.eid)
+ return [(e.get_email(), e.eid)
for e in form.cw_rset.entities()
- if e.get_email()]
+ if e.get_email()]
def recipient_value(form, field):
- return [e.eid for e in form.cw_rset.entities()
+ return [e.eid for e in form.cw_rset.entities()
if e.get_email()]
class MassMailingForm(forms.FieldsForm):
- __regid__ = 'massmailing'
+ __regid__ = 'massmailing'
- needs_js = ('cubicweb.widgets.js',)
- domid = 'sendmail'
- action = 'sendmail'
+ needs_js = ('cubicweb.widgets.js',)
+ domid = 'sendmail'
+ action = 'sendmail'
- sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
- label=_('From:'),
- value=sender_value)
+ sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
+ label=_('From:'),
+ value=sender_value)
- recipient = ff.StringField(widget=CheckBox(),
- label=_('Recipients:'),
- choices=recipient_choices,
- value=recipients_value)
+ recipient = ff.StringField(widget=CheckBox(),
+ label=_('Recipients:'),
+ choices=recipient_choices,
+ value=recipients_value)
- subject = ff.StringField(label=_('Subject:'), max_length=256)
+ subject = ff.StringField(label=_('Subject:'), max_length=256)
- mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
- inputid='mailbody'))
+ mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
+ inputid='mailbody'))
- form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
- _('send email'), 'SEND_EMAIL_ICON'),
- ImgButton('cancelbutton', "javascript: history.back()",
- stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
+ form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
+ _('send email'), 'SEND_EMAIL_ICON'),
+ ImgButton('cancelbutton', "javascript: history.back()",
+ stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
Let's detail what's going on up there. Our form will hold four fields:
@@ -125,13 +125,13 @@
.. sourcecode:: python
class MassMailingFormView(form.FormViewMixIn, EntityView):
- __regid__ = 'massmailing'
- __select__ = is_instance(IEmailable) & authenticated_user()
+ __regid__ = 'massmailing'
+ __select__ = is_instance(IEmailable) & authenticated_user()
- def call(self):
- form = self._cw.vreg['forms'].select('massmailing', self._cw,
- rset=self.cw_rset)
- form.render(w=self.w)
+ def call(self):
+ form = self._cw.vreg['forms'].select('massmailing', self._cw,
+ rset=self.cw_rset)
+ form.render(w=self.w)
As you see, we simply define a view with proper selector so it only apply to a
result set containing :class:`IEmailable` entities, and so that only users in the
--- a/doc/book/devweb/js.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/book/devweb/js.rst Tue Mar 17 13:34:54 2020 +0100
@@ -142,7 +142,7 @@
function removeBookmark(beid) {
d = asyncRemoteExec('delete_bookmark', beid);
d.addCallback(function(boxcontent) {
- reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
+ reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
document.location.hash = '#header';
updateMessage(_("bookmark has been removed"));
});
--- a/doc/book/devweb/views/reledit.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/book/devweb/views/reledit.rst Tue Mar 17 13:34:54 2020 +0100
@@ -136,14 +136,14 @@
from cubicweb.web.views import reledit
class DeactivatedAutoClickAndEditFormView(reledit.AutoClickAndEditFormView):
- def _should_edit_attribute(self, rschema):
- return False
+ def _should_edit_attribute(self, rschema):
+ return False
- def _should_edit_attribute(self, rschema, role):
- return False
+ def _should_edit_attribute(self, rschema, role):
+ return False
def registration_callback(vreg):
- vreg.register_and_replace(DeactivatedAutoClickAndEditFormView,
- reledit.AutoClickAndEditFormView)
+ vreg.register_and_replace(DeactivatedAutoClickAndEditFormView,
+ reledit.AutoClickAndEditFormView)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/changes/3.28.rst Tue Mar 17 13:34:54 2020 +0100
@@ -0,0 +1,8 @@
+3.28
+====
+
+Changes
+-------
+
+- the class cubicweb.view.EntityAdapter was moved to cubicweb.entity.EntityAdapter
+ a deprecation warning is in place, but please update your source code accordingly.
--- a/doc/changes/changelog.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/changes/changelog.rst Tue Mar 17 13:34:54 2020 +0100
@@ -2,6 +2,7 @@
Changelog history
===================
+.. include:: 3.28.rst
.. include:: 3.27.rst
.. include:: 3.26.rst
.. include:: 3.25.rst
--- a/doc/tutorials/advanced/part02_security.rst Tue Mar 17 13:31:50 2020 +0100
+++ b/doc/tutorials/advanced/part02_security.rst Tue Mar 17 13:34:54 2020 +0100
@@ -117,25 +117,25 @@
from cubicweb.schema import ERQLExpression
VISIBILITY_PERMISSIONS = {
- 'read': ('managers',
- ERQLExpression('X visibility "public"'),
- ERQLExpression('X may_be_read_by U')),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
+ 'read': ('managers',
+ ERQLExpression('X visibility "public"'),
+ ERQLExpression('X may_be_read_by U')),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
AUTH_ONLY_PERMISSIONS = {
- 'read': ('managers', 'users'),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
+ 'read': ('managers', 'users'),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
CLASSIFIERS_PERMISSIONS = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
from cubicweb_folder.schema import Folder
from cubicweb_file.schema import File
--- a/setup.py Tue Mar 17 13:31:50 2020 +0100
+++ b/setup.py Tue Mar 17 13:34:54 2020 +0100
@@ -73,6 +73,8 @@
'pytz',
'Markdown',
'filelock',
+ 'rdflib',
+ 'rdflib-jsonld',
],
entry_points={
'console_scripts': [
@@ -102,9 +104,6 @@
'pyramid_multiauth',
'repoze.lru',
],
- 'rdf': [
- 'rdflib',
- ],
'sparql': [
'fyzz >= 0.1.0',
],
@@ -113,5 +112,5 @@
],
},
zip_safe=False,
- python_requires=">=3.4",
+ python_requires=">=3.6",
)