--- a/cwvreg.py Tue Mar 12 12:04:51 2013 +0100
+++ b/cwvreg.py Tue Mar 12 12:08:22 2013 +0100
@@ -197,7 +197,7 @@
from os.path import join, dirname, realpath
from warnings import warn
from datetime import datetime, date, time, timedelta
-from functools import partial
+from functools import partial, reduce
from logilab.common.decorators import cached, clear_cache
from logilab.common.deprecation import deprecated, class_deprecated
@@ -210,16 +210,25 @@
from yams.constraints import BASE_CONVERTERS
from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER,
- Binary, UnknownProperty, UnknownEid)
+ onevent, Binary, UnknownProperty, UnknownEid)
from cubicweb.predicates import (implements, appobject_selectable,
_reset_is_instance_cache)
-# backward compat: those modules are now refering to app objects in
-# cw.web.views.uicfg and import * from backward compat. On registry reload, we
-# should pop those modules from the cache so references are properly updated on
-# subsequent reload
-CW_EVENT_MANAGER.bind('before-registry-reload', partial(sys.modules.pop, 'cubicweb.web.uicfg', None))
-CW_EVENT_MANAGER.bind('before-registry-reload', partial(sys.modules.pop, 'cubicweb.web.uihelper', None))
+
+@onevent('before-registry-reload')
+def cleanup_uicfg_compat():
+ """ backward compat: those modules are now refering to app objects in
+ cw.web.views.uicfg and import * from backward compat. On registry reload, we
+ should pop those modules from the cache so references are properly updated on
+ subsequent reload
+ """
+ if 'cubicweb.web' in sys.modules:
+ if getattr(sys.modules['cubicweb.web'], 'uicfg', None):
+ del sys.modules['cubicweb.web'].uicfg
+ if getattr(sys.modules['cubicweb.web'], 'uihelper', None):
+ del sys.modules['cubicweb.web'].uihelper
+ sys.modules.pop('cubicweb.web.uicfg', None)
+ sys.modules.pop('cubicweb.web.uihelper', None)
def use_interfaces(obj):
"""return interfaces required by the given object by searching for
--- a/dbapi.py Tue Mar 12 12:04:51 2013 +0100
+++ b/dbapi.py Tue Mar 12 12:08:22 2013 +0100
@@ -103,15 +103,6 @@
The returned repository may be an in-memory repository or a proxy object
using a specific RPC method, depending on the given URI (pyro or zmq).
"""
- try:
- return _get_repository(uri, config, vreg)
- except ConnectionError:
- raise
- except Exception as exc:
- raise ConnectionError('cause: %r' % exc)
-
-def _get_repository(uri=None, config=None, vreg=None):
- """ implements get_repository (see above) """
if uri is None:
return _get_inmemory_repo(config, vreg)
--- a/devtools/httptest.py Tue Mar 12 12:04:51 2013 +0100
+++ b/devtools/httptest.py Tue Mar 12 12:08:22 2013 +0100
@@ -156,7 +156,7 @@
response = self.web_get('logout')
self._ident_cookie = None
- def web_get(self, path='', headers=None):
+ def web_request(self, path='', method='GET', body=None, headers=None)
"""Return an httplib.HTTPResponse object for the specified path
Use available credential if available.
@@ -166,12 +166,15 @@
if self._ident_cookie is not None:
assert 'Cookie' not in headers
headers['Cookie'] = self._ident_cookie
- self._web_test_cnx.request("GET", '/' + path, headers=headers)
+ self._web_test_cnx.request(method, '/' + path, headers=headers, body=body)
response = self._web_test_cnx.getresponse()
response.body = response.read() # to chain request
response.read = lambda : response.body
return response
+ def web_get(self, path='', body=None, headers=None):
+ return self.web_request(path=path, body=body, headers=headers)
+
def setUp(self):
super(CubicWebServerTC, self).setUp()
self.start_server()
--- a/devtools/testlib.py Tue Mar 12 12:04:51 2013 +0100
+++ b/devtools/testlib.py Tue Mar 12 12:08:22 2013 +0100
@@ -85,8 +85,7 @@
class JsonValidator(object):
def parse_string(self, data):
- json.loads(data)
- return data
+ return json.loads(data)
# email handling, to test emails sent by an application ########################
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/additionnal_services/index.rst Tue Mar 12 12:08:22 2013 +0100
@@ -0,0 +1,14 @@
+Additional services
+===================
+
+In this chapter, we introduce services crossing the *web -
+repository - administration* organisation of the first parts of the
+CubicWeb book. Those services can be either proper services (like the
+undo functionality) or mere *topical cross-sections* across CubicWeb.
+
+.. toctree::
+ :maxdepth: 2
+
+ undo
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/additionnal_services/undo.rst Tue Mar 12 12:08:22 2013 +0100
@@ -0,0 +1,337 @@
+Undoing changes in CubicWeb
+---------------------------
+
+Many desktop applications offer the possibility for the user to
+undo its last changes : this *undo feature* has now been
+integrated into the CubicWeb framework. This document will
+introduce you to the *undo feature* both from the end-user and the
+application developer point of view.
+
+But because a semantic web application and a common desktop
+application are not the same thing at all, especially as far as
+undoing is concerned, we will first introduce *what* is the *undo
+feature* for now.
+
+What's *undoing* in a CubicWeb application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+What is an *undo feature* is quite intuitive in the context of a
+desktop application. But it is a bit subtler in the context of a
+Semantic Web application. This section introduces some of the main
+differences between a classical desktop and a Semantic Web
+applications to keep in mind in order to state precisely *what we
+want*.
+
+The notion transactions
+```````````````````````
+
+A CubicWeb application acts upon an *Entity-Relationship* model,
+described by a schema. This allows to ensure some data integrity
+properties. It also implies that changes are made by all-or-none
+groups called *transactions*, such that the data integrity is
+preserved whether the transaction is completely applied *or* none
+of it is applied.
+
+A transaction can thus include more actions than just those
+directly required by the main purpose of the user. For example,
+when a user *just* writes a new blog entry, the underlying
+*transaction* holds several *actions* as illustrated below :
+
+* By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
+
+ #. Created Blog entry : Torototo
+ #. Added relation : Torototo owned by admin
+ #. Added relation : Torototo blog entry of Undo Blog
+ #. Added relation : Torototo in state draft (draft)
+ #. Added relation : Torototo created by admin
+
+Because of the very nature (all-or-none) of the transactions, the
+"undoable stuff" are the transactions and not the actions !
+
+Public and private actions within a transaction
+```````````````````````````````````````````````
+
+Actually, within the *transaction* "Created Blog entry :
+Torototo", two of those *actions* are said to be *public* and
+the others are said to be *private*. *Public* here means that the
+public actions (1 and 3) were directly requested by the end user ;
+whereas *private* means that the other actions (2, 4, 5) were
+triggered "under the hood" to fulfill various requirements for the
+user operation (ensuring integrity, security, ... ).
+
+And because quite a lot of actions can be triggered by a "simple"
+end-user request, most of which the end-user is not (and does not
+need or wish to be) aware, only the so-called public actions will
+appear [1]_ in the description of the an undoable transaction.
+
+* By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
+
+ #. Created Blog entry : Torototo
+ #. Added relation : Torototo blog entry of Undo Blog
+
+But note that both public and private actions will be undone
+together when the transaction is undone.
+
+(In)dependent transactions : the simple case
+````````````````````````````````````````````
+
+A CubicWeb application can be used *simultaneously* by different users
+(whereas a single user works on an given office document at a
+given time), so that there is not always a single history
+time-line in the CubicWeb case. Moreover CubicWeb provides
+security through the mechanism of *permissions* granted to each
+user. This can lead to some transactions *not* being undoable in
+some contexts.
+
+In the simple case two (unprivileged) users Alice and Bob make
+relatively independent changes : then both Alice and Bob can undo
+their changes. But in some case there is a clean dependency
+between Alice's and Bob's actions or between actions of one of
+them. For example let's suppose that :
+
+- Alice has created a blog,
+- then has published a first post inside,
+- then Bob has published a second post in the same blog,
+- and finally Alice has updated its post contents.
+
+Then it is clear that Alice can undo her contents changes and Bob
+can undo his post creation independently. But Alice can not undo
+her post creation while she has not first undone her changes.
+It is also clear that Bob should *not* have the
+permissions to undo any of Alice's transactions.
+
+
+More complex dependencies between transactions
+``````````````````````````````````````````````
+
+But more surprising things can quickly happen. Going back to the
+previous example, Alice *can* undo the creation of the blog after
+Bob has published its post in it ! But this is possible only
+because the schema does not *require* for a post to be in a
+blog. Would the *blog entry of* relation have been mandatory, then
+Alice could not have undone the blog creation because it would
+have broken integrity constraint for Bob's post.
+
+When a user attempts to undo a transaction the system will check
+whether a later transaction has explicit dependency on the
+would-be-undone transaction. In this case the system will not even
+attempt the undo operation and inform the user.
+
+If no such dependency is detected the system will attempt the undo
+operation but it can fail, typically because of integrity
+constraint violations. In such a case the undo operation is
+completely [3]_ rollbacked.
+
+
+The *undo feature* for CubicWeb end-users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The exposition of the undo feature to the end-user through a Web
+interface is still quite basic and will be improved toward a
+greater usability. But it is already fully functional. For now
+there are two ways to access the *undo feature* as long as the it
+has been activated in the instance configuration file with the
+option *undo-support=yes*.
+
+Immediately after having done the change to be canceled through
+the **undo** link in the message. This allows to undo an
+hastily action immediately. For example, just after having
+validated the creation of the blog entry *A second blog entry* we
+get the following message, allowing to undo the creation.
+
+.. image:: /images/undo_mesage_w600.png
+ :width: 600px
+ :alt: Screenshot of the undo link in the message
+ :align: center
+
+At any time we can access the **undo-history view** accessible from the
+start-up page.
+
+.. image:: /images/undo_startup-link_w600.png
+ :width: 600px
+ :alt: Screenshot of the startup menu with access to the history view
+ :align: center
+
+This view will provide inspection of the transaction and their (public)
+actions. Each transaction provides its own **undo** link. Only the
+transactions the user has permissions to see and undo will be shown.
+
+.. image:: /images/undo_history-view_w600.png
+ :width: 600px
+ :alt: Screenshot of the undo history main view
+ :align: center
+
+If the user attempts to undo a transaction which can't be undone or
+whose undoing fails, then a message will explain the situation and
+no partial undoing will be left behind.
+
+This is all for the end-user side of the undo mechanism : this is
+quite simple indeed ! Now, in the following section, we are going
+to introduce the developer side of the undo mechanism.
+
+The *undo feature* for CubicWeb application developers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A word of warning : this section is intended for developers,
+already having some knowledge of what's under CubicWeb's hood. If
+it is not *yet* the case, please refer to CubicWeb documentation
+http://docs.cubicweb.org/ .
+
+Overview
+````````
+
+The core of the undo mechanisms is at work in the *native source*,
+beyond the RQL. This does mean that *transactions* and *actions*
+are *no entities*. Instead they are represented at the SQL level
+and exposed through the *DB-API* supported by the repository
+*Connection* objects.
+
+Once the *undo feature* has been activated in the instance
+configuration file with the option *undo-support=yes*, each
+mutating operation (cf. [2]_) will be recorded in some special SQL
+table along with its associated transaction. Transaction are
+identified by a *txuuid* through which the functions of the
+*DB-API* handle them.
+
+On the web side the last commited transaction *txuuid* is
+remembered in the request's data to allow for imediate undoing
+whereas the *undo-history view* relies upon the *DB-API* to list
+the accessible transactions. The actual undoing is performed by
+the *UndoController* accessible at URL of the form
+`www.my.host/my/instance/undo?txuuid=...`
+
+The repository side
+```````````````````
+
+Please refer to the file `cubicweb/server/sources/native.py` and
+`cubicweb/transaction.py` for the details.
+
+The undoing information is mainly stored in three SQL tables:
+
+`transactions`
+ Stores the txuuid, the user eid and the date-and-time of
+ the transaction. This table is referenced by the two others.
+
+`tx_entity_actions`
+ Stores the undo information for actions on entities.
+
+`tx_relation_actions`
+ Stores the undo information for the actions on relations.
+
+When the undo support is activated, entries are added to those
+tables for each mutating operation on the data repository, and are
+deleted on each transaction undoing.
+
+Those table are accessible through the following methods of the
+repository `Connection` object :
+
+`undoable_transactions`
+ Returns a list of `Transaction` objects accessible to the user
+ and according to the specified filter(s) if any.
+
+`tx_info`
+ Returns a `Transaction` object from a `txuuid`
+
+`undo_transaction`
+ Returns the list of `Action` object for the given `txuuid`.
+
+ NB: By default it only return *public* actions.
+
+The web side
+````````````
+
+The exposure of the *undo feature* to the end-user through the Web
+interface relies on the *DB-API* introduced above. This implies
+that the *transactions* and *actions* are not *entities* linked by
+*relations* on which the usual views can be applied directly.
+
+That's why the file `cubicweb/web/views/undohistory.py` defines
+some dedicated views to access the undo information :
+
+`UndoHistoryView`
+ This is a *StartupView*, the one accessible from the home
+ page of the instance which list all transactions.
+
+`UndoableTransactionView`
+ This view handles the display of a single `Transaction` object.
+
+`UndoableActionBaseView`
+ This (abstract) base class provides private methods to build
+ the display of actions whatever their nature.
+
+`Undoable[Add|Remove|Create|Delete|Update]ActionView`
+ Those views all inherit from `UndoableActionBaseView` and
+ each handles a specific kind of action.
+
+`UndoableActionPredicate`
+ This predicate is used as a *selector* to pick the appropriate
+ view for actions.
+
+Apart from this main *undo-history view* a `txuuid` is stored in
+the request's data `last_undoable_transaction` in order to allow
+immediate undoing of a hastily validated operation. This is
+handled in `cubicweb/web/application.py` in the `main_publish` and
+`add_undo_link_to_msg` methods for the storing and displaying
+respectively.
+
+Once the undo information is accessible, typically through a
+`txuuid` in an *undo* URL, the actual undo operation can be
+performed by the `UndoController` defined in
+`cubicweb/web/views/basecontrollers.py`. This controller basically
+extracts the `txuuid` and performs a call to `undo_transaction` and
+in case of an undo-specific error, lets the top level publisher
+handle it as a validation error.
+
+
+Conclusion
+~~~~~~~~~~
+
+The undo mechanism relies upon a low level recording of the
+mutating operation on the repository. Those records are accessible
+through some method added to the *DB-API* and exposed to the
+end-user either through a whole history view of through an
+immediate undoing link in the message box.
+
+The undo feature is functional but the interface and configuration
+options are still quite reduced. One major improvement would be to
+be able to filter with a finer grain which transactions or actions
+one wants to see in the *undo-history view*. Another critical
+improvement would be to enable the undo feature on a part only of
+the entity-relationship schema to avoid storing too much useless
+data and reduce the underlying overhead.
+
+But both functionality are related to the strong design choice not
+to represent transactions and actions as entities and
+relations. This has huge benefits in terms of safety and conceptual
+simplicity but prevents from using lots of convenient CubicWeb
+features such as *facets* to access undo information.
+
+Before developing further the undo feature or eventually revising
+this design choice, it appears that some return of experience is
+strongly needed. So don't hesitate to try the undo feature in your
+application and send us some feedback.
+
+
+Notes
+~~~~~
+
+.. [1] The end-user Web interface could be improved to enable
+ user to choose whether he wishes to see private actions.
+
+.. [2] There is only five kind of elementary actions (beyond
+ merely accessing data for reading):
+
+ * **C** : creating an entity
+ * **D** : deleting an entity
+ * **U** : updating an entity attributes
+ * **A** : adding a relation
+ * **R** : removing a relation
+
+.. [3] Meaning none of the actions in the transaction is
+ undone. Depending upon the application, it might make sense
+ to enable *partial* undo. That is to say undo in which some
+ actions could not be undo without preventing to undo the
+ others actions in the transaction (as long as it does not
+ break schema integrity). This is not forbidden by the
+ back-end but is deliberately not supported by the front-end
+ (for now at least).
--- a/doc/book/en/devrepo/migration.rst Tue Mar 12 12:04:51 2013 +0100
+++ b/doc/book/en/devrepo/migration.rst Tue Mar 12 12:08:22 2013 +0100
@@ -139,7 +139,7 @@
* `drop_relation_type(rtype, commit=True)`, removes a relation type and all the
definitions of this type.
-* `rename_relationi_type(oldname, newname, commit=True)`, renames a relation type.
+* `rename_relation_type(oldname, newname, commit=True)`, renames a relation type.
* `add_relation_definition(subjtype, rtype, objtype, commit=True)`, adds a new
relation definition.
Binary file doc/book/en/images/undo_history-view_w600.png has changed
Binary file doc/book/en/images/undo_mesage_w600.png has changed
Binary file doc/book/en/images/undo_startup-link_w600.png has changed
--- a/doc/book/en/index.rst Tue Mar 12 12:04:51 2013 +0100
+++ b/doc/book/en/index.rst Tue Mar 12 12:08:22 2013 +0100
@@ -65,6 +65,7 @@
:maxdepth: 2
admin/index
+ additionnal_services/index
annexes/index
See also:
--- a/entity.py Tue Mar 12 12:04:51 2013 +0100
+++ b/entity.py Tue Mar 12 12:08:22 2013 +0100
@@ -521,8 +521,8 @@
Example (in a shell session):
- >>> companycls = vreg['etypes'].etype_class(('Company')
- >>> personcls = vreg['etypes'].etype_class(('Person')
+ >>> companycls = vreg['etypes'].etype_class('Company')
+ >>> personcls = vreg['etypes'].etype_class('Person')
>>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
>>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
... works_for=c)
--- a/etwist/server.py Tue Mar 12 12:04:51 2013 +0100
+++ b/etwist/server.py Tue Mar 12 12:08:22 2013 +0100
@@ -31,7 +31,6 @@
from datetime import date, timedelta
from urlparse import urlsplit, urlunsplit
from cgi import FieldStorage, parse_header
-from cStringIO import StringIO
from twisted.internet import reactor, task, threads
from twisted.internet.defer import maybeDeferred
@@ -40,6 +39,7 @@
from twisted.web.server import NOT_DONE_YET
+from logilab.mtconverter import xml_escape
from logilab.common.decorators import monkeypatch
from cubicweb import (AuthenticationError, ConfigurationError,
@@ -144,9 +144,8 @@
request.process_multipart()
return self._render_request(request)
except Exception:
- errorstream = StringIO()
- traceback.print_exc(file=errorstream)
- return HTTPResponse(stream='<pre>%s</pre>' % errorstream.getvalue(),
+ trace = traceback.format_exc()
+ return HTTPResponse(stream='<pre>%s</pre>' % xml_escape(trace),
code=500, twisted_request=request)
def _render_request(self, request):
--- a/server/repository.py Tue Mar 12 12:04:51 2013 +0100
+++ b/server/repository.py Tue Mar 12 12:08:22 2013 +0100
@@ -962,7 +962,7 @@
def close_sessions(self):
"""close every opened sessions"""
- for sessionid in self._sessions:
+ for sessionid in list(self._sessions):
try:
self.close(sessionid, checkshuttingdown=False)
except Exception: # XXX BaseException?
--- a/server/serverconfig.py Tue Mar 12 12:04:51 2013 +0100
+++ b/server/serverconfig.py Tue Mar 12 12:08:22 2013 +0100
@@ -82,7 +82,9 @@
"""serialize a repository source configuration as text"""
stream = StringIO()
optsbysect = list(sconfig.options_by_section())
- assert len(optsbysect) == 1, 'all options for a source should be in the same group'
+ assert len(optsbysect) == 1, (
+ 'all options for a source should be in the same group, got %s'
+ % [x[0] for x in optsbysect])
lgconfig.ini_format(stream, optsbysect[0][1], encoding)
return stream.getvalue()
--- a/server/sources/ldapfeed.py Tue Mar 12 12:04:51 2013 +0100
+++ b/server/sources/ldapfeed.py Tue Mar 12 12:08:22 2013 +0100
@@ -17,6 +17,7 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""cubicweb ldap feed source"""
+from cubicweb.cwconfig import merge_options
from cubicweb.server.sources import datafeed
from cubicweb.server import ldaputils
@@ -30,5 +31,7 @@
support_entities = {'CWUser': False}
use_cwuri_as_url = False
- options = datafeed.DataFeedSource.options + ldaputils.LDAPSourceMixIn.options
+ options = merge_options(datafeed.DataFeedSource.options
+ + ldaputils.LDAPSourceMixIn.options,
+ optgroup='ldap-source')
--- a/server/sources/ldapuser.py Tue Mar 12 12:04:51 2013 +0100
+++ b/server/sources/ldapuser.py Tue Mar 12 12:08:22 2013 +0100
@@ -88,9 +88,9 @@
def init(self, activated, source_entity):
"""method called by the repository once ready to handle request"""
+ super(LDAPUserSource, self).init(activated, source_entity)
if activated:
self.info('ldap init')
- self._entity_update(source_entity)
# set minimum period of 5min 1s (the additional second is to
# minimize resonnance effet)
if self.user_rev_attrs['email']:
--- a/server/sources/native.py Tue Mar 12 12:04:51 2013 +0100
+++ b/server/sources/native.py Tue Mar 12 12:08:22 2013 +0100
@@ -406,6 +406,7 @@
def init(self, activated, source_entity):
+ super(NativeSQLSource, self).init(activated, source_entity)
self.init_creating(source_entity._cw.cnxset)
try:
# test if 'asource' column exists
--- a/server/sources/remoterql.py Tue Mar 12 12:04:51 2013 +0100
+++ b/server/sources/remoterql.py Tue Mar 12 12:08:22 2013 +0100
@@ -136,6 +136,7 @@
def init(self, activated, source_entity):
"""method called by the repository once ready to handle request"""
+ super(RemoteSource, self).init(activated, source_entity)
self.load_mapping(source_entity._cw)
if activated:
interval = self.config['synchronization-interval']
--- a/web/data/cubicweb.facets.js Tue Mar 12 12:04:51 2013 +0100
+++ b/web/data/cubicweb.facets.js Tue Mar 12 12:08:22 2013 +0100
@@ -11,7 +11,7 @@
function copyParam(origparams, newparams, param) {
- var index = jQuery.inArray(param, origparams[0]);
+ var index = $.inArray(param, origparams[0]);
if (index > - 1) {
newparams[param] = origparams[1][index];
}
@@ -22,14 +22,14 @@
var names = [];
var values = [];
$form.find('.facet').each(function() {
- var facetName = jQuery(this).find('.facetTitle').attr('cubicweb:facetName');
+ var facetName = $(this).find('.facetTitle').attr('cubicweb:facetName');
// FacetVocabularyWidget
- jQuery(this).find('.facetValueSelected').each(function(x) {
+ $(this).find('.facetValueSelected').each(function(x) {
names.push(facetName);
values.push(this.getAttribute('cubicweb:value'));
});
// FacetStringWidget (e.g. has-text)
- jQuery(this).find('input:text').each(function(){
+ $(this).find('input:text').each(function(){
names.push(facetName);
values.push(this.value);
});
@@ -51,7 +51,7 @@
// XXX deprecate vidargs once TableView is gone
function buildRQL(divid, vid, paginate, vidargs) {
- jQuery(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
+ $(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
var $form = $('#' + divid + 'Form');
var zipped = facetFormContent($form);
zipped[0].push('facetargs');
@@ -59,7 +59,7 @@
var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('filter_build_rql', null, zipped[0], zipped[1]));
d.addCallback(function(result) {
var rql = result[0];
- var $bkLink = jQuery('#facetBkLink');
+ var $bkLink = $('#facetBkLink');
if ($bkLink.length) {
var bkPath = 'view?rql=' + encodeURIComponent(rql);
if (vid) {
@@ -68,7 +68,7 @@
var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + encodeURIComponent(bkPath);
$bkLink.attr('href', bkUrl);
}
- var $focusLink = jQuery('#focusLink');
+ var $focusLink = $('#focusLink');
if ($focusLink.length) {
var url = baseuri()+ 'view?rql=' + encodeURIComponent(rql);
if (vid) {
@@ -99,20 +99,20 @@
null, 'swap');
d.addCallback(function() {
// XXX rql/vid in extraparams
- jQuery(CubicWeb).trigger('facets-content-loaded', [divid, rql, vid, extraparams]);
+ $(CubicWeb).trigger('facets-content-loaded', [divid, rql, vid, extraparams]);
});
if (paginate) {
// FIXME the edit box might not be displayed in which case we don't
// know where to put the potential new one, just skip this case for
// now
- var $node = jQuery('#edit_box');
+ var $node = $('#edit_box');
if ($node.length) {
$node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('render', {
'rql': rql
},
'ctxcomponents', 'edit_box'));
}
- $node = jQuery('#breadcrumbs');
+ $node = $('#breadcrumbs');
if ($node.length) {
$node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('render', {
'rql': rql
@@ -121,7 +121,7 @@
}
}
var mainvar = null;
- var index = jQuery.inArray('mainvar', zipped[0]);
+ var index = $.inArray('mainvar', zipped[0]);
if (index > - 1) {
mainvar = zipped[1][index];
}
@@ -134,13 +134,13 @@
//$form.find('div[cubicweb\\:facetName="' + facetName + '"] ~ div .facetCheckBox').each(function() {
$form.find('div').filter(function () {return $(this).attr('cubicweb:facetName') == facetName}).parent().find('.facetCheckBox').each(function() {
var value = this.getAttribute('cubicweb:value');
- if (jQuery.inArray(value, values) == -1) {
- if (!jQuery(this).hasClass('facetValueDisabled')) {
- jQuery(this).addClass('facetValueDisabled');
+ if ($.inArray(value, values) == -1) {
+ if (!$(this).hasClass('facetValueDisabled')) {
+ $(this).addClass('facetValueDisabled');
}
} else {
- if (jQuery(this).hasClass('facetValueDisabled')) {
- jQuery(this).removeClass('facetValueDisabled');
+ if ($(this).hasClass('facetValueDisabled')) {
+ $(this).removeClass('facetValueDisabled');
}
}
});
@@ -153,8 +153,8 @@
function initFacetBoxEvents(root) {
// facetargs : (divid, vid, paginate, extraargs)
root = root || document;
- jQuery(root).find('form').each(function() {
- var form = jQuery(this);
+ $(root).find('form').each(function() {
+ var form = $(this);
// NOTE: don't evaluate facetargs here but in callbacks since its value
// may changes and we must send its value when the callback is
// called, not when the page is initialized
@@ -167,19 +167,19 @@
return false;
});
var divid = jsfacetargs[0];
- if (jQuery('#'+divid).length) {
+ if ($('#'+divid).length) {
var $loadingDiv = $(DIV({id:'facetLoading'},
facetLoadingMsg));
$loadingDiv.corner();
- $(jQuery('#'+divid).get(0).parentNode).append($loadingDiv);
- }
+ $($('#'+divid).get(0).parentNode).append($loadingDiv);
+ }
form.find('div.facet').each(function() {
- var facet = jQuery(this);
+ var facet = $(this);
facet.find('div.facetCheckBox').each(function(i) {
this.setAttribute('cubicweb:idx', i);
});
facet.find('div.facetCheckBox').click(function() {
- var $this = jQuery(this);
+ var $this = $(this);
// NOTE : add test on the facet operator (i.e. OR, AND)
// if ($this.hasClass('facetValueDisabled')){
// return
@@ -189,23 +189,22 @@
$this.find('img').each(function(i) {
if (this.getAttribute('cubicweb:unselimg')) {
this.setAttribute('src', UNSELECTED_BORDER_IMG);
- this.setAttribute('alt', (_("not selected")));
}
else {
this.setAttribute('src', UNSELECTED_IMG);
- this.setAttribute('alt', (_("not selected")));
}
+ this.setAttribute('alt', (_("not selected")));
});
var index = parseInt($this.attr('cubicweb:idx'));
// we dont need to move the element when cubicweb:idx == 0
if (index > 0) {
- var shift = jQuery.grep(facet.find('.facetValueSelected'), function(n) {
+ var shift = $.grep(facet.find('.facetValueSelected'), function(n) {
var nindex = parseInt(n.getAttribute('cubicweb:idx'));
return nindex > index;
}).length;
index += shift;
var parent = this.parentNode;
- var $insertAfter = jQuery(parent).find('.facetCheckBox:nth(' + index + ')');
+ var $insertAfter = $(parent).find('.facetCheckBox:nth(' + index + ')');
if (! ($insertAfter.length == 1 && shift == 0)) {
// only rearrange element if necessary
$insertAfter.after(this);
@@ -217,10 +216,10 @@
lastSelected.after(this);
} else {
var parent = this.parentNode;
- jQuery(parent).prepend(this);
+ $(parent).prepend(this);
}
- jQuery(this).addClass('facetValueSelected');
- var $img = jQuery(this).find('img');
+ $(this).addClass('facetValueSelected');
+ var $img = $(this).find('img');
$img.attr('src', SELECTED_IMG).attr('alt', (_("selected")));
}
buildRQL.apply(null, jsfacetargs);
@@ -237,7 +236,7 @@
});
facet.find('div.facetTitle.hideFacetBody').click(function() {
facet.find('div.facetBody').toggleClass('hidden').toggleClass('opened');
- jQuery(this).toggleClass('opened');
+ $(this).toggleClass('opened');
});
});
@@ -250,20 +249,20 @@
// persistent search (eg crih)
function reorderFacetsItems(root) {
root = root || document;
- jQuery(root).find('form').each(function() {
- var form = jQuery(this);
+ $(root).find('form').each(function() {
+ var form = $(this);
if (form.attr('cubicweb:facetargs')) {
form.find('div.facet').each(function() {
- var facet = jQuery(this);
+ var facet = $(this);
var lastSelected = null;
facet.find('div.facetCheckBox').each(function(i) {
- var $this = jQuery(this);
+ var $this = $(this);
if ($this.hasClass('facetValueSelected')) {
if (lastSelected) {
lastSelected.after(this);
} else {
var parent = this.parentNode;
- jQuery(parent).prepend(this);
+ $(parent).prepend(this);
}
lastSelected = $this;
}
@@ -290,18 +289,18 @@
// argument or without any argument. If we use `initFacetBoxEvents` as the
// direct callback on the jQuery.ready event, jQuery will pass some argument of
// his, so we use this small anonymous function instead.
-jQuery(document).ready(function() {
+$(document).ready(function() {
initFacetBoxEvents();
- jQuery(cw).bind('facets-content-loaded', onFacetContentLoaded);
- jQuery(cw).bind('facets-content-loading', onFacetFiltering);
- jQuery(cw).bind('facets-content-loading', updateFacetTitles);
+ $(cw).bind('facets-content-loaded', onFacetContentLoaded);
+ $(cw).bind('facets-content-loading', onFacetFiltering);
+ $(cw).bind('facets-content-loading', updateFacetTitles);
});
function showFacetLoading(parentid) {
var loadingWidth = 200; // px
var loadingHeight = 100; // px
- var $msg = jQuery('#facetLoading');
- var $parent = jQuery('#' + parentid);
+ var $msg = $('#facetLoading');
+ var $parent = $('#' + parentid);
var leftPos = $parent.offset().left + ($parent.width() - loadingWidth) / 2;
$parent.fadeTo('normal', 0.2);
$msg.css('left', leftPos).show();
@@ -312,11 +311,11 @@
}
function onFacetContentLoaded(event, divid, rql, vid, extraparams) {
- jQuery('#facetLoading').hide();
+ $('#facetLoading').hide();
}
-jQuery(document).ready(function () {
- if (jQuery('div.facetBody').length) {
+$(document).ready(function () {
+ if ($('div.facetBody').length) {
var $loadingDiv = $(DIV({id:'facetLoading'},
facetLoadingMsg));
$loadingDiv.corner();
--- a/web/facet.py Tue Mar 12 12:04:51 2013 +0100
+++ b/web/facet.py Tue Mar 12 12:08:22 2013 +0100
@@ -49,6 +49,7 @@
__docformat__ = "restructuredtext en"
_ = unicode
+from functools import reduce
from warnings import warn
from copy import deepcopy
from datetime import datetime, timedelta
--- a/web/formwidgets.py Tue Mar 12 12:04:51 2013 +0100
+++ b/web/formwidgets.py Tue Mar 12 12:08:22 2013 +0100
@@ -94,6 +94,7 @@
"""
__docformat__ = "restructuredtext en"
+from functools import reduce
from datetime import date
from warnings import warn
--- a/web/test/unittest_views_json.py Tue Mar 12 12:04:51 2013 +0100
+++ b/web/test/unittest_views_json.py Tue Mar 12 12:08:22 2013 +0100
@@ -35,14 +35,14 @@
rset = req.execute('Any GN,COUNT(X) GROUPBY GN ORDERBY GN WHERE X in_group G, G name GN')
data = self.view('jsonexport', rset)
self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
- self.assertEqual(data, '[["guests", 1], ["managers", 1]]')
+ self.assertListEqual(data, [["guests", 1], ["managers", 1]])
def test_json_rsetexport_empty_rset(self):
req = self.request()
rset = req.execute('Any X WHERE X is CWUser, X login "foobarbaz"')
data = self.view('jsonexport', rset)
self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
- self.assertEqual(data, '[]')
+ self.assertListEqual(data, [])
def test_json_rsetexport_with_jsonp(self):
req = self.request()
@@ -68,7 +68,7 @@
def test_json_ersetexport(self):
req = self.request()
rset = req.execute('Any G ORDERBY GN WHERE G is CWGroup, G name GN')
- data = json.loads(self.view('ejsonexport', rset))
+ data = self.view('ejsonexport', rset)
self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
self.assertEqual(data[0]['name'], 'guests')
self.assertEqual(data[1]['name'], 'managers')
--- a/web/views/basetemplates.py Tue Mar 12 12:04:51 2013 +0100
+++ b/web/views/basetemplates.py Tue Mar 12 12:08:22 2013 +0100
@@ -498,7 +498,7 @@
if config['auth-mode'] != 'http':
self.login_form(id) # Cookie authentication
w(u'</div>')
- if self._cw.https and config.anonymous_user()[0]:
+ if self._cw.https and config.anonymous_user()[0] and config['https-deny-anonymous']:
path = xml_escape(config['base-url'] + self._cw.relative_path())
w(u'<div class="loginMessage"><a href="%s">%s</a></div>\n'
% (path, self._cw._('No account? Try public access at %s') % path))
--- a/web/views/massmailing.py Tue Mar 12 12:04:51 2013 +0100
+++ b/web/views/massmailing.py Tue Mar 12 12:08:22 2013 +0100
@@ -21,6 +21,7 @@
_ = unicode
import operator
+from functools import reduce
from cubicweb.predicates import (is_instance, authenticated_user,
adaptable, match_form_params)