stable is now 1.6.0 stable
authorNicolas Chauvat <nicolas.chauvat@logilab.fr>
Mon, 15 Feb 2010 17:14:30 +0100
branchstable
changeset 105 0f164eed9ba8
parent 104 e811be22b97c (current diff)
parent 103 b27eb54052f6 (diff)
child 106 d9746abcdd02
stable is now 1.6.0
.hgtags
--- a/.hgtags	Mon Feb 15 17:13:55 2010 +0100
+++ b/.hgtags	Mon Feb 15 17:14:30 2010 +0100
@@ -6,4 +6,6 @@
 d3db00ada04f55086064650d52e48d1dc5afe0dc cubicweb-keyword-debian-version-1.4.0-1
 40beb6f7c57df553b77f0bf8f739081f644318fe cubicweb-keyword-version-1.5.0
 39b0f2292d0fa4a80c861c5db82a4cd3ce312f64 cubicweb-keyword-debian-version-1.5.0-1
+b9c77c4c40b8f37dd0f27c3397ec2fdc8fc032f6 cubicweb-keyword-version-1.6.0
+294a50fe97722d9285bbb30762c9040a4bc23214 cubicweb-keyword-debian-version-1.6.0-1
 cbda8e6981069dc2fbb653a545c40950fad101dc oldstable
--- a/__pkginfo__.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/__pkginfo__.py	Mon Feb 15 17:14:30 2010 +0100
@@ -4,11 +4,11 @@
 modname = 'keyword'
 distname = "cubicweb-keyword"
 
-numversion = (1, 5, 0)
+numversion = (1, 6, 0)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL'
-copyright = '''Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
+copyright = '''Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
 http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
 
 author = "Logilab"
@@ -16,10 +16,28 @@
 web = 'http://www.cubicweb.org/project/%s' % distname
 
 short_desc = "keyword component for the Cubicweb framework"
-long_desc = """This Cubicweb component provides classification (hierarchies of keywords)
-to classify content. See also cubicweb-tag.
+long_desc = """Summary
+-------
+The `keyword` cube provides classification by using hierarchies of keywords to
+classify content.
+
+There is two types of keywords:
+
+- `Keyword` which contains a description,
+- `CodeKeyword` which contains the keyword description and the
+  associated code.
 
-CubicWeb is a semantic web application framework, see http://www.cubicweb.org
+In order to link an entity to a keyword, you have to add a relation
+`applied_to` in the schema.
+
+Each keyword has the `subkeyword_of` relation definition. This allows
+to navigate in the classification without a Modified Preorder Tree
+Traversal representation of the data.
+
+Some methods are defined in order to get parents and children or get
+the status of a keyword (leaf or root).
+
+See also cubicweb-tag as another (simpler) way to classify content.
 """
 
 classifiers = [
@@ -30,7 +48,7 @@
     ]
 
 __depends_cubes__ = {}
-__depends__ = {'cubicweb': '>= 3.0.0'}
+__depends__ = {'cubicweb': '>= 3.6.0'}
 __use__ = tuple(__depends_cubes__)
 
 from os import listdir
--- a/debian/changelog	Mon Feb 15 17:13:55 2010 +0100
+++ b/debian/changelog	Mon Feb 15 17:14:30 2010 +0100
@@ -1,3 +1,9 @@
+cubicweb-keyword (1.6.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 10 Feb 2010 08:14:44 +0100
+
 cubicweb-keyword (1.5.0-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Mon Feb 15 17:13:55 2010 +0100
+++ b/debian/control	Mon Feb 15 17:14:30 2010 +0100
@@ -11,7 +11,7 @@
 Conflicts: cubicweb-keywords
 Replaces: cubicweb-keywords
 Architecture: all
-Depends: cubicweb-common (>= 3.0.0)
+Depends: cubicweb-common (>= 3.6.0)
 Description: keyword component for the Cubicweb framework
  This Cubicweb component provides classification (hierarchies of keywords)
  to classify content. See also cubicweb-tag.
Binary file doc/schema.png has changed
Binary file doc/screenshot_keyword_tree.png has changed
--- a/entities.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/entities.py	Mon Feb 15 17:14:30 2010 +0100
@@ -6,13 +6,13 @@
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.mixins import MI_REL_TRIGGERS, TreeMixIn
+from cubicweb.mixins import MI_REL_TRIGGERS, TreeMixIn
 from cubicweb.entities import AnyEntity, fetch_config
 from cubicweb.interfaces import ITree
 
 
 class Classification(AnyEntity):
-    id = 'Classification'
+    __regid__ = 'Classification'
     fetch_attrs, fetch_order = fetch_config(['name'])
     __implements__ = AnyEntity.__implements__ + (ITree,)
 
@@ -51,7 +51,7 @@
 
 
 class Keyword(TreeMixIn, AnyEntity):
-    id = 'Keyword'
+    __regid__ = 'Keyword'
     fetch_attrs, fetch_order = fetch_config(['name'])
     __implements__ = AnyEntity.__implements__ + (ITree,)
 
@@ -119,7 +119,7 @@
     """
 
 class CodeKeyword(Keyword):
