--- a/cwvreg.py Wed Mar 24 08:42:49 2010 +0100
+++ b/cwvreg.py Wed Mar 24 17:58:05 2010 +0100
@@ -22,12 +22,6 @@
from cubicweb.rtags import RTAGS
-@onevent('before-registry-reload')
-def clear_rtag_objects():
- for rtag in RTAGS:
- rtag.clear()
-
-
def use_interfaces(obj):
"""return interfaces used by the given object by searching for implements
selectors, with a bw compat fallback to accepts_interfaces attribute
@@ -265,6 +259,13 @@
self.schema = None
self.initialized = False
self.reset()
+ if self.config.mode != 'test':
+ # don't clear rtags during test, this may cause breakage with
+ # manually imported appobject modules
+ @onevent('before-registry-reload')
+ def clear_rtag_objects():
+ for rtag in RTAGS:
+ rtag.clear()
def setdefault(self, regid):
try:
--- a/doc/book/en/admin/additional-tips.rst Wed Mar 24 08:42:49 2010 +0100
+++ b/doc/book/en/admin/additional-tips.rst Wed Mar 24 17:58:05 2010 +0100
@@ -9,12 +9,10 @@
Backup, backup, backup
``````````````````````
-It is always a good idea to backup. If your system does not do that,
-you should set it up. Note that whenever you do an upgrade,
-`cubicweb-ctl` offers you to backup your database.
-
-There are a number of ways for doing backups. Before you go ahead,
-make sure the following permissions are correct ::
+It is always a good idea to backup. If your system does not do that, you should
+set it up. Note that whenever you do an upgrade, `cubicweb-ctl` offers you to
+backup your database. There are a number of ways for doing backups. Before you
+go ahead, make sure the following permissions are correct ::
# chgrp postgres /var/lib/cubicweb/backup
@@ -24,31 +22,36 @@
# chmod g+r /etc/cubicweb.d/*<instance>*/sources
-**Classic way**
+**Classic way on PostgreSQL server**
-Simply use the pg_dump in a cron ::
+Simply use the pg_dump in a cron installed for `postgres` user on the database server::
- su -c "pg_dump -Fc --username=cubicweb --no-owner" postgres > <your-instance>-$(date '+%Y-%m-%d_%H:%M:%S').dump
+ # m h dom mon dow command
+ 0 2 * * * pg_dump -Fc --username=cubicweb --no-owner <instance> > /var/backups/<instance>-$(date '+%Y-%m-%d_%H:%M:%S').dump
**CubicWeb way**
-The CubicWeb way is to use the `db-dump` command. For that, you have to put your passwords in a user-only-readable file at the
-root of the postgres user. The file is `.pgpass` (`chmod 0600`), in this case for a socket run connection to postgres ::
+The CubicWeb way is to use the `db-dump` command. For that, you have to put
+your passwords in a user-only-readable file at the home directory of root user.
+The file is `.pgpass` (`chmod 0600`), in this case for a socket run connection
+to PostgreSQL ::
- /var/run/postgresql:5432:<instance>:cubicweb:<password>
+ /var/run/postgresql:5432:<instance>:<database user>:<database password>
The postgres documentation for the `.pgpass` format can be found `here`_
-Then add the following command to the crontab of the postgres user (`su posgres 'crontab -e'`)::
+Then add the following command to the crontab of the user (`crontab -e`)::
# m h dom mon dow command
0 2 * * * cubicweb-ctl db-dump <instance>
**The automated sysadmin way**
-You can use a combination `backup-ninja`_ (which has a postgres script in the example directory), `backuppc`)_ (for versionning).
+You can use a combination `backup-ninja`_ (which has a postgres script in the
+example directory), `backuppc`)_ (for versionning).
-Please note that in the *CubicWeb way* it adds a second location for your password which is error-prone.
+Please note that in the *CubicWeb way* it adds a second location for your
+password which is error-prone.
.. _`here` : http://www.postgresql.org/docs/current/static/libpq-pgpass.html
.. _`backup-ninja` : https://labs.riseup.net/code/projects/show/backupninja/
--- a/entity.py Wed Mar 24 08:42:49 2010 +0100
+++ b/entity.py Wed Mar 24 17:58:05 2010 +0100
@@ -235,11 +235,13 @@
def __setitem__(self, attr, value):
"""override __setitem__ to update self.edited_attributes.
- Typically, a before_update_hook could do::
+ Typically, a before_[update|add]_hook could do::
entity['generated_attr'] = generated_value
- and this way, edited_attributes will be updated accordingly
+ and this way, edited_attributes will be updated accordingly. Also, add
+ the attribute to skip_security since we don't want to check security
+ for such attributes set by hooks.
"""
if attr == 'eid':
warn('[3.7] entity["eid"] = value is deprecated, use entity.eid = value instead',
@@ -247,14 +249,40 @@
self.eid = value
else:
super(Entity, self).__setitem__(attr, value)
- if hasattr(self, 'edited_attributes'):
+ # don't add attribute into skip_security if already in edited
+ # attributes, else we may accidentaly skip a desired security check
+ if hasattr(self, 'edited_attributes') and \
+ attr not in self.edited_attributes:
self.edited_attributes.add(attr)
self.skip_security_attributes.add(attr)
+ def __delitem__(self, attr):
+ """override __delitem__ to update self.edited_attributes on cleanup of
+ undesired changes introduced in the entity's dict. For example, see the
+ code snippet below from the `forge` cube:
+
+ .. sourcecode:: python
+
+ edited = self.entity.edited_attributes
+ has_load_left = 'load_left' in edited
+ if 'load' in edited and self.entity.load_left is None:
+ self.entity.load_left = self.entity['load']
+ elif not has_load_left and edited:
+ # cleanup, this may cause undesired changes
+ del self.entity['load_left']
+
+ """
+ super(Entity, self).__delitem__(attr)
+ if hasattr(self, 'edited_attributes'):
+ self.edited_attributes.remove(attr)
+
def setdefault(self, attr, default):
"""override setdefault to update self.edited_attributes"""
super(Entity, self).setdefault(attr, default)
- if hasattr(self, 'edited_attributes'):
+ # don't add attribute into skip_security if already in edited
+ # attributes, else we may accidentaly skip a desired security check
+ if hasattr(self, 'edited_attributes') and \
+ attr not in self.edited_attributes:
self.edited_attributes.add(attr)
self.skip_security_attributes.add(attr)
@@ -861,8 +889,8 @@
else:
restr = 'X %s Y' % attr
if values is None:
- execute('DELETE %s WHERE X eid %%(x)s' % restr,
- {'x': self.eid}, 'x')
+ self._cw.execute('DELETE %s WHERE X eid %%(x)s' % restr,
+ {'x': self.eid}, 'x')
continue
if not isinstance(values, (tuple, list, set, frozenset)):
values = (values,)
@@ -901,13 +929,16 @@
_ = unicode
else:
_ = self._cw._
- if creation or not hasattr(self, 'edited_attributes'):
+ if creation:
# on creations, we want to check all relations, especially
# required attributes
- relations = None
- else:
+ relations = [rschema for rschema in self.e_schema.subject_relations()
+ if rschema.final and rschema.type != 'eid']
+ elif hasattr(self, 'edited_attributes'):
relations = [self._cw.vreg.schema.rschema(rtype)
for rtype in self.edited_attributes]
+ else:
+ relations = None
self.e_schema.check(self, creation=creation, _=_,
relations=relations)
--- a/hooks/notification.py Wed Mar 24 08:42:49 2010 +0100
+++ b/hooks/notification.py Wed Mar 24 17:58:05 2010 +0100
@@ -112,12 +112,12 @@
if session.added_in_transaction(self.entity.eid):
return # entity is being created
# then compute changes
- changes = session.transaction_data.setdefault('changes', {})
- thisentitychanges = changes.setdefault(self.entity.eid, set())
attrs = [k for k in self.entity.edited_attributes
if not k in self.skip_attrs]
if not attrs:
return
+ changes = session.transaction_data.setdefault('changes', {})
+ thisentitychanges = changes.setdefault(self.entity.eid, set())
rqlsel, rqlrestr = [], ['X eid %(x)s']
for i, attr in enumerate(attrs):
var = chr(65+i)
--- a/hooks/security.py Wed Mar 24 08:42:49 2010 +0100
+++ b/hooks/security.py Wed Mar 24 17:58:05 2010 +0100
@@ -25,15 +25,16 @@
except AttributeError:
editedattrs = entity # XXX unexpected
for attr in editedattrs:
- try:
- dontcheck.remove(attr)
+ if attr in dontcheck:
continue
- except KeyError:
- pass
rdef = eschema.rdef(attr)
if rdef.final: # non final relation are checked by other hooks
# add/delete should be equivalent (XXX: unify them into 'update' ?)
rdef.check_perm(session, 'update', eid=eid)
+ # don't update dontcheck until everything went fine: see usage in
+ # after_update_entity, where if we got an Unauthorized at hook time, we will
+ # retry and commit time
+ dontcheck |= frozenset(editedattrs)
class _CheckEntityPermissionOp(hook.LateOperation):
--- a/server/session.py Wed Mar 24 08:42:49 2010 +0100
+++ b/server/session.py Wed Mar 24 17:58:05 2010 +0100
@@ -155,6 +155,9 @@
session = Session(user, self.repo)
threaddata = session._threaddata
threaddata.pool = self.pool
+ # share pending_operations, else operation added in the hi-jacked
+ # session such as SendMailOp won't ever be processed
+ threaddata.pending_operations = self.pending_operations
# everything in transaction_data should be copied back but the entity
# type cache we don't want to avoid security pb
threaddata.transaction_data = self.transaction_data.copy()
@@ -885,13 +888,15 @@
# deprecated ###############################################################
- @deprecated("[3.7] control security with session.[read|write]_security")
+ @deprecated("[3.7] execute is now unsafe by default in hooks/operation. You"
+ " can also control security with session.[read|write]_security")
def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
propagate=False):
"""like .execute but with security checking disabled (this method is
internal to the server, it's not part of the db-api)
"""
- return self.execute(rql, kwargs, eid_key, build_descr)
+ with security_enabled(self, read=False, write=False):
+ return self.execute(rql, kwargs, eid_key, build_descr)
@property
@deprecated("[3.7] is_super_session is deprecated, test "
--- a/server/test/unittest_rql2sql.py Wed Mar 24 08:42:49 2010 +0100
+++ b/server/test/unittest_rql2sql.py Wed Mar 24 17:58:05 2010 +0100
@@ -543,13 +543,10 @@
ORDER BY 4 DESC'''),
- ("Any X WHERE X eid 0, X eid 0",
- '''SELECT 0'''),
-
- ("Any X WHERE X eid 0, X eid 0, X test TRUE",
+ ("Any X WHERE X eid 0, X test TRUE",
'''SELECT _X.cw_eid
FROM cw_Personne AS _X
-WHERE _X.cw_eid=0 AND _X.cw_eid=0 AND _X.cw_test=TRUE'''),
+WHERE _X.cw_eid=0 AND _X.cw_test=TRUE'''),
("Any X,GROUP_CONCAT(TN) GROUPBY X ORDERBY XN WHERE T tags X, X name XN, T name TN, X is CWGroup",
'''SELECT _X.cw_eid, GROUP_CONCAT(_T.cw_name)
--- a/web/application.py Wed Mar 24 08:42:49 2010 +0100
+++ b/web/application.py Wed Mar 24 17:58:05 2010 +0100
@@ -123,7 +123,11 @@
SESSION_MANAGER = self.session_manager
if not 'last_login_time' in self.vreg.schema:
self._update_last_login_time = lambda x: None
- CW_EVENT_MANAGER.bind('after-registry-reload', self.reset_session_manager)
+ if self.vreg.config.mode != 'test':
+ # don't try to reset session manager during test, this leads to
+ # weird failures when running multiple tests
+ CW_EVENT_MANAGER.bind('after-registry-reload',
+ self.reset_session_manager)
def reset_session_manager(self):
data = self.session_manager.dump_data()
--- a/web/test/unittest_uicfg.py Wed Mar 24 08:42:49 2010 +0100
+++ b/web/test/unittest_uicfg.py Wed Mar 24 17:58:05 2010 +0100
@@ -1,10 +1,12 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web import uicfg
+abaa = uicfg.actionbox_appearsin_addmenu
+
class UICFGTC(CubicWebTC):
- def test(self):
- self.skip('write some tests')
+ def test_default_actionbox_appearsin_addmenu_config(self):
+ self.failIf(abaa.etype_get('TrInfo', 'wf_info_for', 'object', 'CWUser'))
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
--- a/web/views/actions.py Wed Mar 24 08:42:49 2010 +0100
+++ b/web/views/actions.py Wed Mar 24 17:58:05 2010 +0100
@@ -404,16 +404,8 @@
addmenu = uicfg.actionbox_appearsin_addmenu
addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
-addmenu.tag_subject_of(('*', 'wf_info_for', '*'), False)
-addmenu.tag_object_of(('*', 'wf_info_for', '*'), False)
-addmenu.tag_object_of(('*', 'state_of', 'CWEType'), True)
-addmenu.tag_object_of(('*', 'transition_of', 'CWEType'), True)
addmenu.tag_object_of(('*', 'relation_type', 'CWRType'), True)
addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
addmenu.tag_object_of(('*', 'in_group', 'CWGroup'), True)
addmenu.tag_object_of(('*', 'bookmarked_by', 'CWUser'), True)
-addmenu.tag_subject_of(('Transition', 'destination_state', '*'), True)
-addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
-addmenu.tag_object_of(('*', 'destination_state', 'State'), True)
-addmenu.tag_subject_of(('State', 'allowed_transition', '*'), True)
--- a/web/views/workflow.py Wed Mar 24 08:42:49 2010 +0100
+++ b/web/views/workflow.py Wed Mar 24 17:58:05 2010 +0100
@@ -35,6 +35,15 @@
_abaa.tag_subject_of(('State', 'allowed_transition', 'BaseTransition'), False)
_abaa.tag_object_of(('SubWorkflowExitPoint', 'destination_state', 'State'),
False)
+_abaa.tag_subject_of(('*', 'wf_info_for', '*'), False)
+_abaa.tag_object_of(('*', 'wf_info_for', '*'), False)
+
+_abaa.tag_object_of(('*', 'state_of', 'CWEType'), True)
+_abaa.tag_object_of(('*', 'transition_of', 'CWEType'), True)
+_abaa.tag_subject_of(('Transition', 'destination_state', '*'), True)
+_abaa.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
+_abaa.tag_object_of(('*', 'destination_state', 'State'), True)
+_abaa.tag_subject_of(('State', 'allowed_transition', '*'), True)
_abaa.tag_object_of(('State', 'state_of', 'Workflow'), True)
_abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
_abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
--- a/web/views/xmlrss.py Wed Mar 24 08:42:49 2010 +0100
+++ b/web/views/xmlrss.py Wed Mar 24 17:58:05 2010 +0100
@@ -50,11 +50,14 @@
self.w(u'<%s>\n' % (entity.e_schema))
for rschema, attrschema in entity.e_schema.attribute_definitions():
attr = rschema.type
- try:
- value = entity[attr]
- except KeyError:
- # Bytes
- continue
+ if attr == 'eid':
+ value = entity.eid
+ else:
+ try:
+ value = entity[attr]
+ except KeyError:
+ # Bytes
+ continue
if value is not None:
if attrschema == 'Bytes':
from base64 import b64encode