# HG changeset patch # User Nicolas Chauvat # Date 1240087804 18000 # Node ID 79755c89b4f3b52cf55b6ac7529c11c4c2b8dc97 # Parent 56e347b8189c3fd32e720bfc68ec086dc975202a# Parent e7f12251945a38e12ca9901bc4871fb7edced24f merge diff -r 56e347b8189c -r 79755c89b4f3 cwvreg.py --- a/cwvreg.py Sat Apr 18 15:49:40 2009 -0500 +++ b/cwvreg.py Sat Apr 18 15:50:04 2009 -0500 @@ -179,9 +179,9 @@ def possible_actions(self, req, rset, **kwargs): if rset is None: - actions = self.possible_vobjects('actions', req, rset) + actions = self.possible_vobjects('actions', req, rset, **kwargs) else: - actions = rset.possible_actions() # cached implementation + actions = rset.possible_actions(**kwargs) # cached implementation result = {} for action in actions: result.setdefault(action.category, []).append(action) diff -r 56e347b8189c -r 79755c89b4f3 devtools/apptest.py --- a/devtools/apptest.py Sat Apr 18 15:49:40 2009 -0500 +++ b/devtools/apptest.py Sat Apr 18 15:50:04 2009 -0500 @@ -14,6 +14,8 @@ from logilab.common.pytest import nocoverage from logilab.common.umessage import message_from_string +from logilab.common.deprecation import deprecated_function + from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration from cubicweb.devtools._apptest import TestEnvironment from cubicweb.devtools.fake import FakeRequest @@ -218,6 +220,13 @@ def pactions(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')): return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset) if a.category not in skipcategories] + + def pactions_by_cats(self, req, rset, categories=('addrelated',)): + return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset) + if a.category in categories] + + paddrelactions = deprecated_function(pactions_by_cats) + def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')): res = {} for a in self.vreg.possible_vobjects('actions', req, rset): @@ -225,10 +234,7 @@ res.setdefault(a.category, []).append(a.__class__) return res - def paddrelactions(self, req, rset): - return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset) - if a.category == 'addrelated'] - + def remote_call(self, fname, *args): """remote call simulation""" dump = simplejson.dumps diff -r 56e347b8189c -r 79755c89b4f3 doc/book/en/B0012-schema-definition.en.txt --- a/doc/book/en/B0012-schema-definition.en.txt Sat Apr 18 15:49:40 2009 -0500 +++ b/doc/book/en/B0012-schema-definition.en.txt Sat Apr 18 15:50:04 2009 -0500 @@ -6,7 +6,9 @@ An entity type is defined by a Python class which inherits from `EntityType`. The class definition contains the description of attributes and relations for the defined entity type. -The class name corresponds to the entity type name. +The class name corresponds to the entity type name. It is exepected to be +defined in the module ``mycube.schema``. + For example :: diff -r 56e347b8189c -r 79755c89b4f3 doc/book/en/B0030-data-as-objects.en.txt --- a/doc/book/en/B0030-data-as-objects.en.txt Sat Apr 18 15:49:40 2009 -0500 +++ b/doc/book/en/B0030-data-as-objects.en.txt Sat Apr 18 15:50:04 2009 -0500 @@ -7,12 +7,12 @@ In this chapter, we will introduce the objects that are used to handle the data stored in the database. -Classes `Entity` and `AnyEntity` --------------------------------- +Class `Entity` and `AnyEntity` +------------------------------ To provide a specific behavior for each entity, we have to define a class inheriting from `cubicweb.entities.AnyEntity`. In general, we -define this class in a module of `entities` package of an application +define this class in a module of `mycube.entities` package of an application so that it will be available on both server and client side. The class `AnyEntity` is loaded dynamically from the class `Entity` @@ -114,6 +114,105 @@ * `relation_vocabulary(rtype, targettype, x, limit=None)`, called internally by `subject_relation_vocabulary` and `object_relation_vocabulary` +Class `TreeMixIn` +----------------- + +This class provides a tree interface. This mixin has to be inherited +explicitly and configured using the tree_attribute, parent_target and +children_target class attribute to benefit from this default implementation. + +This class provides the following methods: + + * `different_type_children(entities=True)`, returns children entities + of different type as this entity. According to the `entities` parameter, + returns entity objects (if entity=True) or the equivalent result set. + + * `same_type_children(entities=True)`, returns children entities of + the same type as this entity. According to the `entities` parameter, + return entity objects (if entity=True) or the equivalent result set. + + * `iterchildren( _done=None)`, iters on the children of the entity. + + * `prefixiter( _done=None)` + + * `path()`, returns the list of eids from the root object to this object. + + * `iterparents()`, iters on the parents of the entity. + + * `notification_references(view)`, used to control References field + of email send on notification for this entity. `view` is the notification view. + Should return a list of eids which can be used to generate message ids + of previously sent email. + +`TreeMixIn` implements also the ITree interface (``cubicweb.interfaces``): + + * `parent()`, returns the parent entity if any, else None (e.g. if we are on the + root) + + * `children(entities=True, sametype=False)`, returns children entities + according to the `entities` parameter, return entity objects or the + equivalent result set. + + * `children_rql()`, returns the RQL query corresponding to the children + of the entity. + + * `is_leaf()`, returns True if the entity does not have any children. + + * `is_root()`, returns True if the entity does not have any parent. + + * `root()`, returns the root object of the tree representation of + the entity and its related entities. + +Example of use +`````````````` + +Imagine you defined three types of entities in your schema, and they +relates to each others as follows in ``schema.py``:: + + class Entity1(EntityType): + title = String() + is_related_to = SubjectRelation('Entity2', 'subject') + + class Entity2(EntityType): + title = String() + belongs_to = SubjectRelation('Entity3', 'subject') + + class Entity3(EntityType): + name = String() + +You would like to create a view that applies to both entity types +`Entity1` and `Entity2` and which lists the entities they are related to. +That means when you view `Entity1` you want to list all `Entity2`, and +when you view `Entity2` you want to list all `Entity3`. + +In ``entities.py``:: + + class Entity1(TreeMixIn, AnyEntity): + id = 'Entity1' + __implements__ = AnyEntity.__implements__ + (ITree,) + __rtags__ = {('is_related_to', 'Entity2', 'object'): 'link'} + tree_attribute = 'is_related_to' + + def children(self, entities=True): + return self.different_type_children(entities) + + class Entity2(TreeMixIn, AnyEntity): + id = 'Entity2' + __implements__ = AnyEntity.__implements__ + (ITree,) + __rtags__ = {('belongs_to', 'Entity3', 'object'): 'link'} + tree_attribute = 'belongs_to' + + def children(self, entities=True): + return self.different_type_children(entities) + +Once this is done, you can define your common view as follows:: + + class E1E2CommonView(baseviews.PrimaryView): + accepts = ('Entity11, 'Entity2') + + def render_entity_relations(self, entity, siderelations): + self.wview('list', entity.children(entities=False)) + *rtags* ------- diff -r 56e347b8189c -r 79755c89b4f3 doc/book/en/B0031-define-entities.en.txt --- a/doc/book/en/B0031-define-entities.en.txt Sat Apr 18 15:49:40 2009 -0500 +++ b/doc/book/en/B0031-define-entities.en.txt Sat Apr 18 15:50:04 2009 -0500 @@ -133,7 +133,7 @@ The method ``filterform_vocabulary(rtype, x, var, rqlst, args, cachekey)`` takes the name of a relation and the target as parameters, [XXX what does it mean ?] - which indicates of the +which indicates of the entity on which we apply the method is subject or object of the relation. It has to return: diff -r 56e347b8189c -r 79755c89b4f3 doc/book/en/B1020-define-views.en.txt --- a/doc/book/en/B1020-define-views.en.txt Sat Apr 18 15:49:40 2009 -0500 +++ b/doc/book/en/B1020-define-views.en.txt Sat Apr 18 15:50:04 2009 -0500 @@ -13,6 +13,8 @@ understanding of the classes and methods available, then detail the view selection principle which makes `CubicWeb` web interface very flexible. +A `View` is an object applied to another object such as an entity. + Basic class for views --------------------- diff -r 56e347b8189c -r 79755c89b4f3 doc/book/en/D010-faq.en.txt --- a/doc/book/en/D010-faq.en.txt Sat Apr 18 15:49:40 2009 -0500 +++ b/doc/book/en/D010-faq.en.txt Sat Apr 18 15:50:04 2009 -0500 @@ -145,9 +145,15 @@ It depends on what has been modified in the schema. - * Update of a non final relation. + * Update of an attribute permissions and properties: + ``synchronize_eschema('MyEntity')``. - * Update of a final relation. + * Update of a relation permissions and properties: + ``synchronize_rschema('MyRelation')``. + + * Add an attribute: ``add_attribute('MyEntityType', 'myattr')``. + + * Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``. * How to create an anonymous user? @@ -164,9 +170,16 @@ anonymous-password=anon You also must ensure that this `anon` user is a registered user of - the DB backend. This could be the admin account (for development + the DB backend. If not, you can create through the administation + interface of your instance by adding a user with the role `guests`. + This could be the admin account (for development purposes, of course). +.. note:: + While creating a new instance, you can decide to allow access + to anonymous user, which will automatically execute what is + decribed above. + * How to change the application logo? @@ -225,3 +238,13 @@ This will yield additional WARNINGs, like this: :: 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for + +* How to format an entity date attribute? + + If your schema has an attribute of type Date or Datetime, you might + want to format it. First, you should define your preferred format using + the site configuration panel ``http://appurl/view?vid=systemepropertiesform`` + and then set ``ui.date`` and/or ``ui.datetime``. + Then in the view code, use:: + + self.format_date(entity.date_attribute) diff -r 56e347b8189c -r 79755c89b4f3 rset.py --- a/rset.py Sat Apr 18 15:49:40 2009 -0500 +++ b/rset.py Sat Apr 18 15:50:04 2009 -0500 @@ -51,7 +51,9 @@ # set by the cursor which returned this resultset self.vreg = None self.req = None - + # actions cache + self._rsetactions = None + def __str__(self): if not self.rows: return '' % self.rql @@ -70,9 +72,19 @@ '\n'.join('%s (%s)' % (r, d) for r, d in zip(rows, self.description))) - @cached - def possible_actions(self): - return self.vreg.possible_vobjects('actions', self.req, self) + def possible_actions(self, **kwargs): + if self._rsetactions is None: + self._rsetactions = {} + if kwargs: + key = tuple(sorted(kwargs.iteritems())) + else: + key = None + try: + return self._rsetactions[key] + except KeyError: + actions = self.vreg.possible_vobjects('actions', self.req, self, **kwargs) + self._rsetactions[key] = actions + return actions def __len__(self): """returns the result set's size""" diff -r 56e347b8189c -r 79755c89b4f3 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Sat Apr 18 15:49:40 2009 -0500 +++ b/server/sources/rql2sql.py Sat Apr 18 15:50:04 2009 -0500 @@ -936,9 +936,9 @@ def visit_function(self, func, contextrels=None): """generate SQL name for a function""" # function_description will check function is supported by the backend - self.dbms_helper.function_description(func.name) - return '%s(%s)' % (func.name, ', '.join(c.accept(self, contextrels) - for c in func.children)) + sqlname = self.dbms_helper.func_sqlname(func.name) + return '%s(%s)' % (sqlname, ', '.join(c.accept(self, contextrels) + for c in func.children)) def visit_constant(self, constant, contextrels=None): """generate SQL name for a constant""" diff -r 56e347b8189c -r 79755c89b4f3 web/views/iprogress.py --- a/web/views/iprogress.py Sat Apr 18 15:49:40 2009 -0500 +++ b/web/views/iprogress.py Sat Apr 18 15:50:04 2009 -0500 @@ -40,7 +40,7 @@ accepts_interfaces = (IMileStone,) # default columns of the table - columns = (_('project'), _('milestone'), _('state'), _('eta_date'), _('planned_delivery'), + columns = (_('project'), _('milestone'), _('state'), _('eta_date'), _('cost'), _('progress'), _('todo_by')) @@ -134,12 +134,6 @@ formated_date = u'%s %s' % (_('expected:'), eta_date) return formated_date - def build_planned_delivery_cell(self, entity): - """``initial_prevision_date`` column cell renderer""" - if entity.finished(): - return self.format_date(entity.completion_date()) - return self.format_date(entity.initial_prevision_date()) - def build_todo_by_cell(self, entity): """``todo_by`` column cell renderer""" return u', '.join(p.view('outofcontext') for p in entity.contractors()) diff -r 56e347b8189c -r 79755c89b4f3 web/views/tabs.py --- a/web/views/tabs.py Sat Apr 18 15:49:40 2009 -0500 +++ b/web/views/tabs.py Sat Apr 18 15:50:04 2009 -0500 @@ -96,19 +96,25 @@ return selected_tabs def render_tabs(self, tabs, default, entity): + # tabbed views do no support concatenation + # hence we delegate to the default tab if there is more than on entity + # in the result set + if len(self.rset) > 1: + entity.view(default, w=self.w) + return + # XXX (syt) fix below add been introduced at some point to fix something + # (http://intranet.logilab.fr/jpl/ticket/32174 ?) but this is not a clean + # way. We must not consider form['rql'] here since it introduces some + # other failures on non rql queries (plain text, shortcuts,... handled by + # magicsearch) which has a single result whose primary view is using tabs + # (https://www.logilab.net/cwo/ticket/342789) + #rql = self.req.form.get('rql') + #if rql: + # self.req.execute(rql).get_entity(0,0).view(default, w=self.w) + # return self.req.add_css('ui.tabs.css') self.req.add_js(('ui.core.js', 'ui.tabs.js', 'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js')) - # tabbed views do no support concatenation - # hence we delegate to the default tab - form = self.req.form - if form.get('vid') == 'primary': - entity.view(default, w=self.w) - return - rql = form.get('rql') - if rql: - self.req.execute(rql).get_entity(0,0).view(default, w=self.w) - return # prune tabs : not all are to be shown tabs = self.prune_tabs(tabs) # select a tab