-    id = 'CodeKeyword'
+    __regid__ = 'CodeKeyword'
     rest_attr = 'code'
     fetch_attrs, fetch_order = fetch_config(['code','name'])
 
--- a/hooks.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/hooks.py	Mon Feb 15 17:14:30 2010 +0100
@@ -5,12 +5,12 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 from cubicweb import ValidationError
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.server.pool import PreCommitOperation
+from cubicweb.server.hook import Hook, Operation
 from itertools import chain
+from cubicweb.server import hook
 
 
-class SetDescendantOfKeywordOp(PreCommitOperation):
+class SetDescendantOfKeywordOp(Operation):
     def precommit_event(self):
         """transitive closure of ``descendant_of`` relations to current entity"""
         closure = set()
@@ -29,32 +29,34 @@
 class BeforeAddDescendantOf(Hook):
     """check indirect cycle for ``descendant_of`` relation
     """
+    __regid__ = 'beforeadddescendant'
     events = ('before_add_relation', )
-    accepts = ('descendant_of',)
+    __select__ = Hook.__select__ & hook.match_rtype('descendant_of')
 
-    def call(self, session, fromeid, rtype, toeid):
-        entity = session.entity_from_eid(fromeid)
-        parent = session.entity_from_eid(toeid)
+    def __call__(self):
+        entity = self._cw.entity_from_eid(self.eidfrom)
+        parent = self._cw.entity_from_eid(self.eidto)
         parents = set([x.eid for x in chain([parent,], parent.iterparents())])
         children = set([x.eid for x in chain([entity], entity.iterchildren())])
         if children & parents:
             msg = _('detected descendant_of cycle')
-            raise ValidationError(fromeid, {'descendant_of': msg})
+            raise ValidationError(self.eidfrom, {'descendant_of': msg})
 
 
 class AfterAddSubKeywordOf(Hook):
     """sets ``descendant_of`` relation
     """
+    __regid__ = 'afteradddescendant'
     events = ('after_add_relation', )
-    accepts = ('subkeyword_of',)
+    __select__ = Hook.__select__ & hook.match_rtype('subkeyword_of')
 
-    def call(self, session, fromeid, rtype, toeid):
-        entity = session.entity_from_eid(fromeid)
-        parent = session.entity_from_eid(toeid)
-        SetDescendantOfKeywordOp(session, parent=parent, entity=entity)
+    def __call__(self):
+        entity = self._cw.entity_from_eid(self.eidfrom)
+        parent = self._cw.entity_from_eid(self.eidto)
+        SetDescendantOfKeywordOp(self._cw, parent=parent, entity=entity)
 
 
-class SetIncludedInRelationOp(PreCommitOperation):
+class SetIncludedInRelationOp(Operation):
     """delay this operation to commit to avoid conflict with a late rql query
     already setting the relation
     """
