--- 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> ' % self.req._('keywords:'))
+ self.w(u'<span>%s</span> ' % 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)