cubicweb/entities/adapters.py
changeset 12910 c87c3943d6ab
parent 12892 0df0db725f07
equal deleted inserted replaced
12892:0df0db725f07 12910:c87c3943d6ab
    21 from cubicweb import _
    21 from cubicweb import _
    22 
    22 
    23 from hashlib import sha1
    23 from hashlib import sha1
    24 from itertools import chain
    24 from itertools import chain
    25 
    25 
    26 from rdflib import URIRef, Literal
    26 from rdflib import URIRef, Literal, namespace as rdflib_namespace
    27 
       
    28 from logilab.mtconverter import TransformError
    27 from logilab.mtconverter import TransformError
    29 from logilab.common.decorators import cached, cachedproperty
    28 from logilab.common.decorators import cached, cachedproperty
    30 
    29 
    31 from cubicweb.entity import EntityAdapter
    30 from cubicweb import rdf
    32 from cubicweb import (Unauthorized, ValidationError, ViolatedConstraint,
    31 from cubicweb import (Unauthorized, ValidationError, ViolatedConstraint,
    33                       UniqueTogetherError)
    32                       UniqueTogetherError)
    34 from cubicweb.schema import constraint_name_for
    33 from cubicweb.entity import EntityAdapter
       
    34 from cubicweb.schema import constraint_name_for, VIRTUAL_RTYPES
    35 from cubicweb.predicates import is_instance, relation_possible, match_exception
    35 from cubicweb.predicates import is_instance, relation_possible, match_exception
    36 
       
    37 from cubicweb.rdf import NAMESPACES
       
    38 
    36 
    39 
    37 
    40 class EntityRDFAdapter(EntityAdapter):
    38 class EntityRDFAdapter(EntityAdapter):
    41     """EntityRDFAdapter is to be specialized for each entity that wants to
    39     """EntityRDFAdapter is to be specialized for each entity that wants to
    42     be converted to RDF using the mechanism from cubicweb.rdf
    40     be converted to RDF using the mechanism from cubicweb.rdf
    43     """
    41     """
    44     __abstract__ = True
    42     __regid__ = "rdf"
       
    43 
       
    44     SKIP_RTYPES = VIRTUAL_RTYPES | set(['cwuri', 'is', 'is_instance_of'])
    45 
    45 
    46     def __init__(self, _cw, **kwargs):
    46     def __init__(self, _cw, **kwargs):
    47         super().__init__(_cw, **kwargs)
    47         super().__init__(_cw, **kwargs)
    48         self.entity.complete()
    48         self.entity.complete()
       
    49         self.used_namespaces = {}
       
    50 
       
    51     def _use_namespace(self, prefix, base_url=None):
       
    52         if prefix not in self.used_namespaces:
       
    53             if base_url is not None:
       
    54                 if prefix in rdf.NAMESPACES:
       
    55                     raise KeyError('prefix redefinition not allowed: '
       
    56                                    f'"{prefix}" already exists as "{rdf.NAMESPACES[prefix]}"')
       
    57                 ns = rdflib_namespace.Namespace(base_url)
       
    58             else:
       
    59                 ns = rdf.NAMESPACES[prefix]
       
    60             self.used_namespaces[prefix] = ns
       
    61         elif base_url is not None:
       
    62             if self.used_namespaces[prefix] != rdflib_namespace.Namespace(base_url):
       
    63                 raise ValueError('prefix redefinition not allowed: '
       
    64                                  f'"{prefix}" already exists as "{self.used_namespaces[prefix]}"')
       
    65         return self.used_namespaces[prefix]
    49 
    66 
    50     @cachedproperty
    67     @cachedproperty
    51     def uri(self):
    68     def uri(self):
    52         return self.entity.cwuri
    69         return URIRef(self.entity.cwuri)
    53 
    70 
    54     def triples(self):
    71     def triples(self):
    55         """return sequence of 3-tuple of rdflib identifiers"""
    72         """return sequence of 3-tuple of rdflib identifiers"""
    56         raise NotImplementedError()
    73         yield from self.cw_triples()
    57 
    74         yield from self.dc_triples()
    58 
    75 
    59 class CWUserFoafAdapter(EntityRDFAdapter):
    76     def cw_triples(self):
    60     __regid__ = "rdf.foaf"
    77         RDF = self._use_namespace('rdf')
       
    78         CW = self._use_namespace('cubicweb')
       
    79 
       
    80         yield (self.uri, RDF.type, CW[self.entity.e_schema.type])
       
    81 
       
    82         for rschema, targettypes, role in self.entity.e_schema.relation_definitions('relation'):
       
    83             rtype = rschema.type
       
    84             if rtype in self.SKIP_RTYPES or rtype.endswith('_permission'):
       
    85                 continue
       
    86             for targetype in targettypes:
       
    87                 # if rschema is an attribute
       
    88                 if targetype.final:
       
    89                     try:
       
    90                         value = self.entity.cw_attr_cache[rtype]
       
    91                     except KeyError:
       
    92                         continue
       
    93                     if value is not None:
       
    94                         yield (self.uri, CW[rtype], Literal(value))
       
    95                 # else if rschema is a relation
       
    96                 else:
       
    97                     for related in self.entity.related(rtype, role, entities=True, safe=True):
       
    98                         if role == 'subject':
       
    99                             yield (self.uri, CW[rtype], URIRef(related.cwuri))
       
   100                         else:
       
   101                             yield (URIRef(related.cwuri), CW[rtype], self.uri)
       
   102 
       
   103     def dc_triples(self):
       
   104         dc_entity = self.entity.cw_adapt_to('IDublinCore')
       
   105         DC = self._use_namespace('dc')
       
   106         yield (self.uri, DC.title, Literal(dc_entity.long_title()))  # or title() ?
       
   107         desc = dc_entity.description()
       
   108         if desc:
       
   109             yield (self.uri, DC.description, Literal(desc))
       
   110         creator = dc_entity.creator()  # use URI instead of Literal ?
       
   111         if creator:
       
   112             yield (self.uri, DC.creator, Literal(creator))
       
   113         yield (self.uri, DC.date, Literal(dc_entity.date()))
       
   114         yield (self.uri, DC.type, Literal(dc_entity.type()))
       
   115         yield (self.uri, DC.language, Literal(dc_entity.language()))
       
   116 
       
   117 
       
   118 class CWUserRDFAdapter(EntityRDFAdapter):
    61     __select__ = is_instance("CWUser")
   119     __select__ = is_instance("CWUser")
    62 
   120 
    63     def triples(self):
   121     def triples(self):
    64         RDF = NAMESPACES["rdf"]
   122         yield from super().triples()
    65         FOAF = NAMESPACES["foaf"]
   123         yield from self.foaf_triples()
    66         uri = URIRef(self.uri)
   124 
    67         yield (uri, RDF.type, FOAF.Person)
   125     def foaf_triples(self):
       
   126         RDF = self._use_namespace('rdf')
       
   127         FOAF = self._use_namespace('foaf')
       
   128         yield (self.uri, RDF.type, FOAF.Person)
    68         if self.entity.surname:
   129         if self.entity.surname:
    69             yield (uri, FOAF.familyName, Literal(self.entity.surname))
   130             yield (self.uri, FOAF.familyName, Literal(self.entity.surname))
    70         if self.entity.firstname:
   131         if self.entity.firstname:
    71             yield (uri, FOAF.givenName, Literal(self.entity.firstname))
   132             yield (self.uri, FOAF.givenName, Literal(self.entity.firstname))
    72         emailaddr = self.entity.cw_adapt_to("IEmailable").get_email()
   133         emailaddr = self.entity.cw_adapt_to("IEmailable").get_email()
    73         if emailaddr:
   134         if emailaddr:
    74             email_digest = sha1(emailaddr.encode("utf-8")).hexdigest()
   135             email_digest = sha1(emailaddr.encode("utf-8")).hexdigest()
    75             yield (uri, FOAF.mbox_sha1sum, Literal(email_digest))
   136             yield (self.uri, FOAF.mbox_sha1sum, Literal(email_digest))
    76 
   137 
    77 
   138 
    78 class IDublinCoreAdapter(EntityAdapter):
   139 class IDublinCoreAdapter(EntityAdapter):
    79     __regid__ = 'IDublinCore'
   140     __regid__ = 'IDublinCore'
    80     __select__ = is_instance('Any')
   141     __select__ = is_instance('Any')
   102         """Return a suitable description for entity"""
   163         """Return a suitable description for entity"""
   103         if 'description' in self.entity.e_schema.subjrels:
   164         if 'description' in self.entity.e_schema.subjrels:
   104             return self.entity.printable_value('description', format=format)
   165             return self.entity.printable_value('description', format=format)
   105         return u''
   166         return u''
   106 
   167 
   107     def authors(self):
   168     def authors(self):  # XXX is this part of DC ?
   108         """Return a suitable description for the author(s) of the entity"""
   169         """Return a suitable description for the author(s) of the entity"""
   109         try:
   170         try:
   110             return u', '.join(u.name() for u in self.entity.owned_by)
   171             return u', '.join(u.name() for u in self.entity.owned_by)
   111         except Unauthorized:
   172         except Unauthorized:
   112             return u''
   173             return u''
   130         """Return language used by this entity (translated)"""
   191         """Return language used by this entity (translated)"""
   131         eschema = self.entity.e_schema
   192         eschema = self.entity.e_schema
   132         # check if entities has internationalizable attributes
   193         # check if entities has internationalizable attributes
   133         # XXX one is enough or check if all String attributes are internationalizable?
   194         # XXX one is enough or check if all String attributes are internationalizable?
   134         for rschema, attrschema in eschema.attribute_definitions():
   195         for rschema, attrschema in eschema.attribute_definitions():
   135             if rschema.rdef(eschema, attrschema).internationalizable:
   196             if getattr(rschema.rdef(eschema, attrschema), 'internationalizable', False):
   136                 return self._cw._(self._cw.user.property_value('ui.language'))
   197                 return self._cw._(self._cw.user.property_value('ui.language'))
   137         return self._cw._(self._cw.vreg.property_value('ui.language'))
   198         return self._cw._(self._cw.vreg.property_value('ui.language'))
   138 
   199 
   139 
   200 
   140 class IEmailableAdapter(EntityAdapter):
   201 class IEmailableAdapter(EntityAdapter):