@@ -62,9 +64,9 @@
         session = self.session
         # test for indirect cycles
         self.check_cycle()
-        subkw = session.entity_from_eid(self.fromeid)
+        subkw = session.entity_from_eid(self.eidfrom)
         if subkw.included_in:
-            kw = session.entity_from_eid(self.toeid)
+            kw = session.entity_from_eid(self.eidto)
             if subkw.included_in[0].eid != kw.included_in[0].eid:
                 msgid = "keywords %(subkw)s and %(kw)s belong to different classifications"
                 raise ValidationError(subkw.eid, {'subkeyword_of': session._(msgid) %
@@ -75,29 +77,30 @@
                             {'x': subkw.eid}, 'x')
 
     def check_cycle(self):
-        parents = set([self.toeid])
-        parent = self.session.entity_from_eid(self.toeid)
+        parents = set([self.eidto])
+        parent = self.session.entity_from_eid(self.eidto)
         while parent.subkeyword_of:
             parent = parent.subkeyword_of[0]
             if parent.eid in parents:
                 msg = self.session._('detected subkeyword cycle')
-                raise ValidationError(self.fromeid, {'subkeyword_of': msg})
+                raise ValidationError(self.eidfrom, {'subkeyword_of': msg})
             parents.add(parent.eid)
 
 
 class SetIncludedInRelationHook(Hook):
     """sets the included_in relation on a subkeyword if not already set
     """
+    __regid__ = 'setincludedinrelationhook'
     events = ('before_add_relation',)
-    accepts = ('subkeyword_of',)
+    __select__ = Hook.__select__ & hook.match_rtype('subkeyword_of')
 
-    def call(self, session, fromeid, rtype, toeid):
+    def __call__(self):
         # immediate test direct cycles
-        if fromeid == toeid:
-            msg = session._('keyword cannot be subkeyword of himself')
-            raise ValidationError(fromeid, {rtype : msg})
-        SetIncludedInRelationOp(session, vreg=self.vreg,
-                                fromeid=fromeid, toeid=toeid)
+        if self.eidfrom == self.eidto:
+            msg = self._cw._('keyword cannot be subkeyword of himself')
+            raise ValidationError(self.eidfrom, {self.rtype : msg})
+        SetIncludedInRelationOp(self._cw, vreg=self._cw.vreg,
+                                eidfrom=self.eidfrom, eidto=self.eidto)
 
 
 class RemoveDescendantOfRelation(Hook):
@@ -105,13 +108,14 @@
 
     we delete the relation for entity's parents recursively
     """
+    __regid__ = 'removedescendantofrelation'
     events = ('after_delete_relation',)
-    accepts = ('subkeyword_of',)
+    __select__ = Hook.__select__ & hook.match_rtype('subkeyword_of')
 
-    def call(self, session, fromeid, rtype, toeid):
-        parent = session.entity_from_eid(toeid)
+    def __call__(self):
+        parent = self._cw.entity_from_eid(self.eidto)
         for parent in chain([parent], parent.iterparents()):
-            session.execute('DELETE K descendant_of P WHERE K eid %(k)s, '
-                            'P eid %(p)s', {'p':parent.eid, 'k': fromeid})
+            self._cw.execute('DELETE K descendant_of P WHERE K eid %(k)s, '
+                            'P eid %(p)s', {'p':parent.eid, 'k': self.eidfrom})
 
 
--- a/i18n/en.po	Mon Feb 15 17:13:55 2010 +0100
+++ b/i18n/en.po	Mon Feb 15 17:14:30 2010 +0100
@@ -146,10 +146,6 @@
 msgid "descendant_of"
 msgstr ""
 
-msgctxt "Classification"
-msgid "descendant_of_object"
-msgstr ""
-
 msgctxt "Keyword"
 msgid "descendant_of_object"
 msgstr ""
@@ -252,15 +248,3 @@
 
 msgid "which keyword (if any) this keyword specializes"
 msgstr ""
-
-#~ msgid "add a Classification"
-#~ msgstr "add a classification"
-
-#~ msgid "add a Keyword"
-#~ msgstr "add a keyword"
-
-#~ msgid "remove this Classification"
-#~ msgstr "remove this classification"
-
-#~ msgid "remove this Keyword"
-#~ msgstr "remove this keyword"
--- a/i18n/fr.po	Mon Feb 15 17:13:55 2010 +0100
+++ b/i18n/fr.po	Mon Feb 15 17:14:30 2010 +0100
@@ -154,10 +154,6 @@
 msgid "descendant_of"
 msgstr "descendant de"
 
-msgctxt "Classification"
-msgid "descendant_of_object"
-msgstr "est parent de"
-
 msgctxt "Keyword"
 msgid "descendant_of_object"
 msgstr "est parent de"
--- a/migration/1.4.0_Any.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/migration/1.4.0_Any.py	Mon Feb 15 17:14:30 2010 +0100
@@ -1,4 +1,5 @@
-for entity in ('Keyword', 'CodeKeyword'):
-    add_relation_definition(entity, 'descendant_of', entity)
-    sync_schema_props_perms(entity)
+add_entity_type('CodeKeyword')
+
+add_relation_definition('Keyword', 'descendant_of', 'Keyword')
+sync_schema_props_perms('Keyword')
 checkpoint()
--- a/schema.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/schema.py	Mon Feb 15 17:14:30 2010 +0100
@@ -10,10 +10,12 @@
 from cubicweb.schema import RQLConstraint, ERQLExpression
 
 
+_ = unicode
+
 class Classification(EntityType):
     """classification schemes are used by users to classify entities.
     """
-    permissions = {
+    __permissions__ = {
         'read' : ('managers', 'users', 'guests'),
         'add' : ('managers',),
         'delete' : ('managers',),
@@ -40,7 +42,7 @@
     """A keyword is like a tag but is application specific
     and used to define a classification scheme
     """
-    permissions = {
+    __permissions__ = {
         'read' : ('managers', 'users', 'guests'),
         'add' : ('managers', 'users'),
         'delete' : ('managers',),
--- a/sobjects.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/sobjects.py	Mon Feb 15 17:14:30 2010 +0100
@@ -31,7 +31,7 @@
 
 
 class KeywordNameChanged(KeywordNotificationView):
-    id = 'notif_after_update_entity'
+    __regid__ = 'notif_after_update_entity'
 
     content = _("keyword name changed from %(oldname)s to %(kw)s")
 
--- a/test/test_keyword.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/test/test_keyword.py	Mon Feb 15 17:14:30 2010 +0100
@@ -3,7 +3,7 @@
 
 class AutomaticWebTest(AutomaticWebTest):
 
-    ignored_relations = ('descendant_of',)
+    ignored_relations = set(('descendant_of',))
 
     def to_test_etypes(self):
         # add cwgroup to test keyword components on entity on which they can be applied
--- a/test/unittest_classification.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/test/unittest_classification.py	Mon Feb 15 17:14:30 2010 +0100
@@ -1,9 +1,9 @@
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
-from cubicweb.common import ValidationError
+from cubicweb import ValidationError
 
 
-class ClassificationHooksTC(EnvBasedTC):
+class ClassificationHooksTC(CubicWebTC):
 
     def setup_database(self):
         self.execute('INSERT Classification C: C name "classif1", C classifies ET WHERE ET name "CWGroup"')
--- a/test/unittest_descendant_of.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/test/unittest_descendant_of.py	Mon Feb 15 17:14:30 2010 +0100
@@ -1,9 +1,9 @@
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
-from cubicweb.common import ValidationError
+from cubicweb import ValidationError
 
 
-class KeywordHooksTC(EnvBasedTC):
+class KeywordHooksTC(CubicWebTC):
 
     def setup_database(self):
         self.execute('INSERT Classification C: C name "classif1", C classifies ET WHERE ET name "CWGroup"')
--- a/test/unittest_security.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/test/unittest_security.py	Mon Feb 15 17:14:30 2010 +0100
@@ -1,6 +1,6 @@
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
 
-class SecurityTC(EnvBasedTC):
+class SecurityTC(CubicWebTC):
 
     def setup_database(self):
         self.execute('INSERT Classification C: C name "classif1", C classifies ET WHERE ET name "CWGroup"')
--- a/views.py	Mon Feb 15 17:13:55 2010 +0100
+++ b/views.py	Mon Feb 15 17:14:30 2010 +0100
@@ -12,7 +12,7 @@
 from cubicweb import Unauthorized
 from cubicweb.selectors import implements, rql_condition, relation_possible
 from cubicweb.view import EntityView
-from cubicweb.common.mixins import TreePathMixIn
+from cubicweb.mixins import TreePathMixIn
 from cubicweb.web import stdmsgs, uicfg, component, facet
 from cubicweb.web.views import primary, basecontrollers
 
@@ -38,7 +38,7 @@
         pass
 
     def render_entity_relations(self, entity):
-        rset = self.req.execute('Any K ORDERBY N WHERE K included_in C, '
+        rset = self._cw.execute('Any K ORDERBY N WHERE K included_in C, '
                                 'NOT K subkeyword_of KK, K name N, '
                                 'C eid %(x)s', {'x': entity.eid})
         self.wview('treeview', rset, 'null')
@@ -50,7 +50,7 @@
     __select__ = implements('Keyword')
 
     def cell_call(self, row, col, **kwargs):
-        entity = self.entity(row, col)
+        entity = self.cw_rset.get_entity(row, col)
         self.w(u'<h1 class="titleUnderline">%s</h1>'
                % xml_escape(entity.dc_long_title()))
         rset = entity.related('subkeyword_of','object')
@@ -59,7 +59,7 @@
 
 class KeywordComboBoxView(TreePathMixIn, EntityView):
     """display keyword in edition's combobox"""
-    id = 'combobox'
+    __regid__ = 'combobox'
     __select__ = implements('Keyword', 'Classification')
 
     item_vid = 'text'
@@ -80,7 +80,7 @@
 SKOS_CLOSING_ROOT = u'</rdf:RDF>'
 
 class SkosView(EntityView):
-    id = 'skos'
+    __regid__ = 'skos'
     content_type = 'application/xml'
     templatable = False
     __select__ = implements('Keyword', 'Classification')
@@ -95,7 +95,7 @@
         self.wview('skositemview', self.rset, row=row, col=col)
 
 class SkosItemView(EntityView):
-    id = 'skositemview'
+    __regid__ = 'skositemview'
     content_type = 'application/xml'
     __select__ = implements('Keyword', 'Classification')
 
@@ -122,7 +122,7 @@
 
 class KeywordBarVComponent(component.EntityVComponent):
     """the keywords path bar: display keywords of a tagged entity"""
-    id = 'keywordsbar'
+    __regid__ = 'keywordsbar'
     __select__ = (component.EntityVComponent.__select__ &
                   relation_possible('applied_to', 'object', 'Keyword'))
     context = 'header'
@@ -138,7 +138,7 @@
         rset = self.get_keywords()
         if rset:
             self.w(u'<div class="%s" id="%s">\n' % (self.div_class(), self.div_id()))
-            self.w(u'<span>%s</span>&nbsp;' % self.req._('keywords:'))
+            self.w(u'<span>%s</span>&nbsp;' % self._cw._('keywords:'))
             self.wview('csv', rset, 'null', done=set())
             self.w(u'</div>\n')
         else:
@@ -148,7 +148,7 @@
 
 class AddKeywordVComponent(component.EntityVComponent):
     """the 'add keyword' component"""
-    id = 'addkeywords'
+    __regid__ = 'addkeywords'
     __select__ = component.EntityVComponent.__select__ & \
                  relation_possible('applied_to', 'object', 'Keyword', action='add') & \
                  rql_condition('X is ET, CL classifies ET')
@@ -159,12 +159,12 @@
 
     def call(self):
         self.add_js(['cubicweb.widgets.js', 'cubes.keyword.js'])
-        self.req.add_css('cubicweb.suggest.css')
+        self._cw.add_css('cubicweb.suggest.css')
         entity = self.entity(0)
         self.w(u'<table><tr><td>')
         self.w(u'<a class="button sglink" href="javascript: showKeywordSelector(%s, \'%s\', \'%s\');">%s</a></td>' % (
-            entity.eid, self.req._(stdmsgs.BUTTON_OK),
-            self.req._(stdmsgs.BUTTON_CANCEL), self.req._('add keywords')))
+            entity.eid, self._cw._(stdmsgs.BUTTON_OK[0]),
+            self._cw._(stdmsgs.BUTTON_CANCEL[0]), self._cw._('add keywords')))
         self.w(u'<td><div id="kwformholder"></div>')
         self.w(u'</td></tr></table>')
 
@@ -172,13 +172,13 @@
 # applied_to relation facet ####################################################
 
 class AppliedToFacet(facet.RelationFacet):
-    id = 'applied-to-facet'
+    __regid__ = 'applied-to-facet'
     rtype = 'applied_to'
     role = 'object'
     target_attr = 'name'
 
     def rset_vocabulary(self, rset):
-        _ = self.req._
+        _ = self._cw._
         vocab = []
         scheme = None
         for e in sorted(rset.entities(),
@@ -195,7 +195,7 @@
     subclasses must define their own id the classification name, e.g :
 
     class Classifaction1Facet(ClassificationFacet):
-        id = 'classif1'
+        __regid__ = 'classif1'
         classification = u'classification1'
 
     """
@@ -225,7 +225,7 @@
             # ORDERBY KN
             rqlst.add_sort_var(keyword_name_var, True)
             try:
-                rset = self.req.execute(rqlst.as_string(), self.rset.args,
+                rset = self._cw.execute(rqlst.as_string(), self.rset.args,
                                         self.rset.cachekey)
             except Unauthorized:
                 return []
@@ -235,7 +235,7 @@
 
     @property
     def title(self):
-        return self.req._(self.classification)
+        return self._cw._(self.classification)
 
     def support_and(self):
         return False
@@ -247,18 +247,18 @@
 def js_possible_keywords(self, eid):
     rql = ('DISTINCT Any N WHERE K is Keyword, K name N, NOT K applied_to X, '
            'X eid %(x)s, K included_in C, C classifies ET, X is ET')
-    rset = self.cursor.execute(rql, {'x' : eid, 'u' : self.req.user.eid}, 'x')
+    rset = self.cursor.execute(rql, {'x' : eid, 'u' : self._cw.user.eid}, 'x')
     return [name for (name,) in rset]
 
 @monkeypatch(basecontrollers.JSonController)
 @basecontrollers.jsonize
 def js_add_keywords(self, eid, kwlist):
-    msg = self.req._('keywords applied')
+    msg = self._cw._('keywords applied')
     kwrset = self.cursor.execute('Any K,N,C WHERE K is Keyword, K name N, K included_in C, '
                                  'C classifies ET, X eid %(x)s, X is ET',
                                  {'x' : eid}, 'x')
     if not kwrset:
-        return self.req._('No suitable classification scheme found')
+        return self._cw._('No suitable classification scheme found')
     classification = kwrset[0][2] # XXX what if we have several classifications ?
     valid_keywords = set(kwname for _, kwname,_ in kwrset)
     user_keywords = set(kwlist)