moving away from SQLObject; back to good ol' custom pickle storage. Also added
some nice javascript from scriptaculous.
This commit is contained in:
parent
b2384151f1
commit
52b04ed34f
|
@ -312,3 +312,39 @@ img.theme-icon {
|
|||
a.arrow {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#fields-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#fields-list li {
|
||||
cursor: move;
|
||||
list-style-type: none;
|
||||
margin: 4px 0;
|
||||
padding: 0;
|
||||
border: 1px solid #888;
|
||||
background: #ffe;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#fields-list li span.details {
|
||||
display: block;
|
||||
color: #555;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
||||
#fields-list li span.commands {
|
||||
float: right;
|
||||
margin-top: -1.2em;
|
||||
}
|
||||
|
||||
#fields-list img {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ div.PasswordWidget input {
|
|||
}
|
||||
|
||||
div.EmailWidget input[readonly=readonly],
|
||||
div.TextWidget textarea[readonly=readonly],
|
||||
div.StringWidget input[readonly=readonly] {
|
||||
border: 1px solid #ccc;
|
||||
background: #eee;
|
||||
|
|
|
@ -20,13 +20,17 @@ div#page {
|
|||
#top {
|
||||
color: #09F;
|
||||
background: #FFF url(img/top.jpg) no-repeat;
|
||||
height: 100px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#top h1 {
|
||||
margin: 0;
|
||||
padding-top: 30px;
|
||||
padding-left: 30px;
|
||||
padding-bottom: 20px;
|
||||
padding-right: 30px;
|
||||
line-height: 100px;
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#footer {
|
||||
|
@ -227,31 +231,25 @@ a.listing {
|
|||
a {
|
||||
text-decoration: none;
|
||||
color: #228;
|
||||
color: #113;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: black;
|
||||
color: #06C;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
#prelude {
|
||||
color: #DDD;
|
||||
color: #aaa;
|
||||
background: transparent;
|
||||
text-align: right;
|
||||
padding-right: 35px;
|
||||
position: relative;
|
||||
top: -70px;
|
||||
top: -85px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#prelude a {
|
||||
color: #DDD;
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#prelude a:hover {
|
||||
color: #999;
|
||||
background: transparent;
|
||||
p#breadcrumb {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,699 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
// includes drawing the autocompletion menu, observing keyboard
|
||||
// and mouse events, and similar.
|
||||
//
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// a getUpdatedChoices function that will be invoked every time
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// should get the text for which to provide autocompletion by
|
||||
// invoking this.getToken(), NOT by directly accessing
|
||||
// this.element.value. This is to allow incremental tokenized
|
||||
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
||||
// belongs in getUpdatedChoices.
|
||||
//
|
||||
// Tokenized incremental autocompletion is enabled automatically
|
||||
// when an autocompleter is instantiated with the 'tokens' option
|
||||
// in the options parameter, e.g.:
|
||||
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
||||
// will incrementally autocomplete with a comma as the token.
|
||||
// Additionally, ',' in the above example can be replaced with
|
||||
// a token array, e.g. { tokens: [',', '\n'] } which
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
var Autocompleter = {}
|
||||
Autocompleter.Base = function() {};
|
||||
Autocompleter.Base.prototype = {
|
||||
baseInitialize: function(element, update, options) {
|
||||
this.element = $(element);
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
|
||||
if (this.setOptions)
|
||||
this.setOptions(options);
|
||||
else
|
||||
this.options = options || {};
|
||||
|
||||
this.options.paramName = this.options.paramName || this.element.name;
|
||||
this.options.tokens = this.options.tokens || [];
|
||||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
|
||||
}
|
||||
new Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if (typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
|
||||
this.observer = null;
|
||||
|
||||
this.element.setAttribute('autocomplete','off');
|
||||
|
||||
Element.hide(this.update);
|
||||
|
||||
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
|
||||
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
this.iefix = $(this.update.id+'_iefix');
|
||||
}
|
||||
if(this.iefix) {
|
||||
Position.clone(this.update, this.iefix);
|
||||
this.iefix.style.zIndex = 1;
|
||||
this.update.style.zIndex = 2;
|
||||
Element.show(this.iefix);
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
|
||||
if(this.iefix) Element.hide(this.iefix);
|
||||
},
|
||||
|
||||
startIndicator: function() {
|
||||
if(this.options.indicator) Element.show(this.options.indicator);
|
||||
},
|
||||
|
||||
stopIndicator: function() {
|
||||
if(this.options.indicator) Element.hide(this.options.indicator);
|
||||
},
|
||||
|
||||
onKeyPress: function(event) {
|
||||
if(this.active)
|
||||
switch(event.keyCode) {
|
||||
case Event.KEY_TAB:
|
||||
case Event.KEY_RETURN:
|
||||
this.selectEntry();
|
||||
Event.stop(event);
|
||||
case Event.KEY_ESC:
|
||||
this.hide();
|
||||
this.active = false;
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_LEFT:
|
||||
case Event.KEY_RIGHT:
|
||||
return;
|
||||
case Event.KEY_UP:
|
||||
this.markPrevious();
|
||||
this.render();
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_DOWN:
|
||||
this.markNext();
|
||||
this.render();
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
|
||||
return;
|
||||
|
||||
this.changed = true;
|
||||
this.hasFocus = true;
|
||||
|
||||
if(this.observer) clearTimeout(this.observer);
|
||||
this.observer =
|
||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||
},
|
||||
|
||||
onHover: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
if(this.index != element.autocompleteIndex)
|
||||
{
|
||||
this.index = element.autocompleteIndex;
|
||||
this.render();
|
||||
}
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
this.index = element.autocompleteIndex;
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
onBlur: function(event) {
|
||||
// needed to make click events working
|
||||
setTimeout(this.hide.bind(this), 250);
|
||||
this.hasFocus = false;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if(this.entryCount > 0) {
|
||||
for (var i = 0; i < this.entryCount; i++)
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
Element.removeClassName(this.getEntry(i),"selected");
|
||||
|
||||
if(this.hasFocus) {
|
||||
this.show();
|
||||
this.active = true;
|
||||
}
|
||||
} else this.hide();
|
||||
},
|
||||
|
||||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
else this.index = this.entryCcount-1;
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
else this.index = 0;
|
||||
},
|
||||
|
||||
getEntry: function(index) {
|
||||
return this.update.firstChild.childNodes[index];
|
||||
},
|
||||
|
||||
getCurrentEntry: function() {
|
||||
return this.getEntry(this.index);
|
||||
},
|
||||
|
||||
selectEntry: function() {
|
||||
this.active = false;
|
||||
this.updateElement(this.getCurrentEntry());
|
||||
},
|
||||
|
||||
updateElement: function(selectedElement) {
|
||||
if (this.options.updateElement) {
|
||||
this.options.updateElement(selectedElement);
|
||||
return;
|
||||
}
|
||||
|
||||
var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
var lastTokenPos = this.findLastToken();
|
||||
if (lastTokenPos != -1) {
|
||||
var newValue = this.element.value.substr(0, lastTokenPos + 1);
|
||||
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
|
||||
if (whitespace)
|
||||
newValue += whitespace[0];
|
||||
this.element.value = newValue + value;
|
||||
} else {
|
||||
this.element.value = value;
|
||||
}
|
||||
this.element.focus();
|
||||
},
|
||||
|
||||
updateChoices: function(choices) {
|
||||
if(!this.changed && this.hasFocus) {
|
||||
this.update.innerHTML = choices;
|
||||
Element.cleanWhitespace(this.update);
|
||||
Element.cleanWhitespace(this.update.firstChild);
|
||||
|
||||
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
||||
this.entryCount =
|
||||
this.update.firstChild.childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
this.addObservers(entry);
|
||||
}
|
||||
} else {
|
||||
this.entryCount = 0;
|
||||
}
|
||||
|
||||
this.stopIndicator();
|
||||
|
||||
this.index = 0;
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
addObservers: function(element) {
|
||||
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
||||
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
onObserverEvent: function() {
|
||||
this.changed = false;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.startIndicator();
|
||||
this.getUpdatedChoices();
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
var tokenPos = this.findLastToken();
|
||||
if (tokenPos != -1)
|
||||
var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
|
||||
else
|
||||
var ret = this.element.value;
|
||||
|
||||
return /\n/.test(ret) ? '' : ret;
|
||||
},
|
||||
|
||||
findLastToken: function() {
|
||||
var lastTokenPos = -1;
|
||||
|
||||
for (var i=0; i<this.options.tokens.length; i++) {
|
||||
var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
|
||||
if (thisTokenPos > lastTokenPos)
|
||||
lastTokenPos = thisTokenPos;
|
||||
}
|
||||
return lastTokenPos;
|
||||
}
|
||||
}
|
||||
|
||||
Ajax.Autocompleter = Class.create();
|
||||
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
|
||||
initialize: function(element, update, url, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.asynchronous = true;
|
||||
this.options.onComplete = this.onComplete.bind(this);
|
||||
this.options.defaultParams = this.options.parameters || null;
|
||||
this.url = url;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams)
|
||||
this.options.parameters += '&' + this.options.defaultParams;
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
onComplete: function(request) {
|
||||
this.updateChoices(request.responseText);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// The local array autocompleter. Used when you'd prefer to
|
||||
// inject an array of autocompletion options into the page, rather
|
||||
// than sending out Ajax queries, which can be quite slow sometimes.
|
||||
//
|
||||
// The constructor takes four parameters. The first two are, as usual,
|
||||
// the id of the monitored textbox, and id of the autocompletion menu.
|
||||
// The third is the array you want to autocomplete from, and the fourth
|
||||
// is the options block.
|
||||
//
|
||||
// Extra local autocompletion options:
|
||||
// - choices - How many autocompletion choices to offer
|
||||
//
|
||||
// - partialSearch - If false, the autocompleter will match entered
|
||||
// text only at the beginning of strings in the
|
||||
// autocomplete array. Defaults to true, which will
|
||||
// match text at the beginning of any *word* in the
|
||||
// strings in the autocomplete array. If you want to
|
||||
// search anywhere in the string, additionally set
|
||||
// the option fullSearch to true (default: off).
|
||||
//
|
||||
// - fullSsearch - Search anywhere in autocomplete array strings.
|
||||
//
|
||||
// - partialChars - How many characters to enter before triggering
|
||||
// a partial match (unlike minChars, which defines
|
||||
// how many characters are required to do any match
|
||||
// at all). Defaults to 2.
|
||||
//
|
||||
// - ignoreCase - Whether to ignore case when autocompleting.
|
||||
// Defaults to true.
|
||||
//
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// option, if you prefer to write your own autocompletion logic.
|
||||
// In that case, the other options above will not apply unless
|
||||
// you support them.
|
||||
|
||||
Autocompleter.Local = Class.create();
|
||||
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
|
||||
initialize: function(element, update, array, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.array = array;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.updateChoices(this.options.selector(this));
|
||||
},
|
||||
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
choices: 10,
|
||||
partialSearch: true,
|
||||
partialChars: 2,
|
||||
ignoreCase: true,
|
||||
fullSearch: false,
|
||||
selector: function(instance) {
|
||||
var ret = []; // Beginning matches
|
||||
var partial = []; // Inside matches
|
||||
var entry = instance.getToken();
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
|
||||
var elem = instance.options.array[i];
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
elem.indexOf(entry);
|
||||
|
||||
while (foundPos != -1) {
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
break;
|
||||
} else if (entry.length >= instance.options.partialChars &&
|
||||
instance.options.partialSearch && foundPos != -1) {
|
||||
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
|
||||
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
||||
elem.indexOf(entry, foundPos + 1);
|
||||
|
||||
}
|
||||
}
|
||||
if (partial.length)
|
||||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
|
||||
return "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || {});
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX in-place editor
|
||||
//
|
||||
// The constructor takes three parameters. The first is the element
|
||||
// that should support in-place editing. The second is the url to submit
|
||||
// the changed value to. The server should respond with the updated
|
||||
// value (the server might have post-processed it or validation might
|
||||
// have prevented it from changing). The third is a hash of options.
|
||||
//
|
||||
// Supported options are (all are optional and have sensible defaults):
|
||||
// - okText - The text of the submit button that submits the changed value
|
||||
// to the server (default: "ok")
|
||||
// - cancelText - The text of the link that cancels editing (default: "cancel")
|
||||
// - savingText - The text being displayed as the AJAX engine communicates
|
||||
// with the server (default: "Saving...")
|
||||
// - formId - The id given to the <form> element
|
||||
// (default: the id of the element to edit plus '-inplaceeditor')
|
||||
|
||||
Ajax.InPlaceEditor = Class.create();
|
||||
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
|
||||
Ajax.InPlaceEditor.prototype = {
|
||||
initialize: function(element, url, options) {
|
||||
this.url = url;
|
||||
this.element = $(element);
|
||||
|
||||
this.options = Object.extend({
|
||||
okText: "ok",
|
||||
cancelText: "cancel",
|
||||
savingText: "Saving...",
|
||||
clickToEditText: "Click to edit",
|
||||
okText: "ok",
|
||||
rows: 1,
|
||||
onComplete: function(transport, element) {
|
||||
new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
|
||||
},
|
||||
onFailure: function(transport) {
|
||||
alert("Error communicating with the server: " + transport.responseText.stripTags());
|
||||
},
|
||||
callback: function(form) {
|
||||
return Form.serialize(form);
|
||||
},
|
||||
loadingText: 'Loading...',
|
||||
savingClassName: 'inplaceeditor-saving',
|
||||
formClassName: 'inplaceeditor-form',
|
||||
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
|
||||
highlightendcolor: "#FFFFFF",
|
||||
externalControl: null,
|
||||
ajaxOptions: {}
|
||||
}, options || {});
|
||||
|
||||
if(!this.options.formId && this.element.id) {
|
||||
this.options.formId = this.element.id + "-inplaceeditor";
|
||||
if ($(this.options.formId)) {
|
||||
// there's already a form with that name, don't specify an id
|
||||
this.options.formId = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.externalControl) {
|
||||
this.options.externalControl = $(this.options.externalControl);
|
||||
}
|
||||
|
||||
this.originalBackground = Element.getStyle(this.element, 'background-color');
|
||||
if (!this.originalBackground) {
|
||||
this.originalBackground = "transparent";
|
||||
}
|
||||
|
||||
this.element.title = this.options.clickToEditText;
|
||||
|
||||
this.onclickListener = this.enterEditMode.bindAsEventListener(this);
|
||||
this.mouseoverListener = this.enterHover.bindAsEventListener(this);
|
||||
this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
|
||||
Event.observe(this.element, 'click', this.onclickListener);
|
||||
Event.observe(this.element, 'mouseover', this.mouseoverListener);
|
||||
Event.observe(this.element, 'mouseout', this.mouseoutListener);
|
||||
if (this.options.externalControl) {
|
||||
Event.observe(this.options.externalControl, 'click', this.onclickListener);
|
||||
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
||||
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
||||
}
|
||||
},
|
||||
enterEditMode: function() {
|
||||
if (this.saving) return;
|
||||
if (this.editing) return;
|
||||
this.editing = true;
|
||||
this.onEnterEditMode();
|
||||
if (this.options.externalControl) {
|
||||
Element.hide(this.options.externalControl);
|
||||
}
|
||||
Element.hide(this.element);
|
||||
this.form = this.getForm();
|
||||
this.element.parentNode.insertBefore(this.form, this.element);
|
||||
},
|
||||
getForm: function() {
|
||||
form = document.createElement("form");
|
||||
form.id = this.options.formId;
|
||||
Element.addClassName(form, this.options.formClassName)
|
||||
form.onsubmit = this.onSubmit.bind(this);
|
||||
|
||||
this.createEditField(form);
|
||||
|
||||
if (this.options.textarea) {
|
||||
var br = document.createElement("br");
|
||||
form.appendChild(br);
|
||||
}
|
||||
|
||||
okButton = document.createElement("input");
|
||||
okButton.type = "submit";
|
||||
okButton.value = this.options.okText;
|
||||
form.appendChild(okButton);
|
||||
|
||||
cancelLink = document.createElement("a");
|
||||
cancelLink.href = "#";
|
||||
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
|
||||
cancelLink.onclick = this.onclickCancel.bind(this);
|
||||
form.appendChild(cancelLink);
|
||||
return form;
|
||||
},
|
||||
createEditField: function(form) {
|
||||
if (this.options.rows == 1) {
|
||||
this.options.textarea = false;
|
||||
var textField = document.createElement("input");
|
||||
textField.type = "text";
|
||||
textField.name = "value";
|
||||
textField.value = this.getText();
|
||||
textField.style.backgroundColor = this.options.highlightcolor;
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (size != 0)
|
||||
textField.size = size;
|
||||
form.appendChild(textField);
|
||||
this.editField = textField;
|
||||
} else {
|
||||
this.options.textarea = true;
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.name = "value";
|
||||
textArea.value = this.getText();
|
||||
textArea.rows = this.options.rows;
|
||||
textArea.cols = this.options.cols || 40;
|
||||
form.appendChild(textArea);
|
||||
this.editField = textArea;
|
||||
}
|
||||
},
|
||||
getText: function() {
|
||||
if (this.options.loadTextURL) {
|
||||
this.loadExternalText();
|
||||
return this.options.loadingText;
|
||||
} else {
|
||||
return this.element.innerHTML;
|
||||
}
|
||||
},
|
||||
loadExternalText: function() {
|
||||
new Ajax.Request(
|
||||
this.options.loadTextURL,
|
||||
{
|
||||
asynchronous: true,
|
||||
onComplete: this.onLoadedExternalText.bind(this)
|
||||
}
|
||||
);
|
||||
},
|
||||
onLoadedExternalText: function(transport) {
|
||||
this.form.value.value = transport.responseText.stripTags();
|
||||
},
|
||||
onclickCancel: function() {
|
||||
this.onComplete();
|
||||
this.leaveEditMode();
|
||||
return false;
|
||||
},
|
||||
onFailure: function(transport) {
|
||||
this.options.onFailure(transport);
|
||||
if (this.oldInnerHTML) {
|
||||
this.element.innerHTML = this.oldInnerHTML;
|
||||
this.oldInnerHTML = null;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onSubmit: function() {
|
||||
this.saving = true;
|
||||
new Ajax.Updater(
|
||||
{
|
||||
success: this.element,
|
||||
// don't update on failure (this could be an option)
|
||||
failure: null
|
||||
},
|
||||
this.url,
|
||||
Object.extend({
|
||||
parameters: this.options.callback(this.form, this.editField.value),
|
||||
onComplete: this.onComplete.bind(this),
|
||||
onFailure: this.onFailure.bind(this)
|
||||
}, this.options.ajaxOptions)
|
||||
);
|
||||
this.onLoading();
|
||||
return false;
|
||||
},
|
||||
onLoading: function() {
|
||||
this.saving = true;
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.showSaving();
|
||||
},
|
||||
showSaving: function() {
|
||||
this.oldInnerHTML = this.element.innerHTML;
|
||||
this.element.innerHTML = this.options.savingText;
|
||||
Element.addClassName(this.element, this.options.savingClassName);
|
||||
this.element.style.backgroundColor = this.originalBackground;
|
||||
Element.show(this.element);
|
||||
},
|
||||
removeForm: function() {
|
||||
if(this.form) {
|
||||
Element.remove(this.form);
|
||||
this.form = null;
|
||||
}
|
||||
},
|
||||
enterHover: function() {
|
||||
if (this.saving) return;
|
||||
this.element.style.backgroundColor = this.options.highlightcolor;
|
||||
if (this.effect) {
|
||||
this.effect.cancel();
|
||||
}
|
||||
Element.addClassName(this.element, this.options.hoverClassName)
|
||||
},
|
||||
leaveHover: function() {
|
||||
if (this.options.backgroundColor) {
|
||||
this.element.style.backgroundColor = this.oldBackground;
|
||||
}
|
||||
Element.removeClassName(this.element, this.options.hoverClassName)
|
||||
if (this.saving) return;
|
||||
this.effect = new Effect.Highlight(this.element, {
|
||||
startcolor: this.options.highlightcolor,
|
||||
endcolor: this.options.highlightendcolor,
|
||||
restorecolor: this.originalBackground
|
||||
});
|
||||
},
|
||||
leaveEditMode: function() {
|
||||
Element.removeClassName(this.element, this.options.savingClassName);
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.element.style.backgroundColor = this.originalBackground;
|
||||
Element.show(this.element);
|
||||
if (this.options.externalControl) {
|
||||
Element.show(this.options.externalControl);
|
||||
}
|
||||
this.editing = false;
|
||||
this.saving = false;
|
||||
this.oldInnerHTML = null;
|
||||
this.onLeaveEditMode();
|
||||
},
|
||||
onComplete: function(transport) {
|
||||
this.leaveEditMode();
|
||||
this.options.onComplete.bind(this)(transport, this.element);
|
||||
},
|
||||
onEnterEditMode: function() {},
|
||||
onLeaveEditMode: function() {},
|
||||
dispose: function() {
|
||||
if (this.oldInnerHTML) {
|
||||
this.element.innerHTML = this.oldInnerHTML;
|
||||
}
|
||||
this.leaveEditMode();
|
||||
Event.stopObserving(this.element, 'click', this.onclickListener);
|
||||
Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
|
||||
Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
|
||||
if (this.options.externalControl) {
|
||||
Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
|
||||
Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
||||
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,545 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// Element.Class part Copyright (c) 2005 by Rick Olson
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Droppables = {
|
||||
drops: false,
|
||||
|
||||
remove: function(element) {
|
||||
for(var i = 0; i < this.drops.length; i++)
|
||||
if(this.drops[i].element == element)
|
||||
this.drops.splice(i,1);
|
||||
},
|
||||
|
||||
add: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
greedy: true,
|
||||
hoverclass: null
|
||||
}, arguments[1] || {});
|
||||
|
||||
// cache containers
|
||||
if(options.containment) {
|
||||
options._containers = new Array();
|
||||
var containment = options.containment;
|
||||
if((typeof containment == 'object') &&
|
||||
(containment.constructor == Array)) {
|
||||
for(var i=0; i<containment.length; i++)
|
||||
options._containers.push($(containment[i]));
|
||||
} else {
|
||||
options._containers.push($(containment));
|
||||
}
|
||||
options._containers_length =
|
||||
options._containers.length-1;
|
||||
}
|
||||
|
||||
Element.makePositioned(element); // fix IE
|
||||
|
||||
options.element = element;
|
||||
|
||||
// activate the droppable
|
||||
if(!this.drops) this.drops = [];
|
||||
this.drops.push(options);
|
||||
},
|
||||
|
||||
isContained: function(element, drop) {
|
||||
var containers = drop._containers;
|
||||
var parentNode = element.parentNode;
|
||||
var i = drop._containers_length;
|
||||
do { if(parentNode==containers[i]) return true; } while (i--);
|
||||
return false;
|
||||
},
|
||||
|
||||
isAffected: function(pX, pY, element, drop) {
|
||||
return (
|
||||
(drop.element!=element) &&
|
||||
((!drop._containers) ||
|
||||
this.isContained(element, drop)) &&
|
||||
((!drop.accept) ||
|
||||
(Element.Class.has_any(element, drop.accept))) &&
|
||||
Position.within(drop.element, pX, pY) );
|
||||
},
|
||||
|
||||
deactivate: function(drop) {
|
||||
Element.Class.remove(drop.element, drop.hoverclass);
|
||||
this.last_active = null;
|
||||
},
|
||||
|
||||
activate: function(drop) {
|
||||
if(this.last_active) this.deactivate(this.last_active);
|
||||
if(drop.hoverclass)
|
||||
Element.Class.add(drop.element, drop.hoverclass);
|
||||
this.last_active = drop;
|
||||
},
|
||||
|
||||
show: function(event, element) {
|
||||
if(!this.drops) return;
|
||||
var pX = Event.pointerX(event);
|
||||
var pY = Event.pointerY(event);
|
||||
Position.prepare();
|
||||
|
||||
var i = this.drops.length-1; do {
|
||||
var drop = this.drops[i];
|
||||
if(this.isAffected(pX, pY, element, drop)) {
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
if(drop.greedy) {
|
||||
this.activate(drop);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (i--);
|
||||
},
|
||||
|
||||
fire: function(event, element) {
|
||||
if(!this.last_active) return;
|
||||
Position.prepare();
|
||||
|
||||
if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
|
||||
if (this.last_active.onDrop)
|
||||
this.last_active.onDrop(element, this.last_active.element);
|
||||
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if(this.last_active)
|
||||
this.deactivate(this.last_active);
|
||||
}
|
||||
}
|
||||
|
||||
var Draggables = {
|
||||
observers: new Array(),
|
||||
addObserver: function(observer) {
|
||||
this.observers.push(observer);
|
||||
},
|
||||
removeObserver: function(element) { // element instead of obsever fixes mem leaks
|
||||
for(var i = 0; i < this.observers.length; i++)
|
||||
if(this.observers[i].element && (this.observers[i].element == element))
|
||||
this.observers.splice(i,1);
|
||||
},
|
||||
notify: function(eventName, draggable) { // 'onStart', 'onEnd'
|
||||
for(var i = 0; i < this.observers.length; i++)
|
||||
this.observers[i][eventName](draggable);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Draggable = Class.create();
|
||||
Draggable.prototype = {
|
||||
initialize: function(element) {
|
||||
var options = Object.extend({
|
||||
handle: false,
|
||||
starteffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
|
||||
},
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false
|
||||
}, arguments[1] || {});
|
||||
|
||||
this.element = $(element);
|
||||
this.handle = options.handle ? $(options.handle) : this.element;
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
this.originalLeft = this.currentLeft();
|
||||
this.originalTop = this.currentTop();
|
||||
this.originalX = this.element.offsetLeft;
|
||||
this.originalY = this.element.offsetTop;
|
||||
this.originalZ = parseInt(this.element.style.zIndex || "0");
|
||||
|
||||
this.options = options;
|
||||
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
|
||||
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.update.bindAsEventListener(this);
|
||||
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||
|
||||
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||
},
|
||||
destroy: function() {
|
||||
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||
this.unregisterEvents();
|
||||
},
|
||||
registerEvents: function() {
|
||||
if(this.active) return;
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
Event.observe(document, "keypress", this.eventKeypress);
|
||||
},
|
||||
unregisterEvents: function() {
|
||||
if(!this.active) return;
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||
},
|
||||
currentLeft: function() {
|
||||
return parseInt(this.element.style.left || '0');
|
||||
},
|
||||
currentTop: function() {
|
||||
return parseInt(this.element.style.top || '0')
|
||||
},
|
||||
startDrag: function(event) {
|
||||
if(Event.isLeftClick(event)) {
|
||||
this.registerEvents();
|
||||
this.active = true;
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var offsets = Position.cumulativeOffset(this.element);
|
||||
this.offsetX = (pointer[0] - offsets[0]);
|
||||
this.offsetY = (pointer[1] - offsets[1]);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
finishDrag: function(event, success) {
|
||||
this.unregisterEvents();
|
||||
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
|
||||
if(this.options.ghosting) {
|
||||
Position.relativize(this.element);
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
if(success) Droppables.fire(event, this.element);
|
||||
Draggables.notify('onEnd', this);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && typeof revert == 'function') revert = revert(this.element);
|
||||
|
||||
if(revert && this.options.reverteffect) {
|
||||
this.options.reverteffect(this.element,
|
||||
this.currentTop()-this.originalTop,
|
||||
this.currentLeft()-this.originalLeft);
|
||||
} else {
|
||||
this.originalLeft = this.currentLeft();
|
||||
this.originalTop = this.currentTop();
|
||||
}
|
||||
|
||||
this.element.style.zIndex = this.originalZ;
|
||||
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
|
||||
Droppables.reset();
|
||||
},
|
||||
keyPress: function(event) {
|
||||
if(this.active) {
|
||||
if(event.keyCode==Event.KEY_ESC) {
|
||||
this.finishDrag(event, false);
|
||||
Event.stop(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
endDrag: function(event) {
|
||||
if(this.active && this.dragging) {
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
}
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
},
|
||||
draw: function(event) {
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var offsets = Position.cumulativeOffset(this.element);
|
||||
offsets[0] -= this.currentLeft();
|
||||
offsets[1] -= this.currentTop();
|
||||
var style = this.element.style;
|
||||
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||
style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = (pointer[1] - offsets[1] - this.offsetY) + "px";
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
update: function(event) {
|
||||
if(this.active) {
|
||||
if(!this.dragging) {
|
||||
var style = this.element.style;
|
||||
this.dragging = true;
|
||||
if(style.position=="") style.position = "relative";
|
||||
style.zIndex = this.options.zindex;
|
||||
|
||||
if(this.options.ghosting) {
|
||||
this._clone = this.element.cloneNode(true);
|
||||
Position.absolutize(this.element);
|
||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||
}
|
||||
|
||||
Draggables.notify('onStart', this);
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
}
|
||||
|
||||
Droppables.show(event, this.element);
|
||||
this.draw(event);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var SortableObserver = Class.create();
|
||||
SortableObserver.prototype = {
|
||||
initialize: function(element, observer) {
|
||||
this.element = $(element);
|
||||
this.observer = observer;
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
onStart: function() {
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
onEnd: function() {
|
||||
Sortable.unmark();
|
||||
if(this.lastValue != Sortable.serialize(this.element))
|
||||
this.observer(this.element)
|
||||
}
|
||||
}
|
||||
|
||||
var Sortable = {
|
||||
sortables: new Array(),
|
||||
options: function(element){
|
||||
element = $(element);
|
||||
for(var i=0;i<this.sortables.length;i++)
|
||||
if(this.sortables[i].element == element)
|
||||
return this.sortables[i];
|
||||
return null;
|
||||
},
|
||||
destroy: function(element){
|
||||
element = $(element);
|
||||
for(var i=0;i<this.sortables.length;i++) {
|
||||
if(this.sortables[i].element == element) {
|
||||
var s = this.sortables[i];
|
||||
Draggables.removeObserver(s.element);
|
||||
for(var j=0;j<s.droppables.length;j++)
|
||||
Droppables.remove(s.droppables[j]);
|
||||
for(j=0;j<s.draggables.length;j++)
|
||||
s.draggables[j].destroy();
|
||||
this.sortables.splice(i,1);
|
||||
}
|
||||
}
|
||||
},
|
||||
create: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
element: element,
|
||||
tag: 'li', // assumes li children, override with tag: 'tagname'
|
||||
dropOnEmpty: false,
|
||||
tree: false, // fixme: unimplemented
|
||||
overlap: 'vertical', // one of 'vertical', 'horizontal'
|
||||
constraint: 'vertical', // one of 'vertical', 'horizontal', false
|
||||
containment: element, // also takes array of elements (or id's); or false
|
||||
handle: false, // or a CSS class
|
||||
only: false,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
onChange: function() {},
|
||||
onUpdate: function() {}
|
||||
}, arguments[1] || {});
|
||||
|
||||
// clear any old sortable with same element
|
||||
this.destroy(element);
|
||||
|
||||
// build options for the draggables
|
||||
var options_for_draggable = {
|
||||
revert: true,
|
||||
ghosting: options.ghosting,
|
||||
constraint: options.constraint,
|
||||
handle: handle };
|
||||
|
||||
if(options.starteffect)
|
||||
options_for_draggable.starteffect = options.starteffect;
|
||||
|
||||
if(options.reverteffect)
|
||||
options_for_draggable.reverteffect = options.reverteffect;
|
||||
else
|
||||
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
|
||||
element.style.top = 0;
|
||||
element.style.left = 0;
|
||||
};
|
||||
|
||||
if(options.endeffect)
|
||||
options_for_draggable.endeffect = options.endeffect;
|
||||
|
||||
if(options.zindex)
|
||||
options_for_draggable.zindex = options.zindex;
|
||||
|
||||
// build options for the droppables
|
||||
var options_for_droppable = {
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover,
|
||||
greedy: !options.dropOnEmpty
|
||||
}
|
||||
|
||||
// fix for gecko engine
|
||||
Element.cleanWhitespace(element);
|
||||
|
||||
options.draggables = [];
|
||||
options.droppables = [];
|
||||
|
||||
// make it so
|
||||
|
||||
// drop on empty handling
|
||||
if(options.dropOnEmpty) {
|
||||
Droppables.add(element,
|
||||
{containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
|
||||
options.droppables.push(element);
|
||||
}
|
||||
|
||||
var elements = this.findElements(element, options);
|
||||
if(elements) {
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
// handles are per-draggable
|
||||
var handle = options.handle ?
|
||||
Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i];
|
||||
options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle })));
|
||||
Droppables.add(elements[i], options_for_droppable);
|
||||
|
||||
options.droppables.push(elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// keep reference
|
||||
this.sortables.push(options);
|
||||
|
||||
// for onupdate
|
||||
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
||||
|
||||
},
|
||||
|
||||
// return all suitable-for-sortable elements in a guaranteed order
|
||||
findElements: function(element, options) {
|
||||
if(!element.hasChildNodes()) return null;
|
||||
var elements = [];
|
||||
var children = element.childNodes;
|
||||
for(var i = 0; i<children.length; i++) {
|
||||
if(children[i].tagName && children[i].tagName==options.tag.toUpperCase() &&
|
||||
(!options.only || (Element.Class.has(children[i], options.only))))
|
||||
elements.push(children[i]);
|
||||
if(options.tree) {
|
||||
var grandchildren = this.findElements(children[i], options);
|
||||
if(grandchildren) elements.push(grandchildren);
|
||||
}
|
||||
}
|
||||
|
||||
return (elements.length>0 ? elements.flatten() : null);
|
||||
},
|
||||
|
||||
onHover: function(element, dropon, overlap) {
|
||||
if(overlap>0.5) {
|
||||
Sortable.mark(dropon, 'before');
|
||||
if(dropon.previousSibling != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, dropon);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
} else {
|
||||
Sortable.mark(dropon, 'after');
|
||||
var nextElement = dropon.nextSibling || null;
|
||||
if(nextElement != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, nextElement);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onEmptyHover: function(element, dropon) {
|
||||
if(element.parentNode!=dropon) {
|
||||
dropon.appendChild(element);
|
||||
}
|
||||
},
|
||||
|
||||
unmark: function() {
|
||||
if(Sortable._marker) Element.hide(Sortable._marker);
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker = $('dropmarker') || document.createElement('DIV');
|
||||
Element.hide(Sortable._marker);
|
||||
Element.Class.add(Sortable._marker, 'dropmarker');
|
||||
Sortable._marker.style.position = 'absolute';
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.style.top = offsets[1] + 'px';
|
||||
if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
|
||||
Sortable._marker.style.left = offsets[0] + 'px';
|
||||
Element.show(Sortable._marker);
|
||||
},
|
||||
|
||||
serialize: function(element) {
|
||||
element = $(element);
|
||||
var sortableOptions = this.options(element);
|
||||
var options = Object.extend({
|
||||
tag: sortableOptions.tag,
|
||||
only: sortableOptions.only,
|
||||
name: element.id
|
||||
}, arguments[1] || {});
|
||||
|
||||
var items = $(element).childNodes;
|
||||
var queryComponents = new Array();
|
||||
|
||||
for(var i=0; i<items.length; i++)
|
||||
if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() &&
|
||||
(!options.only || (Element.Class.has(items[i], options.only))))
|
||||
queryComponents.push(
|
||||
encodeURIComponent(options.name) + "[]=" +
|
||||
encodeURIComponent(items[i].id.split("_")[1]));
|
||||
|
||||
return queryComponents.join("&");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,707 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// Parts (c) 2005 Justin Palmer (http://encytemedia.com/)
|
||||
// Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
var Effect = {
|
||||
tagifyText: function(element) {
|
||||
var tagifyStyle = "position:relative";
|
||||
if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1";
|
||||
element = $(element);
|
||||
var children = element.childNodes;
|
||||
for (var i = 0; i < children.length; i++)
|
||||
if(children[i].nodeType==3) {
|
||||
var child = children[i];
|
||||
for (var j = 0; j < child.nodeValue.length; j++)
|
||||
element.insertBefore(
|
||||
Builder.node('span',{style: tagifyStyle},
|
||||
child.nodeValue.substr(j,1) == " " ? String.fromCharCode(160) :
|
||||
child.nodeValue.substr(j,1)), child);
|
||||
Element.remove(child);
|
||||
}
|
||||
},
|
||||
multiple: function(element, effect) {
|
||||
if(((typeof element == 'object') ||
|
||||
(typeof element == 'function')) &&
|
||||
(element.length))
|
||||
var elements = element;
|
||||
else
|
||||
var elements = $(element).childNodes;
|
||||
|
||||
var options = Object.extend({
|
||||
speed: 0.1,
|
||||
delay: 0.0
|
||||
}, arguments[2] || {});
|
||||
var speed = options.speed;
|
||||
var delay = options.delay;
|
||||
|
||||
for(var i = 0; i < elements.length; i++)
|
||||
new effect(elements[i],
|
||||
Object.extend(options, { delay: delay + i*speed }));
|
||||
}
|
||||
};
|
||||
|
||||
var Effect2 = Effect; // deprecated
|
||||
|
||||
/* ------------- transitions ------------- */
|
||||
|
||||
Effect.Transitions = {}
|
||||
|
||||
Effect.Transitions.linear = function(pos) {
|
||||
return pos;
|
||||
}
|
||||
Effect.Transitions.sinoidal = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI)/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.reverse = function(pos) {
|
||||
return 1-pos;
|
||||
}
|
||||
Effect.Transitions.flicker = function(pos) {
|
||||
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25);
|
||||
}
|
||||
Effect.Transitions.wobble = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.pulse = function(pos) {
|
||||
return (Math.floor(pos*10) % 2 == 0 ?
|
||||
(pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
|
||||
}
|
||||
Effect.Transitions.none = function(pos) {
|
||||
return 0;
|
||||
}
|
||||
Effect.Transitions.full = function(pos) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------- core effects ------------- */
|
||||
|
||||
Effect.Queue = {
|
||||
effects: [],
|
||||
interval: null,
|
||||
findLast: function() {
|
||||
var timestamp = false;
|
||||
for(var i = 0; i < this.effects.length; i++)
|
||||
if(!timestamp || (this.effects[i].finishOn>timestamp))
|
||||
timestamp = this.effects[i].finishOn;
|
||||
return timestamp;
|
||||
},
|
||||
add: function(effect) {
|
||||
var timestamp = new Date().getTime();
|
||||
|
||||
switch(effect.options.queue) {
|
||||
case 'front':
|
||||
// move unstarted effects after this effect
|
||||
for(var i = 0; i < this.effects.length; i++)
|
||||
if(this.effects[i].state == 'idle') {
|
||||
this.effects[i].startOn += effect.finishOn;
|
||||
this.effects[i].finishOn += effect.finishOn;
|
||||
}
|
||||
break;
|
||||
case 'end':
|
||||
// start effect after last queued effect has finished
|
||||
timestamp = this.findLast() || timestamp;
|
||||
break;
|
||||
}
|
||||
|
||||
effect.startOn += timestamp;
|
||||
effect.finishOn += timestamp;
|
||||
|
||||
this.effects.push(effect);
|
||||
|
||||
if(!this.interval)
|
||||
this.interval = setInterval(this.loop.bind(this), 40);
|
||||
},
|
||||
remove: function(effect) {
|
||||
for(var i = 0; i < this.effects.length; i++)
|
||||
if(this.effects[i]==effect) this.effects.splice(i,1);
|
||||
if(this.effects.length == 0) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
},
|
||||
loop: function() {
|
||||
var timePos = new Date().getTime();
|
||||
for(var i = 0; i < this.effects.length; i++) {
|
||||
this.effects[i].loop(timePos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Effect.Base = function() {};
|
||||
Effect.Base.prototype = {
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
transition: Effect.Transitions.sinoidal,
|
||||
duration: 1.0, // seconds
|
||||
fps: 25.0, // max. 25fps due to Effect.Queue implementation
|
||||
sync: false, // true for combining
|
||||
from: 0.0,
|
||||
to: 1.0,
|
||||
delay: 0.0,
|
||||
queue: 'parallel'
|
||||
}, options || {});
|
||||
},
|
||||
start: function(options) {
|
||||
this.setOptions(options || {});
|
||||
this.currentFrame = 0;
|
||||
this.state = 'idle';
|
||||
this.startOn = this.options.delay*1000;
|
||||
this.finishOn = this.startOn + (this.options.duration*1000);
|
||||
if(this.options.beforeStart) this.options.beforeStart(this);
|
||||
if(!this.options.sync) Effect.Queue.add(this);
|
||||
},
|
||||
loop: function(timePos) {
|
||||
if(timePos >= this.startOn) {
|
||||
if(timePos >= this.finishOn) {
|
||||
this.render(1.0);
|
||||
this.cancel();
|
||||
if(this.finish) this.finish();
|
||||
if(this.options.afterFinish) this.options.afterFinish(this);
|
||||
return;
|
||||
}
|
||||
var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
|
||||
var frame = Math.round(pos * this.options.fps * this.options.duration);
|
||||
if(frame > this.currentFrame) {
|
||||
this.render(pos);
|
||||
this.currentFrame = frame;
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function(pos) {
|
||||
if(this.state == 'idle') {
|
||||
this.state = 'running';
|
||||
if(this.setup) this.setup();
|
||||
}
|
||||
if(this.options.transition) pos = this.options.transition(pos);
|
||||
pos *= (this.options.to-this.options.from);
|
||||
pos += this.options.from;
|
||||
if(this.options.beforeUpdate) this.options.beforeUpdate(this);
|
||||
if(this.update) this.update(pos);
|
||||
if(this.options.afterUpdate) this.options.afterUpdate(this);
|
||||
},
|
||||
cancel: function() {
|
||||
if(!this.options.sync) Effect.Queue.remove(this);
|
||||
this.state = 'finished';
|
||||
}
|
||||
}
|
||||
|
||||
Effect.Parallel = Class.create();
|
||||
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
|
||||
initialize: function(effects) {
|
||||
this.effects = effects || [];
|
||||
this.start(arguments[1]);
|
||||
},
|
||||
update: function(position) {
|
||||
for (var i = 0; i < this.effects.length; i++)
|
||||
this.effects[i].render(position);
|
||||
},
|
||||
finish: function(position) {
|
||||
for (var i = 0; i < this.effects.length; i++) {
|
||||
this.effects[i].cancel();
|
||||
if(this.effects[i].finish) this.effects[i].finish(position);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Internet Explorer caveat: works only on elements that have
|
||||
// a 'layout', meaning having a given width or height.
|
||||
// There is no way to safely set this automatically.
|
||||
Effect.Opacity = Class.create();
|
||||
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
var options = Object.extend({
|
||||
from: 0.0,
|
||||
to: 1.0
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
update: function(position) {
|
||||
this.setOpacity(position);
|
||||
},
|
||||
setOpacity: function(opacity) {
|
||||
if(opacity<0.0001) opacity = 0; // fix errors with things like 6.152242992829571e-8
|
||||
if(opacity==1.0) {
|
||||
this.element.style.opacity = '0.999999';
|
||||
this.element.style.filter = null;
|
||||
} else {
|
||||
this.element.style.opacity = opacity;
|
||||
this.element.style.filter = "alpha(opacity:"+opacity*100+")";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Effect.MoveBy = Class.create();
|
||||
Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element, toTop, toLeft) {
|
||||
this.element = $(element);
|
||||
this.toTop = toTop;
|
||||
this.toLeft = toLeft;
|
||||
this.start(arguments[3]);
|
||||
},
|
||||
setup: function() {
|
||||
this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
|
||||
this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
|
||||
Element.makePositioned(this.element);
|
||||
},
|
||||
update: function(position) {
|
||||
topd = this.toTop * position + this.originalTop;
|
||||
leftd = this.toLeft * position + this.originalLeft;
|
||||
this.setPosition(topd, leftd);
|
||||
},
|
||||
setPosition: function(topd, leftd) {
|
||||
this.element.style.top = topd + "px";
|
||||
this.element.style.left = leftd + "px";
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Scale = Class.create();
|
||||
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element, percent) {
|
||||
this.element = $(element)
|
||||
var options = Object.extend({
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
scaleContent: true,
|
||||
scaleFromCenter: false,
|
||||
scaleMode: 'box', // 'box' or 'contents' or {} with provided values
|
||||
scaleFrom: 100.0,
|
||||
scaleTo: percent
|
||||
}, arguments[2] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function() {
|
||||
this.originalTop = this.element.offsetTop;
|
||||
this.originalLeft = this.element.offsetLeft;
|
||||
if(Element.getStyle(this.element,'font-size')=="") this.sizeEm = 1.0;
|
||||
if(Element.getStyle(this.element,'font-size') && Element.getStyle(this.element,'font-size').indexOf("em")>0)
|
||||
this.sizeEm = parseFloat(Element.getStyle(this.element,'font-size'));
|
||||
this.factor = (this.options.scaleTo/100.0) - (this.options.scaleFrom/100.0);
|
||||
if(this.options.scaleMode=='box') {
|
||||
this.originalHeight = this.element.clientHeight;
|
||||
this.originalWidth = this.element.clientWidth;
|
||||
} else
|
||||
if(this.options.scaleMode=='contents') {
|
||||
this.originalHeight = this.element.scrollHeight;
|
||||
this.originalWidth = this.element.scrollWidth;
|
||||
} else {
|
||||
this.originalHeight = this.options.scaleMode.originalHeight;
|
||||
this.originalWidth = this.options.scaleMode.originalWidth;
|
||||
}
|
||||
},
|
||||
update: function(position) {
|
||||
var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
|
||||
if(this.options.scaleContent && this.sizeEm)
|
||||
this.element.style.fontSize = this.sizeEm*currentScale + "em";
|
||||
this.setDimensions(
|
||||
this.originalWidth * currentScale,
|
||||
this.originalHeight * currentScale);
|
||||
},
|
||||
setDimensions: function(width, height) {
|
||||
if(this.options.scaleX) this.element.style.width = width + 'px';
|
||||
if(this.options.scaleY) this.element.style.height = height + 'px';
|
||||
if(this.options.scaleFromCenter) {
|
||||
var topd = (height - this.originalHeight)/2;
|
||||
var leftd = (width - this.originalWidth)/2;
|
||||
if(Element.getStyle(this.element,'position')=='absolute') {
|
||||
if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px";
|
||||
if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px";
|
||||
} else {
|
||||
if(this.options.scaleY) this.element.style.top = -topd + "px";
|
||||
if(this.options.scaleX) this.element.style.left = -leftd + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Highlight = Class.create();
|
||||
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
var options = Object.extend({
|
||||
startcolor: "#ffff99"
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function() {
|
||||
// try to parse current background color as default for endcolor
|
||||
// browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format
|
||||
if(!this.options.endcolor) {
|
||||
var endcolor = "#ffffff";
|
||||
var current = Element.getStyle(this.element, 'background-color');
|
||||
if(current && current.slice(0,4) == "rgb(") {
|
||||
endcolor = "#";
|
||||
var cols = current.slice(4,current.length-1).split(',');
|
||||
var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3);
|
||||
}
|
||||
this.options.endcolor = endcolor;
|
||||
}
|
||||
// init color calculations
|
||||
this.colors_base = [
|
||||
parseInt(this.options.startcolor.slice(1,3),16),
|
||||
parseInt(this.options.startcolor.slice(3,5),16),
|
||||
parseInt(this.options.startcolor.slice(5),16) ];
|
||||
this.colors_delta = [
|
||||
parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
|
||||
parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
|
||||
parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
|
||||
},
|
||||
update: function(position) {
|
||||
var colors = [
|
||||
Math.round(this.colors_base[0]+(this.colors_delta[0]*position)),
|
||||
Math.round(this.colors_base[1]+(this.colors_delta[1]*position)),
|
||||
Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ];
|
||||
this.element.style.backgroundColor = "#" +
|
||||
colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
|
||||
},
|
||||
finish: function() {
|
||||
this.element.style.backgroundColor = this.options.restorecolor;
|
||||
}
|
||||
});
|
||||
|
||||
Effect.ScrollTo = Class.create();
|
||||
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
this.start(arguments[1] || {});
|
||||
},
|
||||
setup: function() {
|
||||
Position.prepare();
|
||||
var offsets = Position.cumulativeOffset(this.element);
|
||||
var max = window.innerHeight ?
|
||||
window.height - window.innerHeight :
|
||||
document.body.scrollHeight -
|
||||
(document.documentElement.clientHeight ?
|
||||
document.documentElement.clientHeight : document.body.clientHeight);
|
||||
this.scrollStart = Position.deltaY;
|
||||
this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
|
||||
},
|
||||
update: function(position) {
|
||||
Position.prepare();
|
||||
window.scrollTo(Position.deltaX,
|
||||
this.scrollStart + (position*this.delta));
|
||||
}
|
||||
});
|
||||
|
||||
/* ------------- combination effects ------------- */
|
||||
|
||||
Effect.Fade = function(element) {
|
||||
var options = Object.extend({
|
||||
from: 1.0,
|
||||
to: 0.0,
|
||||
afterFinish: function(effect)
|
||||
{ Element.hide(effect.element);
|
||||
effect.setOpacity(1); }
|
||||
}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Appear = function(element) {
|
||||
var options = Object.extend({
|
||||
from: 0.0,
|
||||
to: 1.0,
|
||||
beforeStart: function(effect)
|
||||
{ effect.setOpacity(0);
|
||||
Element.show(effect.element); },
|
||||
afterUpdate: function(effect)
|
||||
{ Element.show(effect.element); }
|
||||
}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Puff = function(element) {
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
|
||||
Object.extend({ duration: 1.0,
|
||||
beforeUpdate: function(effect)
|
||||
{ effect.effects[0].element.style.position = 'absolute'; },
|
||||
afterFinish: function(effect)
|
||||
{ Element.hide(effect.effects[0].element); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.BlindUp = function(element) {
|
||||
element = $(element);
|
||||
Element.makeClipping(element);
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
afterFinish: function(effect)
|
||||
{
|
||||
Element.hide(effect.element);
|
||||
Element.undoClipping(effect.element);
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.BlindDown = function(element) {
|
||||
element = $(element);
|
||||
element.style.height = '0px';
|
||||
Element.makeClipping(element);
|
||||
Element.show(element);
|
||||
return new Effect.Scale(element, 100,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleMode: 'contents',
|
||||
scaleFrom: 0,
|
||||
afterFinish: function(effect) {
|
||||
Element.undoClipping(effect.element);
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SwitchOff = function(element) {
|
||||
return new Effect.Appear(element,
|
||||
{ duration: 0.4,
|
||||
transition: Effect.Transitions.flicker,
|
||||
afterFinish: function(effect)
|
||||
{ effect.element.style.overflow = 'hidden';
|
||||
new Effect.Scale(effect.element, 1,
|
||||
{ duration: 0.3, scaleFromCenter: true,
|
||||
scaleX: false, scaleContent: false,
|
||||
afterUpdate: function(effect) {
|
||||
if(effect.element.style.position=="")
|
||||
effect.element.style.position = 'relative'; },
|
||||
afterFinish: function(effect) { Element.hide(effect.element); }
|
||||
} )
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
Effect.DropOut = function(element) {
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.MoveBy(element, 100, 0, { sync: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
|
||||
Object.extend(
|
||||
{ duration: 0.5,
|
||||
afterFinish: function(effect)
|
||||
{ Element.hide(effect.effects[0].element); }
|
||||
}, arguments[1] || {}));
|
||||
}
|
||||
|
||||
Effect.Shake = function(element) {
|
||||
return new Effect.MoveBy(element, 0, 20,
|
||||
{ duration: 0.05, afterFinish: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, -40,
|
||||
{ duration: 0.1, afterFinish: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, 40,
|
||||
{ duration: 0.1, afterFinish: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, -40,
|
||||
{ duration: 0.1, afterFinish: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, 40,
|
||||
{ duration: 0.1, afterFinish: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, -20,
|
||||
{ duration: 0.05, afterFinish: function(effect) {
|
||||
}}) }}) }}) }}) }}) }});
|
||||
}
|
||||
|
||||
Effect.SlideDown = function(element) {
|
||||
element = $(element);
|
||||
element.style.height = '0px';
|
||||
Element.makeClipping(element);
|
||||
Element.cleanWhitespace(element);
|
||||
Element.makePositioned(element.firstChild);
|
||||
Element.show(element);
|
||||
return new Effect.Scale(element, 100,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleMode: 'contents',
|
||||
scaleFrom: 0,
|
||||
afterUpdate: function(effect)
|
||||
{ effect.element.firstChild.style.bottom =
|
||||
(effect.originalHeight - effect.element.clientHeight) + 'px'; },
|
||||
afterFinish: function(effect)
|
||||
{ Element.undoClipping(effect.element); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SlideUp = function(element) {
|
||||
element = $(element);
|
||||
Element.makeClipping(element);
|
||||
Element.cleanWhitespace(element);
|
||||
Element.makePositioned(element.firstChild);
|
||||
Element.show(element);
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
afterUpdate: function(effect)
|
||||
{ effect.element.firstChild.style.bottom =
|
||||
(effect.originalHeight - effect.element.clientHeight) + 'px'; },
|
||||
afterFinish: function(effect)
|
||||
{
|
||||
Element.hide(effect.element);
|
||||
Element.undoClipping(effect.element);
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.Squish = function(element) {
|
||||
return new Effect.Scale(element, 0,
|
||||
{ afterFinish: function(effect) { Element.hide(effect.element); } });
|
||||
}
|
||||
|
||||
Effect.Grow = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || {};
|
||||
|
||||
var originalWidth = element.clientWidth;
|
||||
var originalHeight = element.clientHeight;
|
||||
element.style.overflow = 'hidden';
|
||||
Element.show(element);
|
||||
|
||||
var direction = options.direction || 'center';
|
||||
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
|
||||
var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
|
||||
var opacityTransition = options.opacityTransition || Effect.Transitions.full;
|
||||
|
||||
var initialMoveX, initialMoveY;
|
||||
var moveX, moveY;
|
||||
|
||||
switch (direction) {
|
||||
case 'top-left':
|
||||
initialMoveX = initialMoveY = moveX = moveY = 0;
|
||||
break;
|
||||
case 'top-right':
|
||||
initialMoveX = originalWidth;
|
||||
initialMoveY = moveY = 0;
|
||||
moveX = -originalWidth;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
initialMoveX = moveX = 0;
|
||||
initialMoveY = originalHeight;
|
||||
moveY = -originalHeight;
|
||||
break;
|
||||
case 'bottom-right':
|
||||
initialMoveX = originalWidth;
|
||||
initialMoveY = originalHeight;
|
||||
moveX = -originalWidth;
|
||||
moveY = -originalHeight;
|
||||
break;
|
||||
case 'center':
|
||||
initialMoveX = originalWidth / 2;
|
||||
initialMoveY = originalHeight / 2;
|
||||
moveX = -originalWidth / 2;
|
||||
moveY = -originalHeight / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
|
||||
duration: 0.01,
|
||||
beforeUpdate: function(effect) { $(element).style.height = '0px'; },
|
||||
afterFinish: function(effect) {
|
||||
new Effect.Parallel(
|
||||
[ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
|
||||
new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }),
|
||||
new Effect.Scale(element, 100, {
|
||||
scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
|
||||
sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })],
|
||||
options); }
|
||||
});
|
||||
}
|
||||
|
||||
Effect.Shrink = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || {};
|
||||
|
||||
var originalWidth = element.clientWidth;
|
||||
var originalHeight = element.clientHeight;
|
||||
element.style.overflow = 'hidden';
|
||||
Element.show(element);
|
||||
|
||||
var direction = options.direction || 'center';
|
||||
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
|
||||
var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
|
||||
var opacityTransition = options.opacityTransition || Effect.Transitions.none;
|
||||
|
||||
var moveX, moveY;
|
||||
|
||||
switch (direction) {
|
||||
case 'top-left':
|
||||
moveX = moveY = 0;
|
||||
break;
|
||||
case 'top-right':
|
||||
moveX = originalWidth;
|
||||
moveY = 0;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
moveX = 0;
|
||||
moveY = originalHeight;
|
||||
break;
|
||||
case 'bottom-right':
|
||||
moveX = originalWidth;
|
||||
moveY = originalHeight;
|
||||
break;
|
||||
case 'center':
|
||||
moveX = originalWidth / 2;
|
||||
moveY = originalHeight / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
|
||||
new Effect.Scale(element, 0, { sync: true, transition: moveTransition }),
|
||||
new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ],
|
||||
options);
|
||||
}
|
||||
|
||||
Effect.Pulsate = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || {};
|
||||
var transition = options.transition || Effect.Transitions.sinoidal;
|
||||
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
|
||||
reverser.bind(transition);
|
||||
return new Effect.Opacity(element,
|
||||
Object.extend(Object.extend({ duration: 3.0,
|
||||
afterFinish: function(effect) { Element.show(effect.element); }
|
||||
}, options), {transition: reverser}));
|
||||
}
|
||||
|
||||
Effect.Fold = function(element) {
|
||||
element = $(element);
|
||||
element.style.overflow = 'hidden';
|
||||
return new Effect.Scale(element, 5, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleTo: 100,
|
||||
scaleX: false,
|
||||
afterFinish: function(effect) {
|
||||
new Effect.Scale(element, 1, {
|
||||
scaleContent: false,
|
||||
scaleTo: 0,
|
||||
scaleY: false,
|
||||
afterFinish: function(effect) { Element.hide(effect.element) } });
|
||||
}}, arguments[1] || {}));
|
||||
}
|
||||
|
||||
// old: new Effect.ContentZoom(element, percent)
|
||||
// new: Element.setContentZoom(element, percent)
|
||||
|
||||
Element.setContentZoom = function(element, percent) {
|
||||
element = $(element);
|
||||
element.style.fontSize = (percent/100) + "em";
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
var Scriptaculous = {
|
||||
Version: '1.5_pre4',
|
||||
require: function(libraryName) {
|
||||
// inserting via DOM fails in Safari 2.0, so brute force approach
|
||||
document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
|
||||
},
|
||||
load: function() {
|
||||
if((typeof Prototype=='undefined') ||
|
||||
parseFloat(Prototype.Version.split(".")[0] + "." +
|
||||
Prototype.Version.split(".")[1]) < 1.4)
|
||||
throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0");
|
||||
var scriptTags = document.getElementsByTagName("script");
|
||||
for(var i=0;i<scriptTags.length;i++) {
|
||||
if(scriptTags[i].src && scriptTags[i].src.match(/scriptaculous\.js$/)) {
|
||||
var path = scriptTags[i].src.replace(/scriptaculous\.js$/,'');
|
||||
this.require(path + 'util.js');
|
||||
this.require(path + 'effects.js');
|
||||
this.require(path + 'dragdrop.js');
|
||||
this.require(path + 'controls.js');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scriptaculous.load();
|
|
@ -0,0 +1,381 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
// experimental, Firefox-only
|
||||
Event.simulateMouse = function(element, eventName) {
|
||||
var options = Object.extend({
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
buttons: 0
|
||||
}, arguments[2] || {});
|
||||
var oEvent = document.createEvent("MouseEvents");
|
||||
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
|
||||
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
|
||||
false, false, false, false, 0, $(element));
|
||||
|
||||
if(this.mark) Element.remove(this.mark);
|
||||
this.mark = document.createElement('div');
|
||||
this.mark.appendChild(document.createTextNode(" "));
|
||||
document.body.appendChild(this.mark);
|
||||
this.mark.style.position = 'absolute';
|
||||
this.mark.style.top = options.pointerY + "px";
|
||||
this.mark.style.left = options.pointerX + "px";
|
||||
this.mark.style.width = "5px";
|
||||
this.mark.style.height = "5px;";
|
||||
this.mark.style.borderTop = "1px solid red;"
|
||||
this.mark.style.borderLeft = "1px solid red;"
|
||||
|
||||
if(this.step)
|
||||
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
|
||||
|
||||
$(element).dispatchEvent(oEvent);
|
||||
};
|
||||
|
||||
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
|
||||
// You need to downgrade to 1.0.4 for now to get this working
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
|
||||
Event.simulateKey = function(element, eventName) {
|
||||
var options = Object.extend({
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false,
|
||||
keyCode: 0,
|
||||
charCode: 0
|
||||
}, arguments[2] || {});
|
||||
|
||||
var oEvent = document.createEvent("KeyEvents");
|
||||
oEvent.initKeyEvent(eventName, true, true, window,
|
||||
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
|
||||
options.keyCode, options.charCode );
|
||||
$(element).dispatchEvent(oEvent);
|
||||
};
|
||||
|
||||
Event.simulateKeys = function(element, command) {
|
||||
for(var i=0; i<command.length; i++) {
|
||||
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
|
||||
}
|
||||
};
|
||||
|
||||
var Test = {}
|
||||
Test.Unit = {};
|
||||
|
||||
// security exception workaround
|
||||
Test.Unit.inspect = function(obj) {
|
||||
var info = [];
|
||||
|
||||
if(typeof obj=="string" ||
|
||||
typeof obj=="number") {
|
||||
return obj;
|
||||
} else {
|
||||
for(property in obj)
|
||||
if(typeof obj[property]!="function")
|
||||
info.push(property + ' => ' +
|
||||
(typeof obj[property] == "string" ?
|
||||
'"' + obj[property] + '"' :
|
||||
obj[property]));
|
||||
}
|
||||
|
||||
return ("'" + obj + "' #" + typeof obj +
|
||||
": {" + info.join(", ") + "}");
|
||||
}
|
||||
|
||||
Test.Unit.Logger = Class.create();
|
||||
Test.Unit.Logger.prototype = {
|
||||
initialize: function(log) {
|
||||
this.log = $(log);
|
||||
if (this.log) {
|
||||
this._createLogTable();
|
||||
}
|
||||
},
|
||||
start: function(testName) {
|
||||
if (!this.log) return;
|
||||
this.testName = testName;
|
||||
this.lastLogLine = document.createElement('tr');
|
||||
this.statusCell = document.createElement('td');
|
||||
this.nameCell = document.createElement('td');
|
||||
this.nameCell.appendChild(document.createTextNode(testName));
|
||||
this.messageCell = document.createElement('td');
|
||||
this.lastLogLine.appendChild(this.statusCell);
|
||||
this.lastLogLine.appendChild(this.nameCell);
|
||||
this.lastLogLine.appendChild(this.messageCell);
|
||||
this.loglines.appendChild(this.lastLogLine);
|
||||
},
|
||||
finish: function(status, summary) {
|
||||
if (!this.log) return;
|
||||
this.lastLogLine.className = status;
|
||||
this.statusCell.innerHTML = status;
|
||||
this.messageCell.innerHTML = this._toHTML(summary);
|
||||
},
|
||||
message: function(message) {
|
||||
if (!this.log) return;
|
||||
this.messageCell.innerHTML = this._toHTML(message);
|
||||
},
|
||||
summary: function(summary) {
|
||||
if (!this.log) return;
|
||||
this.logsummary.innerHTML = this._toHTML(summary);
|
||||
},
|
||||
_createLogTable: function() {
|
||||
this.log.innerHTML =
|
||||
'<div id="logsummary"></div>' +
|
||||
'<table id="logtable">' +
|
||||
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
|
||||
'<tbody id="loglines"></tbody>' +
|
||||
'</table>';
|
||||
this.logsummary = $('logsummary')
|
||||
this.loglines = $('loglines');
|
||||
},
|
||||
_toHTML: function(txt) {
|
||||
return txt.escapeHTML().replace(/\n/g,"<br/>");
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Runner = Class.create();
|
||||
Test.Unit.Runner.prototype = {
|
||||
initialize: function(testcases) {
|
||||
this.options = Object.extend({
|
||||
testLog: 'testlog'
|
||||
}, arguments[1] || {});
|
||||
this.options.resultsURL = this.parseResultsURLQueryParameter();
|
||||
if (this.options.testLog) {
|
||||
this.options.testLog = $(this.options.testLog) || null;
|
||||
}
|
||||
if(this.options.tests) {
|
||||
this.tests = [];
|
||||
for(var i = 0; i < this.options.tests.length; i++) {
|
||||
if(/^test/.test(this.options.tests[i])) {
|
||||
this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.options.test) {
|
||||
this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
|
||||
} else {
|
||||
this.tests = [];
|
||||
for(var testcase in testcases) {
|
||||
if(/^test/.test(testcase)) {
|
||||
this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.currentTest = 0;
|
||||
this.logger = new Test.Unit.Logger(this.options.testLog);
|
||||
setTimeout(this.runTests.bind(this), 1000);
|
||||
},
|
||||
parseResultsURLQueryParameter: function() {
|
||||
return window.location.search.parseQuery()["resultsURL"];
|
||||
},
|
||||
// Returns:
|
||||
// "ERROR" if there was an error,
|
||||
// "FAILURE" if there was a failure, or
|
||||
// "SUCCESS" if there was neither
|
||||
getResult: function() {
|
||||
var hasFailure = false;
|
||||
for(var i=0;i<this.tests.length;i++) {
|
||||
if (this.tests[i].errors > 0) {
|
||||
return "ERROR";
|
||||
}
|
||||
if (this.tests[i].failures > 0) {
|
||||
hasFailure = true;
|
||||
}
|
||||
}
|
||||
if (hasFailure) {
|
||||
return "FAILURE";
|
||||
} else {
|
||||
return "SUCCESS";
|
||||
}
|
||||
},
|
||||
postResults: function() {
|
||||
if (this.options.resultsURL) {
|
||||
new Ajax.Request(this.options.resultsURL,
|
||||
{ method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
|
||||
}
|
||||
},
|
||||
runTests: function() {
|
||||
var test = this.tests[this.currentTest];
|
||||
if (!test) {
|
||||
// finished!
|
||||
this.postResults();
|
||||
this.logger.summary(this.summary());
|
||||
return;
|
||||
}
|
||||
if(!test.isWaiting) {
|
||||
this.logger.start(test.name);
|
||||
}
|
||||
test.run();
|
||||
if(test.isWaiting) {
|
||||
this.logger.message("Waiting for " + test.timeToWait + "ms");
|
||||
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
|
||||
} else {
|
||||
this.logger.finish(test.status(), test.summary());
|
||||
this.currentTest++;
|
||||
// tail recursive, hopefully the browser will skip the stackframe
|
||||
this.runTests();
|
||||
}
|
||||
},
|
||||
summary: function() {
|
||||
var assertions = 0;
|
||||
var failures = 0;
|
||||
var errors = 0;
|
||||
var messages = [];
|
||||
for(var i=0;i<this.tests.length;i++) {
|
||||
assertions += this.tests[i].assertions;
|
||||
failures += this.tests[i].failures;
|
||||
errors += this.tests[i].errors;
|
||||
}
|
||||
return (
|
||||
this.tests.length + " tests, " +
|
||||
assertions + " assertions, " +
|
||||
failures + " failures, " +
|
||||
errors + " errors");
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Assertions = Class.create();
|
||||
Test.Unit.Assertions.prototype = {
|
||||
initialize: function() {
|
||||
this.assertions = 0;
|
||||
this.failures = 0;
|
||||
this.errors = 0;
|
||||
this.messages = [];
|
||||
},
|
||||
summary: function() {
|
||||
return (
|
||||
this.assertions + " assertions, " +
|
||||
this.failures + " failures, " +
|
||||
this.errors + " errors" + "\n" +
|
||||
this.messages.join("\n"));
|
||||
},
|
||||
pass: function() {
|
||||
this.assertions++;
|
||||
},
|
||||
fail: function(message) {
|
||||
this.failures++;
|
||||
this.messages.push("Failure: " + message);
|
||||
},
|
||||
error: function(error) {
|
||||
this.errors++;
|
||||
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
|
||||
},
|
||||
status: function() {
|
||||
if (this.failures > 0) return 'failed';
|
||||
if (this.errors > 0) return 'error';
|
||||
return 'passed';
|
||||
},
|
||||
assert: function(expression) {
|
||||
var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
|
||||
try { expression ? this.pass() :
|
||||
this.fail(message); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertEqual";
|
||||
try { (expected == actual) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertNotEqual";
|
||||
try { (expected != actual) ? this.pass() :
|
||||
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNull: function(obj) {
|
||||
var message = arguments[1] || 'assertNull'
|
||||
try { (obj==null) ? this.pass() :
|
||||
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertHidden: function(element) {
|
||||
var message = arguments[1] || 'assertHidden';
|
||||
this.assertEqual("none", element.style.display, message);
|
||||
},
|
||||
assertNotNull: function(object) {
|
||||
var message = arguments[1] || 'assertNotNull';
|
||||
this.assert(object != null, message);
|
||||
},
|
||||
assertInstanceOf: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertInstanceOf';
|
||||
try {
|
||||
(actual instanceof expected) ? this.pass() :
|
||||
this.fail(message + ": object was not an instance of the expected type"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotInstanceOf: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertNotInstanceOf';
|
||||
try {
|
||||
!(actual instanceof expected) ? this.pass() :
|
||||
this.fail(message + ": object was an instance of the not expected type"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
_isVisible: function(element) {
|
||||
element = $(element);
|
||||
if(!element.parentNode) return true;
|
||||
this.assertNotNull(element);
|
||||
if(element.style && Element.getStyle(element, 'display') == 'none')
|
||||
return false;
|
||||
|
||||
return this._isVisible(element.parentNode);
|
||||
},
|
||||
assertNotVisible: function(element) {
|
||||
this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
|
||||
},
|
||||
assertVisible: function(element) {
|
||||
this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Testcase = Class.create();
|
||||
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
|
||||
initialize: function(name, test, setup, teardown) {
|
||||
Test.Unit.Assertions.prototype.initialize.bind(this)();
|
||||
this.name = name;
|
||||
this.test = test || function() {};
|
||||
this.setup = setup || function() {};
|
||||
this.teardown = teardown || function() {};
|
||||
this.isWaiting = false;
|
||||
this.timeToWait = 1000;
|
||||
},
|
||||
wait: function(time, nextPart) {
|
||||
this.isWaiting = true;
|
||||
this.test = nextPart;
|
||||
this.timeToWait = time;
|
||||
},
|
||||
run: function() {
|
||||
try {
|
||||
try {
|
||||
if (!this.isWaiting) this.setup.bind(this)();
|
||||
this.isWaiting = false;
|
||||
this.test.bind(this)();
|
||||
} finally {
|
||||
if(!this.isWaiting) {
|
||||
this.teardown.bind(this)();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) { this.error(e); }
|
||||
}
|
||||
});
|
|
@ -0,0 +1,429 @@
|
|||
// small but works-for-me stuff for testing javascripts
|
||||
// not ready for "production" use
|
||||
|
||||
Object.inspect = function(obj) {
|
||||
var info = [];
|
||||
|
||||
if(typeof obj in ["string","number"]) {
|
||||
return obj;
|
||||
} else {
|
||||
for(property in obj)
|
||||
if(typeof obj[property]!="function")
|
||||
info.push(property + ' => ' +
|
||||
(typeof obj[property] == "string" ?
|
||||
'"' + obj[property] + '"' :
|
||||
obj[property]));
|
||||
}
|
||||
|
||||
return ("'" + obj + "' #" + typeof obj +
|
||||
": {" + info.join(", ") + "}");
|
||||
}
|
||||
|
||||
// borrowed from http://www.schuerig.de/michael/javascript/stdext.js
|
||||
// Copyright (c) 2005, Michael Schuerig, michael@schuerig.de
|
||||
|
||||
Array.flatten = function(array, excludeUndefined) {
|
||||
if (excludeUndefined === undefined) {
|
||||
excludeUndefined = false;
|
||||
}
|
||||
var result = [];
|
||||
var len = array.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var el = array[i];
|
||||
if (el instanceof Array) {
|
||||
var flat = el.flatten(excludeUndefined);
|
||||
result = result.concat(flat);
|
||||
} else if (!excludeUndefined || el != undefined) {
|
||||
result.push(el);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
if (!Array.prototype.flatten) {
|
||||
Array.prototype.flatten = function(excludeUndefined) {
|
||||
return Array.flatten(this, excludeUndefined);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Builder = {
|
||||
node: function(elementName) {
|
||||
var element = document.createElement('div');
|
||||
element.innerHTML =
|
||||
"<" + elementName + "></" + elementName + ">";
|
||||
|
||||
// attributes (or text)
|
||||
if(arguments[1])
|
||||
if(this._isStringOrNumber(arguments[1]) ||
|
||||
(arguments[1] instanceof Array)) {
|
||||
this._children(element.firstChild, arguments[1]);
|
||||
} else {
|
||||
var attrs = this._attributes(arguments[1]);
|
||||
if(attrs.length)
|
||||
element.innerHTML = "<" +elementName + " " +
|
||||
attrs + "></" + elementName + ">";
|
||||
}
|
||||
|
||||
// text, or array of children
|
||||
if(arguments[2])
|
||||
this._children(element.firstChild, arguments[2]);
|
||||
|
||||
return element.firstChild;
|
||||
},
|
||||
_text: function(text) {
|
||||
return document.createTextNode(text);
|
||||
},
|
||||
_attributes: function(attributes) {
|
||||
var attrs = [];
|
||||
for(attribute in attributes)
|
||||
attrs.push((attribute=='className' ? 'class' : attribute) +
|
||||
'="' + attributes[attribute].toString().escapeHTML() + '"');
|
||||
return attrs.join(" ");
|
||||
},
|
||||
_children: function(element, children) {
|
||||
if(typeof children=='object') { // array can hold nodes and text
|
||||
children = children.flatten();
|
||||
for(var i = 0; i<children.length; i++)
|
||||
if(typeof children[i]=='object')
|
||||
element.appendChild(children[i]);
|
||||
else
|
||||
if(this._isStringOrNumber(children[i]))
|
||||
element.appendChild(this._text(children[i]));
|
||||
} else
|
||||
if(this._isStringOrNumber(children))
|
||||
element.appendChild(this._text(children));
|
||||
},
|
||||
_isStringOrNumber: function(param) {
|
||||
return(typeof param=='string' || typeof param=='number');
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------- element ext -------------- */
|
||||
|
||||
// adapted from http://dhtmlkitchen.com/learn/js/setstyle/index4.jsp
|
||||
// note: Safari return null on elements with display:none; see http://bugzilla.opendarwin.org/show_bug.cgi?id=4125
|
||||
// instead of "auto" values returns null so it's easier to use with || constructs
|
||||
|
||||
String.prototype.camelize = function() {
|
||||
var oStringList = this.split('-');
|
||||
if(oStringList.length == 1)
|
||||
return oStringList[0];
|
||||
var ret = this.indexOf("-") == 0 ?
|
||||
oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];
|
||||
for(var i = 1, len = oStringList.length; i < len; i++){
|
||||
var s = oStringList[i];
|
||||
ret += s.charAt(0).toUpperCase() + s.substring(1)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Element.getStyle = function(element, style) {
|
||||
element = $(element);
|
||||
var value = element.style[style.camelize()];
|
||||
if(!value)
|
||||
if(document.defaultView && document.defaultView.getComputedStyle) {
|
||||
var css = document.defaultView.getComputedStyle(element, null);
|
||||
value = (css!=null) ? css.getPropertyValue(style) : null;
|
||||
} else if(element.currentStyle) {
|
||||
value = element.currentStyle[style.camelize()];
|
||||
}
|
||||
if(value=='auto') value = null;
|
||||
return value;
|
||||
}
|
||||
|
||||
Element.makePositioned = function(element) {
|
||||
element = $(element);
|
||||
if(Element.getStyle(element, 'position')=='static')
|
||||
element.style.position = "relative";
|
||||
}
|
||||
|
||||
Element.makeClipping = function(element) {
|
||||
element = $(element);
|
||||
element._overflow = Element.getStyle(element, 'overflow') || 'visible';
|
||||
if(element._overflow!='hidden') element.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
Element.undoClipping = function(element) {
|
||||
element = $(element);
|
||||
if(element._overflow!='hidden') element.style.overflow = element._overflow;
|
||||
}
|
||||
|
||||
Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
||||
var children = $(element).childNodes;
|
||||
var text = "";
|
||||
var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
|
||||
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if(children[i].nodeType==3) {
|
||||
text+=children[i].nodeValue;
|
||||
} else {
|
||||
if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
|
||||
text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
Position.positionedOffset = function(element) {
|
||||
var valueT = 0, valueL = 0;
|
||||
do {
|
||||
valueT += element.offsetTop || 0;
|
||||
valueL += element.offsetLeft || 0;
|
||||
element = element.offsetParent;
|
||||
if (element) {
|
||||
p = Element.getStyle(element,'position');
|
||||
if(p == 'relative' || p == 'absolute') break;
|
||||
}
|
||||
} while (element);
|
||||
return [valueL, valueT];
|
||||
}
|
||||
|
||||
// Safari returns margins on body which is incorrect if the child is absolutely positioned.
|
||||
// for performance reasons, we create a specialized version of Position.positionedOffset for
|
||||
// KHTML/WebKit only
|
||||
|
||||
if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
|
||||
Position.cumulativeOffset = function(element) {
|
||||
var valueT = 0, valueL = 0;
|
||||
do {
|
||||
valueT += element.offsetTop || 0;
|
||||
valueL += element.offsetLeft || 0;
|
||||
|
||||
if (element.offsetParent==document.body)
|
||||
if (Element.getStyle(element,'position')=='absolute') break;
|
||||
|
||||
element = element.offsetParent;
|
||||
} while (element);
|
||||
return [valueL, valueT];
|
||||
}
|
||||
}
|
||||
|
||||
Position.page = function(forElement) {
|
||||
if(element == document.body) return [0, 0];
|
||||
var valueT = 0, valueL = 0;
|
||||
|
||||
var element = forElement;
|
||||
do {
|
||||
valueT += element.offsetTop || 0;
|
||||
valueL += element.offsetLeft || 0;
|
||||
|
||||
// Safari fix
|
||||
if (element.offsetParent==document.body)
|
||||
if (Element.getStyle(element,'position')=='absolute') break;
|
||||
|
||||
} while (element = element.offsetParent);
|
||||
|
||||
element = forElement;
|
||||
do {
|
||||
valueT -= element.scrollTop || 0;
|
||||
valueL -= element.scrollLeft || 0;
|
||||
} while (element = element.parentNode);
|
||||
|
||||
return [valueL, valueT];
|
||||
}
|
||||
|
||||
// elements with display:none don't return an offsetParent,
|
||||
// fall back to manual calculation
|
||||
Position.offsetParent = function(element) {
|
||||
if(element.offsetParent) return element.offsetParent;
|
||||
if(element == document.body) return element;
|
||||
|
||||
while ((element = element.parentNode) && element != document.body)
|
||||
if (Element.getStyle(element,'position')!='static')
|
||||
return element;
|
||||
|
||||
return document.body;
|
||||
}
|
||||
|
||||
Position.clone = function(source, target) {
|
||||
var options = Object.extend({
|
||||
setLeft: true,
|
||||
setTop: true,
|
||||
setWidth: true,
|
||||
setHeight: true,
|
||||
offsetTop: 0,
|
||||
offsetLeft: 0
|
||||
}, arguments[2] || {})
|
||||
|
||||
// find page position of source
|
||||
source = $(source);
|
||||
var p = Position.page(source);
|
||||
|
||||
// find coordinate system to use
|
||||
target = $(target);
|
||||
var delta = [0, 0];
|
||||
var parent = null;
|
||||
// delta [0,0] will do fine with position: fixed elements,
|
||||
// position:absolute needs offsetParent deltas
|
||||
if (Element.getStyle(target,'position') == 'absolute') {
|
||||
parent = Position.offsetParent(target);
|
||||
delta = Position.page(parent);
|
||||
}
|
||||
|
||||
// correct by body offsets (fixes Safari)
|
||||
if (parent==document.body) {
|
||||
delta[0] -= document.body.offsetLeft;
|
||||
delta[1] -= document.body.offsetTop;
|
||||
}
|
||||
|
||||
// set position
|
||||
if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + "px";
|
||||
if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + "px";
|
||||
if(options.setWidth) target.style.width = source.offsetWidth + "px";
|
||||
if(options.setHeight) target.style.height = source.offsetHeight + "px";
|
||||
}
|
||||
|
||||
Position.absolutize = function(element) {
|
||||
element = $(element);
|
||||
if(element.style.position=='absolute') return;
|
||||
Position.prepare();
|
||||
|
||||
var offsets = Position.positionedOffset(element);
|
||||
var top = offsets[1];
|
||||
var left = offsets[0];
|
||||
var width = element.clientWidth;
|
||||
var height = element.clientHeight;
|
||||
|
||||
element._originalLeft = left - parseFloat(element.style.left || 0);
|
||||
element._originalTop = top - parseFloat(element.style.top || 0);
|
||||
element._originalWidth = element.style.width;
|
||||
element._originalHeight = element.style.height;
|
||||
|
||||
element.style.position = 'absolute';
|
||||
element.style.top = top + 'px';;
|
||||
element.style.left = left + 'px';;
|
||||
element.style.width = width + 'px';;
|
||||
element.style.height = height + 'px';;
|
||||
}
|
||||
|
||||
Position.relativize = function(element) {
|
||||
element = $(element);
|
||||
if(element.style.position=='relative') return;
|
||||
Position.prepare();
|
||||
|
||||
element.style.position = 'relative';
|
||||
var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
|
||||
var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
|
||||
|
||||
element.style.top = top + 'px';
|
||||
element.style.left = left + 'px';
|
||||
element.style.height = element._originalHeight;
|
||||
element.style.width = element._originalWidth;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
Element.Class = {
|
||||
// Element.toggleClass(element, className) toggles the class being on/off
|
||||
// Element.toggleClass(element, className1, className2) toggles between both classes,
|
||||
// defaulting to className1 if neither exist
|
||||
toggle: function(element, className) {
|
||||
if(Element.Class.has(element, className)) {
|
||||
Element.Class.remove(element, className);
|
||||
if(arguments.length == 3) Element.Class.add(element, arguments[2]);
|
||||
} else {
|
||||
Element.Class.add(element, className);
|
||||
if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
|
||||
}
|
||||
},
|
||||
|
||||
// gets space-delimited classnames of an element as an array
|
||||
get: function(element) {
|
||||
element = $(element);
|
||||
return element.className.split(' ');
|
||||
},
|
||||
|
||||
// functions adapted from original functions by Gavin Kistner
|
||||
remove: function(element) {
|
||||
element = $(element);
|
||||
var regEx;
|
||||
for(var i = 1; i < arguments.length; i++) {
|
||||
regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)", 'g');
|
||||
element.className = element.className.replace(regEx, '')
|
||||
}
|
||||
},
|
||||
|
||||
add: function(element) {
|
||||
element = $(element);
|
||||
for(var i = 1; i < arguments.length; i++) {
|
||||
Element.Class.remove(element, arguments[i]);
|
||||
element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
|
||||
}
|
||||
},
|
||||
|
||||
// returns true if all given classes exist in said element
|
||||
has: function(element) {
|
||||
element = $(element);
|
||||
if(!element || !element.className) return false;
|
||||
var regEx;
|
||||
for(var i = 1; i < arguments.length; i++) {
|
||||
if((typeof arguments[i] == 'object') &&
|
||||
(arguments[i].constructor == Array)) {
|
||||
for(var j = 0; j < arguments[i].length; j++) {
|
||||
regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
|
||||
if(!regEx.test(element.className)) return false;
|
||||
}
|
||||
} else {
|
||||
regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
|
||||
if(!regEx.test(element.className)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// expects arrays of strings and/or strings as optional paramters
|
||||
// Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
|
||||
has_any: function(element) {
|
||||
element = $(element);
|
||||
if(!element || !element.className) return false;
|
||||
var regEx;
|
||||
for(var i = 1; i < arguments.length; i++) {
|
||||
if((typeof arguments[i] == 'object') &&
|
||||
(arguments[i].constructor == Array)) {
|
||||
for(var j = 0; j < arguments[i].length; j++) {
|
||||
regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
|
||||
if(regEx.test(element.className)) return true;
|
||||
}
|
||||
} else {
|
||||
regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
|
||||
if(regEx.test(element.className)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
childrenWith: function(element, className) {
|
||||
var children = $(element).getElementsByTagName('*');
|
||||
var elements = new Array();
|
||||
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if (Element.Class.has(children[i], className)) {
|
||||
elements.push(children[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
String.prototype.parseQuery = function() {
|
||||
var str = this;
|
||||
if(str.substring(0,1) == '?') {
|
||||
str = this.substring(1);
|
||||
}
|
||||
var result = {};
|
||||
var pairs = str.split('&');
|
||||
for(var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].split('=');
|
||||
result[pair[0]] = pair[1];
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -10,8 +10,6 @@ import time
|
|||
import lasso
|
||||
import __builtin__
|
||||
|
||||
from sqlobject import *
|
||||
|
||||
import gettext, locale
|
||||
|
||||
gettext.install('wcs')
|
||||
|
@ -36,14 +34,6 @@ import formdef
|
|||
import roles
|
||||
import users
|
||||
|
||||
sqlobject_classes = [anonylink.AnonymityLink, categories.Category,
|
||||
roles.RoleEmail, roles.Role, users.NameIdentifier, users.User,
|
||||
formdef.FormField, formdef.FormDef,
|
||||
consultationdef.ConsultationField, consultationdef.Consultation,
|
||||
formdata.FormDataEvolution, formdata.FormData,
|
||||
sessions.SqlSession,
|
||||
]
|
||||
|
||||
from root import RootDirectory
|
||||
|
||||
class WcsPublisher(Publisher):
|
||||
|
@ -67,26 +57,6 @@ class WcsPublisher(Publisher):
|
|||
self.config.display_exceptions = debug_cfg.get('display_exceptions')
|
||||
self.config.form_tokens = True
|
||||
|
||||
def set_connection(self):
|
||||
self.conn = connectionForURI('sqlite://%s/wcs.db' % self.app_dir)
|
||||
tries = 0
|
||||
for klass in sqlobject_classes:
|
||||
klass.setConnection(self.conn)
|
||||
try:
|
||||
existing = self.conn.tableExists(klass._table)
|
||||
except: # connection failed ? this sometimes happens with SQLite
|
||||
tries += 1
|
||||
if tries == 5:
|
||||
raise # abandon all hope; what about sending a 503 ?
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
if not existing:
|
||||
klass.createTable()
|
||||
if hasattr(klass, 'initTable'):
|
||||
klass.initTable()
|
||||
if hasattr(klass, 'migrateOldData'):
|
||||
klass.migrateOldData()
|
||||
|
||||
|
||||
class WcsVHostPublisher(WcsPublisher):
|
||||
def try_publish(self, request):
|
||||
|
@ -94,7 +64,6 @@ class WcsVHostPublisher(WcsPublisher):
|
|||
if not os.path.exists(self.app_dir):
|
||||
os.mkdir(self.app_dir)
|
||||
self.set_config()
|
||||
self.set_connection()
|
||||
return WcsPublisher.try_publish(self, request)
|
||||
|
||||
|
||||
|
@ -106,9 +75,8 @@ def create_publisher(publisher_class = WcsPublisher):
|
|||
publisher.data_dir = DATA_DIR
|
||||
if not os.path.exists(publisher.app_dir):
|
||||
os.mkdir(publisher.app_dir)
|
||||
publisher.set_session_manager(sessions.SqlSessionManager())
|
||||
publisher.set_session_manager(sessions.StorageSessionManager())
|
||||
publisher.set_config()
|
||||
publisher.set_connection()
|
||||
return publisher
|
||||
|
||||
def create_vhost_publisher():
|
||||
|
|
|
@ -32,8 +32,10 @@ class CategoryUI:
|
|||
def submit_form(self, form):
|
||||
if self.category:
|
||||
self.category.name = form.get_widget('name').parse()
|
||||
category = self.category
|
||||
else:
|
||||
category = Category(name = form.get_widget('name').parse())
|
||||
category.store()
|
||||
|
||||
|
||||
|
||||
|
@ -72,7 +74,7 @@ class CategoryPage(Directory):
|
|||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
self.category.destroySelf()
|
||||
self.category.remove_self()
|
||||
return redirect('..')
|
||||
|
||||
|
||||
|
@ -93,7 +95,7 @@ class CategoriesDirectory(Directory):
|
|||
</ul>""" % _('New Category')
|
||||
|
||||
'<div class="biglist">'
|
||||
for category in Category.select(orderBy = Category.q.name):
|
||||
for category in Category.select(order_by = 'name'):
|
||||
"""<div class="biglist-item">"""
|
||||
"<h3>%s</h3>" % category.name
|
||||
"""<p><span class="cmds"> [ """
|
||||
|
|
|
@ -10,22 +10,15 @@ from wcs.consultationdef import Consultation, ConsultationField
|
|||
from wcs.formdata import FormData
|
||||
from wcs.users import User
|
||||
from wcs.categories import Category
|
||||
from wcs.roles import Role
|
||||
from wcs.roles import Role, logged_users_role
|
||||
|
||||
def ellipsize(s, length = 30):
|
||||
if not s or len(s) < length:
|
||||
return s
|
||||
return s[:length-5] + ' (...)'
|
||||
|
||||
def get_user_roles(include_system = False):
|
||||
r = []
|
||||
for x in Role.select():
|
||||
if x.system:
|
||||
if include_system:
|
||||
r.append( (x.id, _(x.name)) )
|
||||
else:
|
||||
r.append( (x.id, x.name) )
|
||||
return r
|
||||
def get_user_roles():
|
||||
return [(x.id, x.name) for x in Role.select()]
|
||||
|
||||
def get_categories():
|
||||
return [(x.id, x.name) for x in Category.select()]
|
||||
|
@ -85,20 +78,21 @@ class ConsultationUI:
|
|||
for x in self.consultation.fields]
|
||||
else:
|
||||
fields_list = []
|
||||
form.add(WidgetList, 'questions', title = _('Questions'), element_type = QuestionWidget,
|
||||
form.add(WidgetList, 'fields', title = _('Questions'), element_type = QuestionWidget,
|
||||
value = fields_list, add_element_label = _('Add Field'),
|
||||
element_kwargs = {'render_br': False, 'consultation': self.consultation})
|
||||
form.add(SingleSelectWidget, 'receiver', title = _('Recipient'), required = True,
|
||||
value = self.consultation and self.consultation.receiverID,
|
||||
form.add(SingleSelectWidget, 'receiver_id', title = _('Recipient'), required = True,
|
||||
value = self.consultation and self.consultation.receiver_id,
|
||||
options = get_user_roles())
|
||||
form.add(WidgetList, 'roles', title = _('Roles'), element_type = SingleSelectWidget,
|
||||
hint = _('Only show this consultation to the given roles.'),
|
||||
value = self.consultation and [x.id for x in self.consultation.roles],
|
||||
value = self.consultation and self.consultation.roles,
|
||||
add_element_label = _('Add Role'),
|
||||
element_kwargs = {'render_br': False,
|
||||
'options': [('', '---')] + get_user_roles(include_system = True)})
|
||||
form.add(SingleSelectWidget, 'category', title = _('Category'),
|
||||
value = self.consultation and self.consultation.categoryID,
|
||||
'options': [('', '---'),
|
||||
(logged_users_role().id, logged_users_role().name)] + get_user_roles()})
|
||||
form.add(SingleSelectWidget, 'category_id', title = _('Category'),
|
||||
value = self.consultation and self.consultation.category_id,
|
||||
options = [('', '---')] + get_categories())
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
|
@ -108,37 +102,40 @@ class ConsultationUI:
|
|||
if self.consultation:
|
||||
consultation = self.consultation
|
||||
else:
|
||||
consultation = Consultation(name = form.get_widget('name').parse())
|
||||
consultation = Consultation()
|
||||
consultation.name = form.get_widget('name').parse()
|
||||
|
||||
for f in ('name', ):
|
||||
for f in ('name', 'receiver_id', 'category_id'):
|
||||
setattr(consultation, f, form.get_widget(f).parse())
|
||||
|
||||
receiver = form.get_widget('receiver').parse()
|
||||
if receiver:
|
||||
consultation.receiver = Role.get(receiver)
|
||||
category = form.get_widget('category').parse()
|
||||
if category:
|
||||
consultation.category = Role.get(category)
|
||||
consultation.roles = [x for x in form.get_widget('roles').parse() if x]
|
||||
|
||||
def lax_int(s):
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
new_fields = []
|
||||
for field in form.get_widget('fields').parse():
|
||||
if not field['label']:
|
||||
if field['id']:
|
||||
FormField.delete(field['id'])
|
||||
consultation.fields = [x for x in consultation.fields if x.id != field['id']]
|
||||
continue
|
||||
if field['id']:
|
||||
form_field = ConsultationField.get(field['id'])
|
||||
form_field.set(label = field['label'], type = field['type'])
|
||||
form_field = [x for x in consultation.fields if x.id == field['id']][0]
|
||||
else:
|
||||
form_field = ConsultationField(label = field['label'], type = field['type'],
|
||||
consultation = self.consultation)
|
||||
form_field = FormField()
|
||||
if consultation.fields:
|
||||
form_field.id = str(max([lax_int(x.id) for x in consultation.fields]) + 1)
|
||||
else:
|
||||
form_field.id = '1'
|
||||
form_field.label = field['label']
|
||||
form_field.type = field['type']
|
||||
new_fields.append(form_field)
|
||||
|
||||
new_roles = [x for x in form.get_widget('roles').parse() if x]
|
||||
for role in consultation.roles or []:
|
||||
if not role.id in new_roles:
|
||||
consultation.removeRole(role)
|
||||
for role_id in new_roles:
|
||||
if not role_id in [x.id for x in consultation.roles]:
|
||||
consultation.addRole(Role.get(role_id))
|
||||
consultation.fields = new_fields
|
||||
consultation.store()
|
||||
|
||||
return consultation
|
||||
|
||||
|
@ -147,7 +144,8 @@ class ConsultationUI:
|
|||
class FieldDefPage(Directory):
|
||||
_q_exports = ['', 'delete', 'down', 'up']
|
||||
|
||||
def __init__(self, field_id):
|
||||
def __init__(self, consultation, field_id):
|
||||
self.consultation = consultation
|
||||
self.field = ConsultationField.get(field_id)
|
||||
|
||||
def form(self):
|
||||
|
@ -164,18 +162,18 @@ class FieldDefPage(Directory):
|
|||
|
||||
if current_type == 'item':
|
||||
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
|
||||
value = self.field.extra.get('items', []), required = True,
|
||||
value = self.field.items, required = True,
|
||||
element_kwargs = {'render_br': False})
|
||||
form.add(CheckboxWidget, 'show-as-radio', title = _('Show as radio buttons'),
|
||||
value = self.field.extra.get('show-as-radio', False))
|
||||
value = self.field.show_as_radio)
|
||||
elif current_type == 'text':
|
||||
form.add(StringWidget, 'cols', title = _('Line length'),
|
||||
value = self.field.extra.get('cols', ''))
|
||||
value = self.field.cols)
|
||||
form.add(StringWidget, 'rows', title = _('Number of rows'),
|
||||
value = self.field.extra.get('rows', ''))
|
||||
value = self.field.rows)
|
||||
elif current_type == 'string':
|
||||
form.add(StringWidget, 'size', title = _('Line length'),
|
||||
value = self.field.extra.get('size', ''))
|
||||
value = self.field.size)
|
||||
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
|
@ -187,7 +185,7 @@ class FieldDefPage(Directory):
|
|||
redo = False
|
||||
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('../../%s/fields' % self.field.consultationID)
|
||||
return redirect('..')
|
||||
|
||||
if form.get_widget('items') and form.get_widget('items').get_widget('add_element').parse():
|
||||
form.clear_errors()
|
||||
|
@ -216,43 +214,113 @@ class FieldDefPage(Directory):
|
|||
w = form.get_widget(f)
|
||||
if not w:
|
||||
continue
|
||||
extra[f] = w.parse()
|
||||
self.field.extra = extra
|
||||
setattr(self.field, f.replace('-', '_'), form.get_widget(f).parse())
|
||||
self.consultation.store()
|
||||
|
||||
def delete [html] (self):
|
||||
value = self.consultation.questions[self.question_no]
|
||||
form = Form(enctype="multipart/form-data")
|
||||
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
|
||||
"You are about to remove a question.")))
|
||||
"You are about to remove a field.")))
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('../questions')
|
||||
return redirect('../../fields/')
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
html_top('consultations', title = _('Delete Field'))
|
||||
'<h2>%s %s</h2>' % (_('Deleting Field:'), value['question'])
|
||||
'<h2>%s %s</h2>' % (_('Deleting Field:'), self.field.label)
|
||||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
pos = self.field.position
|
||||
consultation = self.field.consultation
|
||||
self.field.destroySelf()
|
||||
for field in consultation.fields:
|
||||
if field.position > pos:
|
||||
field.position = field.position - 1
|
||||
return redirect('../../%s/fields' % self.field.consultationID)
|
||||
del self.consultation.fields[ self.consultation.fields.index(self.field) ]
|
||||
self.consultation.store()
|
||||
return redirect('../../fields/')
|
||||
|
||||
def down(self):
|
||||
next = [x for x in self.field.consultation.fields if x.position == self.field.position+1][0]
|
||||
self.field.position = self.field.position+1
|
||||
next.position = next.position-1
|
||||
return redirect('../../%s/fields' % self.field.consultationID)
|
||||
field_pos = self.consultation.fields.index(self.field)
|
||||
fields = self.consultation.fields
|
||||
fields[field_pos], fields[field_pos+1] = fields[field_pos+1], fields[field_pos]
|
||||
self.consultation.store()
|
||||
return redirect('..')
|
||||
|
||||
def up(self):
|
||||
prev = [x for x in self.field.consultation.fields if x.position == self.field.position-1][0]
|
||||
self.field.position = self.field.position-1
|
||||
prev.position = prev.position+1
|
||||
return redirect('../../%s/fields' % self.field.consultationID)
|
||||
field_pos = self.consultation.fields.index(self.field)
|
||||
fields = self.consultation.fields
|
||||
fields[field_pos], fields[field_pos-1] = fields[field_pos-1], fields[field_pos]
|
||||
self.consultation.store()
|
||||
return redirect('..')
|
||||
|
||||
|
||||
class FieldsDirectory(Directory):
|
||||
_q_exports = ['', 'update_order']
|
||||
|
||||
def __init__(self, consultation):
|
||||
self.consultation = consultation
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return FieldDefPage(self.consultation, component)
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('forms', '%s - %s' % (_('Form'), self.consultation.name),
|
||||
scripts = ['/js/prototype.js', '/js/scriptaculous/dragdrop.js',
|
||||
'/js/scriptaculous/util.js', '/js/scriptaculous/controls.js',
|
||||
'/js/scriptaculous/effects.js'] )
|
||||
'<h2>%s</h2>' % self.consultation.name
|
||||
'<p>'
|
||||
_('Use drag and drop to reorder fields.')
|
||||
'</p>'
|
||||
'<ul id="fields-list">'
|
||||
for i, field in enumerate(self.consultation.fields):
|
||||
'<li id="fieldlistitem_%s">' % field.id
|
||||
if field.type in ('subtitle', 'title', 'comment'):
|
||||
label = field.label
|
||||
if len(label) > 60:
|
||||
label = label[:55] + ' (...)'
|
||||
if field.type in ('subtitle', 'title'):
|
||||
'<strong>%s</strong>' % label
|
||||
else:
|
||||
'%s' % label
|
||||
'<span class="commands">'
|
||||
else:
|
||||
type = [x[1] for x in field_types if x[0] == field.type][0]
|
||||
if field.required:
|
||||
required = ''
|
||||
else:
|
||||
required = ' - ' + _('optional')
|
||||
'<span class="label">%s</span>' % field.label
|
||||
'<span class="details">'
|
||||
'<span class="type">%s</span>' % type
|
||||
'<span class="optional">%s</span>' % required
|
||||
'</span>'
|
||||
'<span class="commands">'
|
||||
'<span class="edit"><a href="%s/"><img src="/images/stock_edit_16.png" alt="%s" /></a></span>' % (field.id, _('Edit'))
|
||||
|
||||
'<span class="delete"><a href="%s/delete"><img src="/images/stock_remove_16.png" alt="%s" /></a></span>' % (field.id, _('Delete'))
|
||||
'</span>'
|
||||
'</li>'
|
||||
'</ul>'
|
||||
'''<script type="text/javascript">
|
||||
// <![CDATA[
|
||||
Sortable.create('fields-list', {dropOnEmpty:true,constraint:false,
|
||||
containement:["fields-list"],
|
||||
onUpdate:function(){
|
||||
new Ajax.Request('update_order', {
|
||||
method:'post',
|
||||
parameters:Sortable.serialize('fields-list'),
|
||||
onComplete:function(request){new Effect.Highlight('fields-list',{});},
|
||||
evalScripts:true,
|
||||
asynchronous:true})}});
|
||||
// ]]>
|
||||
</script>'''
|
||||
|
||||
html_foot()
|
||||
|
||||
def update_order(self):
|
||||
request = get_request()
|
||||
new_order = request.form['fields-list[]']
|
||||
new_fields = [ [x for x in self.consultation.fields if x.id == y][0] for y in new_order]
|
||||
self.consultation.fields = new_fields
|
||||
self.consultation.store()
|
||||
return 'ok'
|
||||
|
||||
|
||||
|
||||
|
@ -265,44 +333,7 @@ class ConsultationPage(Directory):
|
|||
except SQLObjectNotFound:
|
||||
raise TraversalError()
|
||||
self.consultationui = ConsultationUI(self.consultation)
|
||||
|
||||
def fields [html] (self):
|
||||
html_top('consultations', '%s - %s' % (_('Consultation'), self.consultation.name))
|
||||
'<h2>%s</h2>' % self.consultation.name
|
||||
'<table>'
|
||||
for i, field in enumerate(self.consultation.fields):
|
||||
'<tr>'
|
||||
if field.type in ('subtitle', 'title', 'comment'):
|
||||
label = field.label
|
||||
if len(label) > 40:
|
||||
label = label[:35] + ' (...)'
|
||||
if field.type in ('subtitle', 'title'):
|
||||
'<td colspan="4"><strong>%s</strong></td>' % label
|
||||
else:
|
||||
'<td colspan="4">%s</td>' % label
|
||||
else:
|
||||
type = [x[1] for x in field_types if x[0] == field.type][0]
|
||||
if field.required:
|
||||
required = ''
|
||||
else:
|
||||
required = _('optional')
|
||||
'<td>%s</td>' % field.label
|
||||
'<td>%s</td>' % type
|
||||
'<td>%s</td>' % required
|
||||
'<td><a href="../fields/%d/">%s</a></td>' % (field.id, _('Edit'))
|
||||
|
||||
'<td><a href="../fields/%d/delete">%s</a></td>' % (field.id, _('Delete'))
|
||||
'<td>'
|
||||
if i != 0:
|
||||
'<a class="arrow" href="../fields/%d/up">%s</a>' % (field.id, '↑')
|
||||
'</td><td>'
|
||||
if i+1 != len(self.consultation.fields):
|
||||
'<a class="arrow" href="../fields/%d/up">%s</a>' % (field.id, '↓')
|
||||
'</td>'
|
||||
'</tr>'
|
||||
'</table>'
|
||||
|
||||
html_foot()
|
||||
self.fields = FieldsDirectory(self.consultation)
|
||||
|
||||
def edit [html] (self, duplicate = False):
|
||||
form = self.consultationui.edit_form_ui()
|
||||
|
@ -347,21 +378,14 @@ class ConsultationPage(Directory):
|
|||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
for formdata in FormData.select(FormData.q.consultationID == self.consultation.id):
|
||||
formdata.destroySelf()
|
||||
self.consultation.destroySelf()
|
||||
# XXX: remove form data
|
||||
#for formdata in FormData.select(FormData.q.consultationID == self.consultation.id):
|
||||
# formdata.destroySelf()
|
||||
self.consultation.remove_self()
|
||||
return redirect('..')
|
||||
|
||||
|
||||
class FieldsDirectory(Directory):
|
||||
def _q_lookup(self, component):
|
||||
return FieldDefPage(component)
|
||||
|
||||
class ConsultationsDirectory(Directory):
|
||||
|
||||
_q_exports = ['', 'new', 'fields']
|
||||
|
||||
fields = FieldsDirectory()
|
||||
_q_exports = ['', 'new']
|
||||
|
||||
def _q_index [html] (self):
|
||||
misc.reload_cfg()
|
||||
|
@ -389,7 +413,7 @@ class ConsultationsDirectory(Directory):
|
|||
html_foot()
|
||||
|
||||
def new [html] (self):
|
||||
if Role.select(Role.q.system == False).count() == 0:
|
||||
if Role.count():
|
||||
return error_page('consultations', _("You first have to define roles."))
|
||||
consultation = Consultation()
|
||||
consultationui = ConsultationUI(consultation)
|
||||
|
|
|
@ -10,22 +10,15 @@ from wcs.formdata import FormData
|
|||
from wcs.formdef import FormDef, FormField
|
||||
from wcs.users import User
|
||||
from wcs.categories import Category
|
||||
from wcs.roles import Role
|
||||
from wcs.roles import Role, logged_users_role
|
||||
|
||||
def ellipsize(s, length = 30):
|
||||
if not s or len(s) < length:
|
||||
return s
|
||||
return s[:length-5] + ' (...)'
|
||||
|
||||
def get_user_roles(include_system = False):
|
||||
r = []
|
||||
for x in Role.select():
|
||||
if x.system:
|
||||
if include_system:
|
||||
r.append( (x.id, _(x.name)) )
|
||||
else:
|
||||
r.append( (x.id, x.name) )
|
||||
return r
|
||||
def get_user_roles():
|
||||
return [(x.id, x.name) for x in Role.select()]
|
||||
|
||||
def get_categories():
|
||||
return [(x.id, x.name) for x in Category.select()]
|
||||
|
@ -84,17 +77,18 @@ class FormDefUI:
|
|||
form.add(WidgetList, 'fields', title = _('Fields'), element_type = FieldWidget,
|
||||
value = fields_list, add_element_label = _('Add Field'),
|
||||
element_kwargs = {'formdef': self.formdef, 'render_br': False})
|
||||
form.add(SingleSelectWidget, 'receiver', title = _('Recipient'), required = True,
|
||||
value = self.formdef and self.formdef.receiverID,
|
||||
form.add(SingleSelectWidget, 'receiver_id', title = _('Recipient'), required = True,
|
||||
value = self.formdef and self.formdef.receiver_id,
|
||||
options = get_user_roles())
|
||||
form.add(WidgetList, 'roles', title = _('Roles'), element_type = SingleSelectWidget,
|
||||
hint = _('Only show this form to the given roles.'),
|
||||
value = self.formdef and [x.id for x in self.formdef.roles],
|
||||
value = self.formdef and self.formdef.roles,
|
||||
add_element_label = _('Add Role'),
|
||||
element_kwargs = {'render_br': False,
|
||||
'options': [('', '---')] + get_user_roles(include_system = True)})
|
||||
form.add(SingleSelectWidget, 'category', title = _('Category'),
|
||||
value = self.formdef and self.formdef.categoryID,
|
||||
'options': [('', '---'),
|
||||
(logged_users_role().id, logged_users_role().name)] + get_user_roles()})
|
||||
form.add(SingleSelectWidget, 'category_id', title = _('Category'),
|
||||
value = self.formdef and self.formdef.category_id,
|
||||
options = [(None, '---')] + get_categories())
|
||||
form.add(CheckboxWidget, 'confirmation', title = _('Include confirmation page'),
|
||||
value = (self.formdef and self.formdef.confirmation) or True)
|
||||
|
@ -113,45 +107,50 @@ class FormDefUI:
|
|||
if self.formdef:
|
||||
formdef = self.formdef
|
||||
else:
|
||||
formdef = FormDef(name = form.get_widget('name').parse())
|
||||
formdef = FormDef()
|
||||
formdef.name = form.get_widget('name').parse()
|
||||
|
||||
for f in ('name', 'confirmation', 'discussion', 'public', 'detailed_emails'):
|
||||
for f in ('name', 'confirmation', 'discussion', 'public', 'detailed_emails',
|
||||
'receiver_id', 'category_id'):
|
||||
setattr(formdef, f, form.get_widget(f).parse())
|
||||
|
||||
receiver = form.get_widget('receiver').parse()
|
||||
if receiver:
|
||||
formdef.receiver = Role.get(receiver)
|
||||
category = form.get_widget('category').parse()
|
||||
if category:
|
||||
formdef.category = Role.get(category)
|
||||
formdef.roles = [x for x in form.get_widget('roles').parse() if x]
|
||||
|
||||
def lax_int(s):
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
new_fields = []
|
||||
for field in form.get_widget('fields').parse():
|
||||
if not field['label']:
|
||||
if field['id']:
|
||||
FormField.delete(field['id'])
|
||||
formdef.fields = [x for x in formdef.fields if x.id != field['id']]
|
||||
continue
|
||||
if field['id']:
|
||||
form_field = FormField.get(field['id'])
|
||||
form_field.set(label = field['label'], type = field['type'])
|
||||
form_field = [x for x in formdef.fields if x.id == field['id']][0]
|
||||
else:
|
||||
form_field = FormField(label = field['label'], type = field['type'],
|
||||
formdef = self.formdef)
|
||||
form_field = FormField()
|
||||
if formdef.fields:
|
||||
form_field.id = str(max([lax_int(x.id) for x in formdef.fields]) + 1)
|
||||
else:
|
||||
form_field.id = '1'
|
||||
form_field.label = field['label']
|
||||
form_field.type = field['type']
|
||||
new_fields.append(form_field)
|
||||
|
||||
new_roles = [x for x in form.get_widget('roles').parse() if x]
|
||||
for role in formdef.roles or []:
|
||||
if not role.id in new_roles:
|
||||
formdef.removeRole(role)
|
||||
for role_id in new_roles:
|
||||
if not role_id in [x.id for x in formdef.roles]:
|
||||
formdef.addRole(Role.get(role_id))
|
||||
formdef.fields = new_fields
|
||||
formdef.store()
|
||||
|
||||
return formdef
|
||||
|
||||
class FieldDefPage(Directory):
|
||||
_q_exports = ['', 'delete', 'down', 'up']
|
||||
|
||||
def __init__(self, field_id):
|
||||
self.field = FormField.get(field_id)
|
||||
def __init__(self, formdef, field_id):
|
||||
self.formdef = formdef
|
||||
self.field = [x for x in self.formdef.fields if x.id == field_id][0]
|
||||
|
||||
def form(self):
|
||||
form = Form(enctype="multipart/form-data")
|
||||
|
@ -164,26 +163,26 @@ class FieldDefPage(Directory):
|
|||
value = self.field.required)
|
||||
|
||||
form.add(CheckboxWidget, 'in_listing', title = _('Display in listings'),
|
||||
value = self.field.extra.get('in_listing', True))
|
||||
value = self.field.in_listing)
|
||||
|
||||
current_type = form.get_widget('type').parse()
|
||||
|
||||
if current_type == 'item':
|
||||
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
|
||||
value = self.field.extra.get('items', []), required = True,
|
||||
value = self.field.items, required = True,
|
||||
element_kwargs = {'render_br': False})
|
||||
form.add(CheckboxWidget, 'show-as-radio', title = _('Show as radio buttons'),
|
||||
value = self.field.extra.get('show-as-radio', False))
|
||||
value = self.field.show_as_radio)
|
||||
elif current_type == 'text':
|
||||
form.add(StringWidget, 'cols', title = _('Line length'),
|
||||
value = self.field.extra.get('cols', ''))
|
||||
value = self.field.cols)
|
||||
form.add(StringWidget, 'rows', title = _('Number of rows'),
|
||||
value = self.field.extra.get('rows', ''))
|
||||
value = self.field.rows)
|
||||
form.add(CheckboxWidget, 'pre', title = _('Preformatted Text'),
|
||||
value = self.field.extra.get('pre', False))
|
||||
value = self.field.pre)
|
||||
elif current_type == 'string':
|
||||
form.add(StringWidget, 'size', title = _('Line length'),
|
||||
value = self.field.extra.get('size', ''))
|
||||
value = self.field.size)
|
||||
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
|
@ -195,7 +194,7 @@ class FieldDefPage(Directory):
|
|||
redo = False
|
||||
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('../../%s/fields' % self.field.formdefID)
|
||||
return redirect('..')
|
||||
|
||||
if form.get_widget('items') and form.get_widget('items').get_widget('add_element').parse():
|
||||
form.clear_errors()
|
||||
|
@ -205,26 +204,26 @@ class FieldDefPage(Directory):
|
|||
redo = True
|
||||
|
||||
if redo or not form.is_submitted() or form.has_errors():
|
||||
html_top('forms', '%s - %s' % (_('Form'), self.field.formdef.name))
|
||||
'<h2>%s - %s</h2>' % (self.field.formdef.name, self.field.label)
|
||||
html_top('forms', '%s - %s' % (_('Form'), self.formdef.name))
|
||||
'<h2>%s - %s</h2>' % (self.formdef.name, self.field.label)
|
||||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
self.submit(form)
|
||||
if form.get_widget('items') is None and self.field.type == 'item':
|
||||
return redirect('.')
|
||||
return redirect('../../%s/fields' % self.field.formdefID)
|
||||
return redirect('..')
|
||||
|
||||
def submit(self, form):
|
||||
for f in ('label', 'type', 'required'):
|
||||
setattr(self.field, f, form.get_widget(f).parse())
|
||||
extra = {}
|
||||
for f in ('items', 'show-as-radio', 'rows', 'cols', 'size', 'pre', 'in_listing'):
|
||||
w = form.get_widget(f)
|
||||
if not w:
|
||||
continue
|
||||
extra[f] = w.parse()
|
||||
self.field.extra = extra
|
||||
setattr(self.field, f.replace('-', '_'), form.get_widget(f).parse())
|
||||
self.formdef.store()
|
||||
|
||||
|
||||
def delete [html] (self):
|
||||
form = Form(enctype="multipart/form-data")
|
||||
|
@ -233,32 +232,102 @@ class FieldDefPage(Directory):
|
|||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('../fields')
|
||||
return redirect('../../fields/')
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
html_top('forms', title = _('Delete Field'))
|
||||
'<h2>%s %s</h2>' % (_('Deleting Field:'), self.field.label)
|
||||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
pos = self.field.position
|
||||
formdef = self.field.formdef
|
||||
self.field.destroySelf()
|
||||
for field in formdef.fields:
|
||||
if field.position > pos:
|
||||
field.position = field.position - 1
|
||||
return redirect('../../%s/fields' % self.field.formdefID)
|
||||
del self.formdef.fields[ self.formdef.fields.index(self.field) ]
|
||||
self.formdef.store()
|
||||
return redirect('../../fields/')
|
||||
|
||||
def down(self):
|
||||
next = [x for x in self.field.formdef.fields if x.position == self.field.position+1][0]
|
||||
self.field.position = self.field.position+1
|
||||
next.position = next.position-1
|
||||
return redirect('../../%s/fields' % self.field.formdefID)
|
||||
field_pos = self.formdef.fields.index(self.field)
|
||||
fields = self.formdef.fields
|
||||
fields[field_pos], fields[field_pos+1] = fields[field_pos+1], fields[field_pos]
|
||||
self.formdef.store()
|
||||
return redirect('..')
|
||||
|
||||
def up(self):
|
||||
prev = [x for x in self.field.formdef.fields if x.position == self.field.position-1][0]
|
||||
self.field.position = self.field.position-1
|
||||
prev.position = prev.position+1
|
||||
return redirect('../../%s/fields' % self.field.formdefID)
|
||||
field_pos = self.formdef.fields.index(self.field)
|
||||
fields = self.formdef.fields
|
||||
fields[field_pos], fields[field_pos-1] = fields[field_pos-1], fields[field_pos]
|
||||
self.formdef.store()
|
||||
return redirect('..')
|
||||
|
||||
class FieldsDirectory(Directory):
|
||||
_q_exports = ['', 'update_order']
|
||||
|
||||
def __init__(self, formdef):
|
||||
self.formdef = formdef
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return FieldDefPage(self.formdef, component)
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('forms', '%s - %s' % (_('Form'), self.formdef.name),
|
||||
scripts = ['/js/prototype.js', '/js/scriptaculous/dragdrop.js',
|
||||
'/js/scriptaculous/util.js', '/js/scriptaculous/controls.js',
|
||||
'/js/scriptaculous/effects.js'] )
|
||||
'<h2>%s</h2>' % self.formdef.name
|
||||
'<p>'
|
||||
_('Use drag and drop to reorder fields.')
|
||||
'</p>'
|
||||
'<ul id="fields-list">'
|
||||
for i, field in enumerate(self.formdef.fields):
|
||||
'<li id="fieldlistitem_%s">' % field.id
|
||||
if field.type in ('subtitle', 'title', 'comment'):
|
||||
label = field.label
|
||||
if len(label) > 60:
|
||||
label = label[:55] + ' (...)'
|
||||
if field.type in ('subtitle', 'title'):
|
||||
'<strong>%s</strong>' % label
|
||||
else:
|
||||
'%s' % label
|
||||
'<span class="commands">'
|
||||
else:
|
||||
type = [x[1] for x in field_types if x[0] == field.type][0]
|
||||
if field.required:
|
||||
required = ''
|
||||
else:
|
||||
required = ' - ' + _('optional')
|
||||
'<span class="label">%s</span>' % field.label
|
||||
'<span class="details">'
|
||||
'<span class="type">%s</span>' % type
|
||||
'<span class="optional">%s</span>' % required
|
||||
'</span>'
|
||||
'<span class="commands">'
|
||||
'<span class="edit"><a href="%s/"><img src="/images/stock_edit_16.png" alt="%s" /></a></span>' % (field.id, _('Edit'))
|
||||
|
||||
'<span class="delete"><a href="%s/delete"><img src="/images/stock_remove_16.png" alt="%s" /></a></span>' % (field.id, _('Delete'))
|
||||
'</span>'
|
||||
'</li>'
|
||||
'</ul>'
|
||||
'''<script type="text/javascript">
|
||||
// <![CDATA[
|
||||
Sortable.create('fields-list', {dropOnEmpty:true,constraint:false,
|
||||
containement:["fields-list"],
|
||||
onUpdate:function(){
|
||||
new Ajax.Request('update_order', {
|
||||
method:'post',
|
||||
parameters:Sortable.serialize('fields-list'),
|
||||
onComplete:function(request){new Effect.Highlight('fields-list',{});},
|
||||
evalScripts:true,
|
||||
asynchronous:true})}});
|
||||
// ]]>
|
||||
</script>'''
|
||||
|
||||
html_foot()
|
||||
|
||||
def update_order(self):
|
||||
request = get_request()
|
||||
new_order = request.form['fields-list[]']
|
||||
new_fields = [ [x for x in self.formdef.fields if x.id == y][0] for y in new_order]
|
||||
self.formdef.fields = new_fields
|
||||
self.formdef.store()
|
||||
return 'ok'
|
||||
|
||||
|
||||
|
||||
|
@ -268,47 +337,10 @@ class FormDefPage(Directory):
|
|||
def __init__(self, component):
|
||||
try:
|
||||
self.formdef = FormDef.get(component)
|
||||
except SQLObjectNotFound:
|
||||
except KeyError:
|
||||
raise TraversalError()
|
||||
self.formdefui = FormDefUI(self.formdef)
|
||||
|
||||
def fields [html] (self):
|
||||
html_top('forms', '%s - %s' % (_('Form'), self.formdef.name))
|
||||
'<h2>%s</h2>' % self.formdef.name
|
||||
'<table>'
|
||||
for i, field in enumerate(self.formdef.fields):
|
||||
'<tr>'
|
||||
if field.type in ('subtitle', 'title', 'comment'):
|
||||
label = field.label
|
||||
if len(label) > 40:
|
||||
label = label[:35] + ' (...)'
|
||||
if field.type in ('subtitle', 'title'):
|
||||
'<td colspan="4"><strong>%s</strong></td>' % label
|
||||
else:
|
||||
'<td colspan="4">%s</td>' % label
|
||||
else:
|
||||
type = [x[1] for x in field_types if x[0] == field.type][0]
|
||||
if field.required:
|
||||
required = ''
|
||||
else:
|
||||
required = _('optional')
|
||||
'<td>%s</td>' % field.label
|
||||
'<td>%s</td>' % type
|
||||
'<td>%s</td>' % required
|
||||
'<td><a href="../fields/%d/">%s</a></td>' % (field.id, _('Edit'))
|
||||
|
||||
'<td><a href="../fields/%d/delete">%s</a></td>' % (field.id, _('Delete'))
|
||||
'<td>'
|
||||
if i != 0:
|
||||
'<a class="arrow" href="../fields/%d/up">%s</a>' % (field.id, '↑')
|
||||
'</td><td>'
|
||||
if i+1 != len(self.formdef.fields):
|
||||
'<a class="arrow" href="../fields/%d/down">%s</a>' % (field.id, '↓')
|
||||
'</td>'
|
||||
'</tr>'
|
||||
'</table>'
|
||||
|
||||
html_foot()
|
||||
self.fields = FieldsDirectory(self.formdef)
|
||||
|
||||
def edit [html] (self, duplicate = False):
|
||||
form = self.formdefui.edit_form_ui()
|
||||
|
@ -353,28 +385,22 @@ class FormDefPage(Directory):
|
|||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
for formdata in FormData.select(FormData.q.formdefID == self.formdef.id):
|
||||
formdata.destroySelf()
|
||||
self.formdef.destroySelf()
|
||||
# XXX: remove form data
|
||||
#for formdata in FormData.select(FormData.q.formdefID == self.formdef.id):
|
||||
# formdata.destroySelf()
|
||||
self.formdef.remove_self()
|
||||
return redirect('..')
|
||||
|
||||
|
||||
class FieldsDirectory(Directory):
|
||||
def _q_lookup(self, component):
|
||||
return FieldDefPage(component)
|
||||
|
||||
|
||||
class FormsDirectory(Directory):
|
||||
|
||||
_q_exports = ['', 'new', 'fields']
|
||||
|
||||
fields = FieldsDirectory()
|
||||
_q_exports = ['', 'new']
|
||||
|
||||
def _q_index [html] (self):
|
||||
misc.reload_cfg()
|
||||
html_top('forms', title = _('Forms'))
|
||||
|
||||
if Role.select(Role.q.system == False).count():
|
||||
if Role.count():
|
||||
"""<ul id="nav-forms-admin">
|
||||
<li><a href="new">%s</a></li>
|
||||
</ul>""" % _('New Form')
|
||||
|
@ -385,7 +411,9 @@ class FormsDirectory(Directory):
|
|||
for formdef in FormDef.select():
|
||||
'<div class="biglist-item">'
|
||||
"<h3>%s</h3>" % formdef.name
|
||||
'<p><span class="data">%s</span>' % ellipsize(formdef.receiver.name, 50)
|
||||
'<p>'
|
||||
if formdef.receiver:
|
||||
'<span class="data">%s</span>' % ellipsize(formdef.receiver.name, 50)
|
||||
'<span class="cmds"> [ '
|
||||
'<a href="%s/edit">%s</a> - ' % (formdef.id, _('Edit'))
|
||||
'<a href="%s/fields">%s</a> - ' % (formdef.id, _('Fields'))
|
||||
|
@ -396,7 +424,7 @@ class FormsDirectory(Directory):
|
|||
html_foot()
|
||||
|
||||
def new [html] (self):
|
||||
if Role.select(Role.q.system == False).count() == 0:
|
||||
if Role.count() == 0:
|
||||
return error_page('forms', _("You first have to define roles."))
|
||||
formdefui = FormDefUI(None)
|
||||
form = formdefui.edit_form_ui()
|
||||
|
|
|
@ -35,15 +35,19 @@ def user_info [html] ():
|
|||
session = quixote.get_session()
|
||||
if not session or not session.user:
|
||||
return ''
|
||||
user = User.get(session.user)
|
||||
try:
|
||||
user = User.get(session.user)
|
||||
username = user.name
|
||||
except KeyError:
|
||||
username = _('Unknown')
|
||||
logout_url = quixote.get_request().environ['SCRIPT_NAME'] + '/logout'
|
||||
"""<ul class="user-info">
|
||||
<li class="ui-name">%s</li>
|
||||
<li class="ui-logout"><a href="%s">%s</a></li>
|
||||
</ul>""" % (user.name, logout_url, _('logout'))
|
||||
</ul>""" % (username, logout_url, _('logout'))
|
||||
|
||||
|
||||
def html_top [html] (section, title = None):
|
||||
def html_top [html] (section, title = None, scripts = None):
|
||||
subtitle = ''
|
||||
for s in items:
|
||||
if s[0] == section:
|
||||
|
@ -52,12 +56,18 @@ def html_top [html] (section, title = None):
|
|||
title = ''
|
||||
else:
|
||||
title = ' - ' + title
|
||||
if not scripts:
|
||||
scripts = ''
|
||||
else:
|
||||
scripts = '\n'.join(['<script src="%s" type="text/javascript"></script>' % x for x in scripts])
|
||||
|
||||
return """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>%s%s</title>
|
||||
<link rel="stylesheet" type="text/css" href="/css/wcs-admin.css"/>
|
||||
%s
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">%s</div>
|
||||
|
@ -65,7 +75,7 @@ def html_top [html] (section, title = None):
|
|||
%s
|
||||
<h1>%s</h1>
|
||||
|
||||
""" % (_('WCS Administration'), title, header_menu(section), user_info(), subtitle)
|
||||
""" % (_('WCS Administration'), title, scripts, header_menu(section), user_info(), subtitle)
|
||||
|
||||
def html_foot [html] ():
|
||||
return """</div><div id="footer"><p id="lasso">Powered by Lasso</p></div></body></html>"""
|
||||
|
|
|
@ -6,7 +6,7 @@ from menu import html_top, html_foot
|
|||
from wcs import errors
|
||||
from wcs import misc
|
||||
from wcs import storage
|
||||
from wcs.roles import Role, RoleEmail
|
||||
from wcs.roles import Role
|
||||
from wcs.form import *
|
||||
|
||||
class RoleUI:
|
||||
|
@ -27,14 +27,13 @@ class RoleUI:
|
|||
|
||||
def form_edit(self):
|
||||
form = Form(enctype="multipart/form-data")
|
||||
form.add(StringWidget, "id", title = _('Role Id'), required = False, size=30,
|
||||
value = self.role.id, readonly = 'readonly')
|
||||
form.add_hidden('id', value = self.role.id)
|
||||
form.add(StringWidget, "name", title = _('Role Name'), required = True, size=30,
|
||||
value = self.role.name)
|
||||
form.add(TextWidget, "details", title = _('Role Details'), required = False,
|
||||
cols = 40, rows = 5, value = self.role.details)
|
||||
form.add(WidgetList, 'emails', title = _('Role Emails'), element_type = StringWidget,
|
||||
value = [x.email for x in self.role.emails],
|
||||
value = self.role.emails,
|
||||
add_element_label = _('Add Email'),
|
||||
element_kwargs = {'render_br': False, 'size': 30})
|
||||
form.add_submit("submit", _("Submit"))
|
||||
|
@ -48,11 +47,8 @@ class RoleUI:
|
|||
role = Role(name = form.get_widget('name').parse())
|
||||
for f in ('name', 'details'):
|
||||
setattr(role, f, form.get_widget(f).parse())
|
||||
for email in role.emails:
|
||||
email.destroySelf()
|
||||
for email in form.get_widget('emails').parse() or []:
|
||||
RoleEmail(email = email, role = role)
|
||||
|
||||
role.emails = [x for x in form.get_widget('emails').parse() or [] if x]
|
||||
role.store()
|
||||
|
||||
|
||||
class RolePage(Directory):
|
||||
|
@ -60,14 +56,12 @@ class RolePage(Directory):
|
|||
|
||||
def __init__(self, component):
|
||||
self.role = Role.get(component)
|
||||
if self.role.system:
|
||||
raise errors.TraversalError()
|
||||
self.role_ui = RoleUI(self.role)
|
||||
|
||||
def edit [html] (self):
|
||||
form = self.role_ui.form_edit()
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
return redirect('..')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
html_top('roles', title = _('Edit Role'))
|
||||
|
@ -92,7 +86,7 @@ class RolePage(Directory):
|
|||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
self.role.destroySelf()
|
||||
self.role.remove_self()
|
||||
return redirect('..')
|
||||
|
||||
|
||||
|
@ -115,7 +109,7 @@ class RolesDirectory(Directory):
|
|||
</ul>""" % _('New Role')
|
||||
|
||||
'<div class="biglist">'
|
||||
for role in Role.select(Role.q.system == False):
|
||||
for role in Role.select(order_by = 'name'):
|
||||
'<div class="biglist-item">'
|
||||
"<h3>%s</h3>" % role.name
|
||||
'<p><span class="cmds"> [ '
|
||||
|
|
|
@ -55,8 +55,7 @@ class RootDirectory(AccessControlled, Directory):
|
|||
get_session().user = None
|
||||
|
||||
if not user:
|
||||
query = users.User.select()
|
||||
if query.count() == 0:
|
||||
if users.User.count() == 0:
|
||||
session.set_user('ultra-user')
|
||||
return # bootstrapping
|
||||
if session.user:
|
||||
|
|
|
@ -24,8 +24,8 @@ class UserUI:
|
|||
form.add(StringWidget, "name", title = _('User Name'), required = True, size=30)
|
||||
form.add(StringWidget, "email", title = _('Email'), required = False, size=30)
|
||||
form.add(CheckboxWidget, 'is_admin', title = _('Administrator Account'))
|
||||
roles = Role.select()
|
||||
if roles.count():
|
||||
roles = list(Role.select())
|
||||
if len(roles):
|
||||
form.add(WidgetList, 'roles', title = _('Roles'), element_type = SingleSelectWidget,
|
||||
add_element_label = _('Add Role'),
|
||||
element_kwargs = {
|
||||
|
@ -46,10 +46,10 @@ class UserUI:
|
|||
value = self.user.email)
|
||||
form.add(CheckboxWidget, 'is_admin', title = _('Administrator Account'),
|
||||
value = self.user.is_admin)
|
||||
roles = Role.select()
|
||||
if roles.count():
|
||||
roles = list(Role.select())
|
||||
if len(roles):
|
||||
form.add(WidgetList, 'roles', title = _('Roles'), element_type = SingleSelectWidget,
|
||||
value = [x.id for x in self.user.roles],
|
||||
value = self.user.roles,
|
||||
add_element_label = _('Add Role'),
|
||||
element_kwargs = {
|
||||
'render_br': False,
|
||||
|
@ -64,15 +64,11 @@ class UserUI:
|
|||
user = self.user
|
||||
else:
|
||||
user = User()
|
||||
for f in ('name', 'email', 'is_admin'):
|
||||
setattr(user, f, form.get_widget(f).parse())
|
||||
for role in user.roles:
|
||||
user.removeRole(role)
|
||||
if form.get_widget('roles'):
|
||||
for role_id in form.get_widget('roles').parse() or []:
|
||||
if not role_id:
|
||||
continue
|
||||
user.addRole(Role.get(role_id))
|
||||
for f in ('name', 'email', 'is_admin', 'roles'):
|
||||
widget = form.get_widget(f)
|
||||
if widget:
|
||||
setattr(user, f, widget.parse())
|
||||
user.store()
|
||||
|
||||
|
||||
class UserPage(Directory):
|
||||
|
@ -118,7 +114,7 @@ class UserPage(Directory):
|
|||
form.render()
|
||||
html_foot()
|
||||
else:
|
||||
self.user.destroySelf()
|
||||
self.user.remove_self()
|
||||
return redirect('..')
|
||||
|
||||
def token [html] (self):
|
||||
|
@ -142,6 +138,7 @@ class UserPage(Directory):
|
|||
html_top('users', title = _('Identification Token'))
|
||||
token = '-'.join(['%04d' % random.randint(1, 9999) for x in range(4)])
|
||||
self.user.identification_token = str(token)
|
||||
self.user.store()
|
||||
|
||||
"<p>"
|
||||
_('Identification Token for %s:') % self.user.name
|
||||
|
@ -178,7 +175,7 @@ class UsersDirectory(Directory):
|
|||
<li><a href="new">%s</a></li>
|
||||
</ul>""" % _('New User')
|
||||
|
||||
users = User.select(orderBy = User.q.name)
|
||||
users = User.select(order_by = 'name')
|
||||
|
||||
'<div class="biglist">'
|
||||
for user in users:
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
from sqlobject import *
|
||||
from storage import StorableObject
|
||||
|
||||
class AnonymityLink(SQLObject):
|
||||
_cacheValue = False
|
||||
formdata = ForeignKey('FormData')
|
||||
key = StringCol(length = 100, default = None)
|
||||
name_identifier = StringCol(default = None)
|
||||
class AnonymityLink(StorableObject):
|
||||
_names = 'anonylinks'
|
||||
|
||||
key = None
|
||||
name_identifier = None
|
||||
formdata_type = None
|
||||
formdata_def_id = None
|
||||
formdata_id = None
|
||||
|
||||
|
|
|
@ -1,53 +1,9 @@
|
|||
from sqlobject import *
|
||||
from storage import StorableObject
|
||||
|
||||
class Category(SQLObject):
|
||||
key = 'id'
|
||||
names = 'categories'
|
||||
_cacheValues = False
|
||||
class Category(StorableObject):
|
||||
_names = 'categories'
|
||||
name = None
|
||||
|
||||
name = StringCol(length = 100)
|
||||
|
||||
def migrateOldData(cls):
|
||||
import storage
|
||||
from formdef import FormDef
|
||||
from consultationdef import Consultation
|
||||
|
||||
init_method = cls.__init__
|
||||
cls.__init__ = lambda x: x
|
||||
categories = storage.get_storage().values('categories')
|
||||
items = [x.__dict__ for x in categories]
|
||||
cls.__init__ = init_method
|
||||
|
||||
new_ids_mapping = []
|
||||
for item in items:
|
||||
category = Category(name = item['name'])
|
||||
new_ids_mapping.append( (item['id'], category.id) )
|
||||
|
||||
# fixing other tables to get the new ids
|
||||
# formdefs
|
||||
formdef_init_method = FormDef.__init__
|
||||
FormDef.__init__ = lambda x: x
|
||||
formdefs = storage.get_storage().values('formdefs')
|
||||
for formdef in formdefs:
|
||||
cat = formdef.__dict__.get('category')
|
||||
if not cat:
|
||||
formdef.__dict__['category'] = None
|
||||
else:
|
||||
formdef.__dict__['category'] = [x[1] for x in new_ids_mapping if x[0] == cat][0]
|
||||
storage.get_storage().store(formdef)
|
||||
FormDef.__init__ = formdef_init_method
|
||||
|
||||
# consultations
|
||||
consultation_init_method = Consultation.__init__
|
||||
Consultation.__init__ = lambda x: x
|
||||
consultations = storage.get_storage().values('consultations')
|
||||
for consultation in consultations:
|
||||
cat = consultation.__dict__['category']
|
||||
if not cat:
|
||||
consultation.__dict__['category'] = None
|
||||
else:
|
||||
consultation.__dict__['category'] = [x[1] for x in new_ids_mapping if x[0] == cat][0]
|
||||
storage.get_storage().store(consultation)
|
||||
Consultation.__init__ = consultation_init_method
|
||||
|
||||
migrateOldData = classmethod(migrateOldData)
|
||||
def __init__(self, name = None):
|
||||
StorableObject.__init__(self)
|
||||
self.name = name
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from sqlobject import *
|
||||
from extracols import DictPickleCol
|
||||
from storage import StorableObject
|
||||
|
||||
import emails
|
||||
|
||||
|
@ -12,6 +11,8 @@ from misc import simplify
|
|||
|
||||
from formdata import FormData
|
||||
from anonylink import AnonymityLink
|
||||
from roles import Role
|
||||
from categories import Category
|
||||
|
||||
widget_classes = {
|
||||
'string': StringWidget,
|
||||
|
@ -28,32 +29,130 @@ widget_extra_attributes = {
|
|||
}
|
||||
|
||||
|
||||
class ConsultationField(SQLObject):
|
||||
label = StringCol(length = 300)
|
||||
type = StringCol(length = 20)
|
||||
required = BoolCol(default = True)
|
||||
extra = DictPickleCol(default = None)
|
||||
consultation = ForeignKey('Consultation')
|
||||
position = IntCol(default = None)
|
||||
class ConsultationField:
|
||||
id = None
|
||||
label = None
|
||||
type = None
|
||||
required = True
|
||||
|
||||
# for type == 'items'
|
||||
items = None
|
||||
show_as_radio = False
|
||||
|
||||
# for type == 'text'
|
||||
cols = None
|
||||
rows = None
|
||||
pre = False
|
||||
|
||||
# for type == 'string'
|
||||
size = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k.replace('-', '_'), v)
|
||||
|
||||
|
||||
|
||||
class Consultation(SQLObject):
|
||||
key = 'id'
|
||||
names = 'consultations'
|
||||
class Consultation(StorableObject):
|
||||
_names = 'consultations'
|
||||
|
||||
name = StringCol(length = 200)
|
||||
url_name = StringCol(default = None, length = 200, alternateID = True)
|
||||
fields = MultipleJoin('ConsultationField', joinMethodName = 'fields',
|
||||
joinColumn = 'consultation_id', orderBy = ConsultationField.q.position)
|
||||
receiver = ForeignKey('Role', default = None)
|
||||
roles = RelatedJoin('Role')
|
||||
category = ForeignKey('Category', default = None)
|
||||
name = None
|
||||
url_name = None
|
||||
fields = None
|
||||
receiver_id = None
|
||||
category_id = None
|
||||
roles = None
|
||||
|
||||
def data_class(self):
|
||||
cls = FormData
|
||||
cls._names = 'consultation-%s' % self.url_name
|
||||
return cls
|
||||
|
||||
def migrate(self):
|
||||
changed = False
|
||||
|
||||
if self.__dict__.has_key('receiver'):
|
||||
self.receiver_id = self.__dict__['receiver']
|
||||
del self.__dict__['receiver']
|
||||
changed = True
|
||||
|
||||
if self.__dict__.has_key('category'):
|
||||
self.category_id = self.__dict__['category']
|
||||
del self.__dict__['category']
|
||||
changed = True
|
||||
|
||||
if not self.url_name:
|
||||
try:
|
||||
int(self.id)
|
||||
except ValueError:
|
||||
self.url_name = self.id
|
||||
else:
|
||||
self.url_name = simplify(self.name)
|
||||
changed = True
|
||||
|
||||
if hasattr(self, 'questions'):
|
||||
for f in self.questions:
|
||||
if f.has_key('question'):
|
||||
f['label'] = f['question']
|
||||
del f['question']
|
||||
if f.has_key('answer'):
|
||||
f['type'] = f['answer']
|
||||
del f['answer']
|
||||
self.fields = [ConsultationField(**x) for x in self.questions]
|
||||
del self.questions
|
||||
for i, f in enumerate(self.fields):
|
||||
f.id = str(i)
|
||||
for formdata in self.data_class().select():
|
||||
for f in self.fields:
|
||||
if not formdata.data.has_key(f.label):
|
||||
continue
|
||||
formdata.data[f.id] = formdata.data[f.label]
|
||||
del formdata.data[f.label]
|
||||
formdata.store()
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self.store()
|
||||
|
||||
def data_class(self):
|
||||
cls = FormData
|
||||
cls._names = 'consultation-%s' % self.url_name
|
||||
return cls
|
||||
|
||||
|
||||
def get_receiver(self):
|
||||
if self.receiver_id:
|
||||
return Role.get(self.receiver_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_receiver(self, role):
|
||||
if role:
|
||||
self.receiver_id = role.id
|
||||
elif self.receiver_id:
|
||||
self.receiver_id = None
|
||||
receiver = property(get_receiver, set_receiver)
|
||||
|
||||
def get_category(self):
|
||||
if self.category_id:
|
||||
return Category.get(self.category_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_category(self, category):
|
||||
if category:
|
||||
self.category_id = category.id
|
||||
elif self.category_id:
|
||||
self.category_id = None
|
||||
category = property(get_category, set_category)
|
||||
|
||||
def get_by_urlname(cls, url_name):
|
||||
objects = [x for x in cls.select() if x.url_name == url_name]
|
||||
if objects:
|
||||
return objects[0]
|
||||
raise KeyError()
|
||||
get_by_urlname = classmethod(get_by_urlname)
|
||||
|
||||
def _set_name(self, value):
|
||||
self._SO_set_name(value)
|
||||
if not self.url_name or FormData.select(FormData.q.consultationID == self.id).count() == 0:
|
||||
self.url_name = simplify(value)
|
||||
|
||||
def create_form(self, page_no, answers = {}):
|
||||
form = Form(enctype = "multipart/form-data", use_tokens = False)
|
||||
|
@ -80,18 +179,17 @@ class Consultation(SQLObject):
|
|||
|
||||
kwargs = {'required': field.required}
|
||||
if field.type == 'item':
|
||||
kwargs['options'] = field.extra.get('items', ['---'])
|
||||
if field.extra.get('show-as-radio'):
|
||||
kwargs['options'] = field.items or [(None, '---')]
|
||||
if field.show_as_radio:
|
||||
widget_class = RadiobuttonsWidget
|
||||
if len(kwargs['options']) > 2:
|
||||
kwargs['delim'] = htmltext('<br />')
|
||||
kwargs['value'] = answers.get(field.id)
|
||||
if widget_extra_attributes.has_key(field.type):
|
||||
for k in widget_extra_attributes[field.type]:
|
||||
v = field.extra.get(k, None)
|
||||
if v is not None:
|
||||
kwargs[k] = v
|
||||
form.add(widget_class, "f%d" % field.id, title = field.label, **kwargs)
|
||||
if hasattr(field, k):
|
||||
kwargs[k] = getattr(field, k)
|
||||
form.add(widget_class, "f%s" % field.id, title = field.label, **kwargs)
|
||||
|
||||
if page_no != 0:
|
||||
form.add_submit("previous", _("Previous"))
|
||||
|
@ -108,61 +206,17 @@ class Consultation(SQLObject):
|
|||
return d
|
||||
|
||||
def get_filled(self, session):
|
||||
completed = FormData.select(AND(
|
||||
FormData.q.consultationID == self.id,
|
||||
FormData.q.userID == session.user))
|
||||
if completed.count():
|
||||
completed = self.data_class().select(lambda x: x.user_id == session.user)
|
||||
if len(completed):
|
||||
return completed[0]
|
||||
|
||||
completed = AnonymityLink.select(OR(
|
||||
AnonymityLink.q.name_identifier == session.name_identifier,
|
||||
AND(AnonymityLink.q.key != None,
|
||||
AnonymityLink.q.key == session.get_anonymous_key(False))))
|
||||
completed = AnonymityLink.select(
|
||||
lambda x: x.formdata_type == 'consultation' and (
|
||||
x.name_identifier == session.name_identifier or
|
||||
x.key == session.anonymous_key))
|
||||
for c in completed:
|
||||
if c.formdata.consultationID == self.id:
|
||||
if c.formdata.consultation_id == self.id:
|
||||
return c.formdata
|
||||
|
||||
raise SQLObjectNotFound
|
||||
raise KeyError
|
||||
|
||||
def migrateOldData(cls):
|
||||
import storage
|
||||
from roles import Role, get_logged_users_role
|
||||
from categories import Category
|
||||
|
||||
init_method = cls.__init__
|
||||
cls.__init__ = lambda x: x
|
||||
consultations = storage.get_storage().values('consultations')
|
||||
items = [x.__dict__ for x in consultations]
|
||||
cls.__init__ = init_method
|
||||
|
||||
items.sort(lambda x,y: cmp(x['id'], y['id']))
|
||||
logged_users = get_logged_users_role()
|
||||
for item in items:
|
||||
consultation = Consultation(name = item['name'])
|
||||
consultation.url_name = item['id']
|
||||
consultation.receiver = Role.get(item['receiver'])
|
||||
if item.get('category'):
|
||||
consultation.category = Category.get(item['category'])
|
||||
for role in item.get('roles', []):
|
||||
if role == 'logged-users':
|
||||
consultation.addRole(logged_users)
|
||||
continue
|
||||
consultation.addRole(Role.get(role))
|
||||
for i, field in enumerate(item['questions']):
|
||||
fieldObject = ConsultationField(label = field['question'], type = field['answer'],
|
||||
consultation = consultation)
|
||||
fieldObject.required = field.get('required', False)
|
||||
fieldObject.position = i
|
||||
d = field.copy()
|
||||
del d['question']
|
||||
del d['answer']
|
||||
del d['required']
|
||||
if d:
|
||||
fieldObject.extra = d
|
||||
|
||||
# convert submitted forms
|
||||
name = 'consultation-%s' % item['id']
|
||||
newname = 'consultationtmp-%s' % consultation.id
|
||||
storage.get_storage().rename_table(name, newname)
|
||||
|
||||
migrateOldData = classmethod(migrateOldData)
|
||||
|
|
|
@ -3,8 +3,6 @@ import os
|
|||
from quixote import get_request, get_response, get_session
|
||||
from quixote.directory import Directory
|
||||
|
||||
from sqlobject import *
|
||||
|
||||
from wcs import errors
|
||||
from wcs import misc
|
||||
from wcs import template
|
||||
|
@ -35,15 +33,15 @@ class BackOfficeDirectory(Directory):
|
|||
|
||||
l = []
|
||||
if user:
|
||||
for consultation in Consultation.select(orderBy = Consultation.q.name):
|
||||
for consultation in Consultation.select(order_by = 'name'):
|
||||
if user.is_admin or consultation.receiver in user.roles:
|
||||
l.append(consultation)
|
||||
l.sort(lambda x,y: cmp(x.name, y.name))
|
||||
|
||||
cats = Category.select(orderBy = Category.q.name)
|
||||
cats = Category.select(order_by = 'name')
|
||||
one = False
|
||||
for c in cats:
|
||||
l2 = [x for x in l if x.categoryID == c.id]
|
||||
l2 = [x for x in l if x.category_id == c.id]
|
||||
if l2:
|
||||
"<h2>%s</h2>" % c.name
|
||||
"<ul>"
|
||||
|
@ -52,7 +50,7 @@ class BackOfficeDirectory(Directory):
|
|||
"</ul>"
|
||||
one = True
|
||||
|
||||
l2 = [x for x in l if not x.category]
|
||||
l2 = [x for x in l if not x.category_id]
|
||||
if one and l2:
|
||||
"<h2>%s</h2>" % _('Misc')
|
||||
if l2:
|
||||
|
@ -71,6 +69,7 @@ class BackOfficeDirectory(Directory):
|
|||
def _q_lookup(self, component):
|
||||
return ConsultationPage(component)
|
||||
|
||||
|
||||
class ConsultationPage(Directory):
|
||||
_q_exports = ["", "listing", "csv", "stats"]
|
||||
|
||||
|
@ -116,7 +115,7 @@ class ConsultationPage(Directory):
|
|||
'<th>%s</th>' % f.label
|
||||
'</tr></thead>'
|
||||
'<tbody>'
|
||||
items = FormData.select(FormData.q.consultationID == self.consultation.id)
|
||||
items = self.consultation.data_class().select(order_by = '-receipt_time')
|
||||
for filled in items:
|
||||
if filled.status != 'done':
|
||||
continue
|
||||
|
@ -142,7 +141,7 @@ class ConsultationPage(Directory):
|
|||
fields = [x for x in self.consultation.fields \
|
||||
if x.type not in ('title', 'subtitle', 'comment', 'newpage')]
|
||||
|
||||
items = FormData.select(FormData.q.consultationID == self.consultation.id)
|
||||
items = self.consultation.data_class().select(order_by = '-receipt_time')
|
||||
for filled in items:
|
||||
';'.join([str(fixcsv(filled.data.get(x.id, ''))) for x in fields]) + '\r\n'
|
||||
response = get_response()
|
||||
|
@ -151,8 +150,8 @@ class ConsultationPage(Directory):
|
|||
def stats [html] (self):
|
||||
html_top('%s - %s' % (_('Consultation'), self.consultation.name))
|
||||
|
||||
filled_values = FormData.select(AND(FormData.q.consultationID == self.consultation.id,
|
||||
FormData.q.status == str('done')))
|
||||
filled_values = self.consultation.data_class().select(
|
||||
lambda x: x.status == str('done'))
|
||||
|
||||
no_records = filled_values.count()
|
||||
'<p>%s %d</p>' % (_('Number of filled consultations:'), no_records)
|
||||
|
|
|
@ -3,8 +3,6 @@ import time
|
|||
from quixote import get_request, get_response, get_session, redirect
|
||||
from quixote.directory import Directory, AccessControlled
|
||||
|
||||
from sqlobject import *
|
||||
|
||||
from wcs import errors
|
||||
from wcs import formdata
|
||||
from wcs import storage
|
||||
|
@ -16,7 +14,7 @@ from wcs.categories import Category
|
|||
from wcs.anonylink import AnonymityLink
|
||||
from wcs.consultationdef import Consultation
|
||||
from wcs.users import User
|
||||
from wcs.roles import get_logged_users_role
|
||||
from wcs.roles import logged_users_role
|
||||
from wcs.formdata import FormData
|
||||
|
||||
from backoffice import BackOfficeDirectory
|
||||
|
@ -76,8 +74,8 @@ class ConsultationPage(Directory):
|
|||
|
||||
def __init__(self, component):
|
||||
try:
|
||||
self.consultation = Consultation.byUrl_name(component)
|
||||
except errors.SQLObjectNotFound:
|
||||
self.consultation = Consultation.get_by_urlname(component)
|
||||
except KeyError:
|
||||
raise errors.TraversalError()
|
||||
|
||||
self.page_number = len([
|
||||
|
@ -90,7 +88,7 @@ class ConsultationPage(Directory):
|
|||
if self.consultation.roles:
|
||||
if not session.user:
|
||||
raise errors.AccessUnauthorizedError()
|
||||
if not user.is_admin and not get_logged_users_role() not in formdef.roles:
|
||||
if not user.is_admin and not logged_users_role().id not in formdef.roles:
|
||||
for q in user.roles:
|
||||
if q in self.consultation.roles or q == self.consultation.receiver:
|
||||
break
|
||||
|
@ -99,18 +97,23 @@ class ConsultationPage(Directory):
|
|||
|
||||
try:
|
||||
self.filled = self.consultation.get_filled(session)
|
||||
except SQLObjectNotFound:
|
||||
self.filled = FormData()
|
||||
self.filled.consultation = self.consultation
|
||||
except KeyError:
|
||||
self.filled = self.consultation.data_class()()
|
||||
if user:
|
||||
self.filled.user = user
|
||||
self.filled.user_id = user.id
|
||||
else:
|
||||
a = AnonymityLink(formdata = self.filled)
|
||||
a = AnonymityLink()
|
||||
a.formdata_type = 'consultation'
|
||||
a.formdata_def_id = self.consultation.id
|
||||
a.formdata_id = self.filled.id
|
||||
if session.name_identifier:
|
||||
a.name_identifier = session.name_identifier
|
||||
else:
|
||||
a.key = session.get_anonymous_key()
|
||||
a.store()
|
||||
self.filled.status = 'watched'
|
||||
self.filled.data = {}
|
||||
self.filled.store()
|
||||
|
||||
|
||||
def _q_index [html] (self):
|
||||
|
@ -166,6 +169,7 @@ class ConsultationPage(Directory):
|
|||
if page_no == self.page_number:
|
||||
self.filled.receipt_time = time.localtime()
|
||||
self.filled.status = 'done'
|
||||
self.filled.store()
|
||||
|
||||
if page_no == self.page_number:
|
||||
return self.page_end()
|
||||
|
@ -199,13 +203,14 @@ class RootDirectory(Directory):
|
|||
|
||||
backoffice_link = False
|
||||
l = []
|
||||
for consultation in Consultation.select(orderBy = Consultation.q.name):
|
||||
if user and consultation.receiver in user.roles:
|
||||
consultations = Consultation.select(order_by = 'name')
|
||||
for consultation in consultations:
|
||||
if user and consultation.receiver in (user.roles or []):
|
||||
backoffice_link = True
|
||||
if consultation.roles:
|
||||
if not session.user:
|
||||
continue
|
||||
if get_logged_users_role() not in consultation.roles:
|
||||
if logged_users_role().id not in consultation.roles:
|
||||
for q in (user and user.roles) or []:
|
||||
if q in consultation.roles:
|
||||
break
|
||||
|
@ -215,15 +220,15 @@ class RootDirectory(Directory):
|
|||
if user and user.is_admin:
|
||||
backoffice_link = True
|
||||
|
||||
cats = Category.select(orderBy = Category.q.name)
|
||||
cats = Category.select(order_by = 'name')
|
||||
one = False
|
||||
for c in cats:
|
||||
l2 = [x for x in l if x.categoryID == c.id]
|
||||
l2 = [x for x in l if x.category_id == c.id]
|
||||
if l2:
|
||||
self.consultation_list(l2, title = c.name, session = session)
|
||||
one = True
|
||||
|
||||
l2 = [x for x in l if not x.categoryID]
|
||||
l2 = [x for x in l if not x.category_id]
|
||||
if l2:
|
||||
if one:
|
||||
title = _('Misc')
|
||||
|
@ -252,7 +257,7 @@ class RootDirectory(Directory):
|
|||
for consultation in list:
|
||||
try:
|
||||
filled = consultation.get_filled(session)
|
||||
except SQLObjectNotFound:
|
||||
except KeyError:
|
||||
filled = None
|
||||
|
||||
if filled and filled.status == 'done':
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import quixote
|
||||
from quixote.errors import *
|
||||
from sqlobject import SQLObjectNotFound
|
||||
|
||||
import template
|
||||
|
||||
|
|
115
wcs/formdata.py
115
wcs/formdata.py
|
@ -1,101 +1,32 @@
|
|||
from sqlobject import *
|
||||
from extracols import DictPickleCol
|
||||
from storage import StorableObject
|
||||
import time
|
||||
|
||||
from anonylink import AnonymityLink
|
||||
|
||||
class FormDataEvolution(SQLObject):
|
||||
_cacheValues = False
|
||||
|
||||
who = ForeignKey('User', default = None)
|
||||
status = StringCol(length = 40)
|
||||
time = DateTimeCol()
|
||||
comment = StringCol(default = None)
|
||||
formdata = ForeignKey('FormData')
|
||||
|
||||
#def __init__(self, status, comment, who):
|
||||
# self.time = time.localtime() #XXX: do not forget
|
||||
|
||||
class FormData(SQLObject):
|
||||
key = 'id'
|
||||
names = 'forms'
|
||||
_cacheValues = False
|
||||
|
||||
user = ForeignKey('User', default = None)
|
||||
receipt_time = DateTimeCol(default = None)
|
||||
status = StringCol(default = None, length = 40)
|
||||
evolution = MultipleJoin('FormDataEvolution', joinMethodName = 'evolution',
|
||||
joinColumn = 'formdata_id')
|
||||
data = DictPickleCol(default = None)
|
||||
|
||||
formdef = ForeignKey('FormDef', default = None)
|
||||
consultation = ForeignKey('Consultation', default = None)
|
||||
class Evolution:
|
||||
who = None
|
||||
status = None
|
||||
time = None
|
||||
comment = None
|
||||
|
||||
|
||||
def migrateOldData(cls):
|
||||
import storage
|
||||
class FormData(StorableObject):
|
||||
_names = 'XXX'
|
||||
|
||||
user_id = None
|
||||
receipt_time = None
|
||||
status = None
|
||||
evolution = None
|
||||
data = None
|
||||
|
||||
def get_formdef(self):
|
||||
from formdef import FormDef
|
||||
from consultationdef import Consultation
|
||||
from users import User
|
||||
type, id = self._names.split('-', 1)
|
||||
return FormDef.get_by_urlname(id)
|
||||
formdef = property(get_formdef)
|
||||
|
||||
def migrate(objectdef):
|
||||
if isinstance(objectdef, FormDef):
|
||||
formdataname = 'formtmp-%s' % objectdef.id
|
||||
elif isinstance(objectdef, Consultation):
|
||||
formdataname = 'consultationtmp-%s' % objectdef.id
|
||||
formdata_init_method = FormData.__init__
|
||||
FormData.__init__ = lambda x: x
|
||||
formdatadicts = [x.__dict__ for x in storage.get_storage().values(formdataname)]
|
||||
FormData.__init__ = formdata_init_method
|
||||
fields = objectdef.fields
|
||||
for formdict in formdatadicts:
|
||||
formdata = FormData()
|
||||
if isinstance(objectdef, FormDef):
|
||||
formdata.formdef = objectdef
|
||||
elif isinstance(objectdef, Consultation):
|
||||
formdata.consultation = objectdef
|
||||
if formdict.get('user_id'):
|
||||
user_id = formdict.get('user_id')
|
||||
if user_id == 'ultra-user':
|
||||
pass # ignore, this shouldn't have happened
|
||||
elif str(user_id).startswith('anonymous-'):
|
||||
a = AnonymityLink(formdata = formdata)
|
||||
a.name_identifier = user_id[10:]
|
||||
elif str(user_id).startswith('_'): # really old wcs versions
|
||||
a = AnonymityLink(formdata = formdata)
|
||||
a.name_identifier = user_id
|
||||
else:
|
||||
try:
|
||||
formdata.user = User.get(user_id)
|
||||
except ValueError:
|
||||
a = AnonymityLink(formdata = formdata)
|
||||
a.key = user_id
|
||||
def get_consultationdef(self):
|
||||
type, id = self._names.split('-', 1)
|
||||
return ConsultationDef.get_by_urlname(id)
|
||||
consultationdef = property(get_consultationdef)
|
||||
|
||||
formdata.status = formdict.get('status')
|
||||
formdata.receipt_time = formdict.get('receipt_time')
|
||||
newdata = {}
|
||||
for k, v in formdict.get('data', {}).items():
|
||||
# changed keys from 'field name' to 'field id'
|
||||
try:
|
||||
field = [x for x in fields if x.label == k][0]
|
||||
except IndexError:
|
||||
continue
|
||||
newdata[field.id] = v
|
||||
formdata.data = newdata
|
||||
for evo in formdict.get('evolution', []):
|
||||
evo_dict = evo.__dict__
|
||||
evo = FormDataEvolution(formdata = formdata,
|
||||
time = evo_dict.get('time'),
|
||||
status = evo_dict.get('status'))
|
||||
if evo_dict.get('who'):
|
||||
evo.who = User.get(evo_dict.get('who'))
|
||||
evo.comment = evo_dict.get('comment')
|
||||
|
||||
for objectdef in FormDef.select():
|
||||
migrate(objectdef)
|
||||
for objectdef in Consultation.select():
|
||||
migrate(objectdef)
|
||||
migrateOldData = classmethod(migrateOldData)
|
||||
|
||||
class Evolution: # for backward compatibility with old pickled files
|
||||
pass
|
||||
|
|
222
wcs/formdef.py
222
wcs/formdef.py
|
@ -1,5 +1,4 @@
|
|||
from sqlobject import *
|
||||
from extracols import DictPickleCol
|
||||
from storage import StorableObject
|
||||
from quixote import get_request, get_session
|
||||
|
||||
import emails
|
||||
|
@ -8,6 +7,8 @@ from form import *
|
|||
from misc import simplify
|
||||
|
||||
from formdata import FormData
|
||||
from roles import Role
|
||||
from categories import Category
|
||||
|
||||
widget_classes = {
|
||||
'string': StringWidget,
|
||||
|
@ -31,35 +32,125 @@ status_labels = {
|
|||
'finished': N_('Finished')
|
||||
}
|
||||
|
||||
class FormField(SQLObject):
|
||||
label = StringCol(length = 300)
|
||||
type = StringCol(length = 20)
|
||||
required = BoolCol(default = True)
|
||||
extra = DictPickleCol(default = None)
|
||||
formdef = ForeignKey('FormDef')
|
||||
position = IntCol(default = None)
|
||||
class FormField:
|
||||
id = None
|
||||
label = None
|
||||
type = None
|
||||
required = True
|
||||
in_listing = True
|
||||
|
||||
# for type == 'items'
|
||||
items = None
|
||||
show_as_radio = False
|
||||
|
||||
# for type == 'text'
|
||||
cols = None
|
||||
rows = None
|
||||
pre = False
|
||||
|
||||
# for type == 'string'
|
||||
size = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k.replace('-', '_'), v)
|
||||
|
||||
|
||||
class FormDef(SQLObject):
|
||||
key = 'id'
|
||||
names = 'formdefs'
|
||||
class FormDef(StorableObject):
|
||||
_names = 'formdefs'
|
||||
|
||||
name = StringCol(length = 200)
|
||||
url_name = StringCol(default = None, length = 200, alternateID = True)
|
||||
fields = MultipleJoin('FormField', joinMethodName = 'fields', joinColumn = 'formdef_id',
|
||||
orderBy = FormField.q.position)
|
||||
receiver = ForeignKey('Role', default = None)
|
||||
category = ForeignKey('Category', default = None)
|
||||
roles = RelatedJoin('Role')
|
||||
discussion = BoolCol(default = False)
|
||||
confirmation = BoolCol(default = True)
|
||||
public = BoolCol(default = False)
|
||||
detailed_emails = BoolCol(default = False)
|
||||
name = None
|
||||
url_name = None
|
||||
fields = None
|
||||
receiver_id = None
|
||||
category_id = None
|
||||
roles = None
|
||||
discussion = False
|
||||
confirmation = True
|
||||
public = False
|
||||
detailed_emails = False
|
||||
|
||||
def migrate(self):
|
||||
changed = False
|
||||
|
||||
if self.__dict__.has_key('receiver'):
|
||||
self.receiver_id = self.__dict__['receiver']
|
||||
del self.__dict__['receiver']
|
||||
changed = True
|
||||
|
||||
if self.__dict__.has_key('category'):
|
||||
self.category_id = self.__dict__['category']
|
||||
del self.__dict__['category']
|
||||
changed = True
|
||||
|
||||
if not self.url_name:
|
||||
try:
|
||||
int(self.id)
|
||||
except ValueError:
|
||||
self.url_name = self.id
|
||||
else:
|
||||
self.url_name = simplify(self.name)
|
||||
changed = True
|
||||
|
||||
if self.fields and type(self.fields[0]) is dict:
|
||||
for f in self.fields:
|
||||
if f.has_key('name'):
|
||||
f['label'] = f['name']
|
||||
del f['name']
|
||||
self.fields = [FormField(**x) for x in self.fields]
|
||||
for i, f in enumerate(self.fields):
|
||||
f.id = str(i)
|
||||
for formdata in self.data_class().select():
|
||||
for f in self.fields:
|
||||
if not formdata.data.has_key(f.label):
|
||||
continue
|
||||
formdata.data[f.id] = formdata.data[f.label]
|
||||
del formdata.data[f.label]
|
||||
formdata.store()
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self.store()
|
||||
|
||||
def data_class(self):
|
||||
cls = FormData
|
||||
cls._names = 'form-%s' % self.url_name
|
||||
return cls
|
||||
|
||||
|
||||
def get_receiver(self):
|
||||
if self.receiver_id:
|
||||
return Role.get(self.receiver_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_receiver(self, role):
|
||||
if role:
|
||||
self.receiver_id = role.id
|
||||
elif self.receiver_id:
|
||||
self.receiver_id = None
|
||||
receiver = property(get_receiver, set_receiver)
|
||||
|
||||
def get_category(self):
|
||||
if self.category_id:
|
||||
return Category.get(self.category_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_category(self, category):
|
||||
if category:
|
||||
self.category_id = category.id
|
||||
elif self.category_id:
|
||||
self.category_id = None
|
||||
category = property(get_category, set_category)
|
||||
|
||||
def get_by_urlname(cls, url_name):
|
||||
objects = [x for x in cls.select() if x.url_name == url_name]
|
||||
if objects:
|
||||
return objects[0]
|
||||
raise KeyError()
|
||||
get_by_urlname = classmethod(get_by_urlname)
|
||||
|
||||
def _set_name(self, value):
|
||||
self._SO_set_name(value)
|
||||
if not self.url_name or FormData.select(FormData.q.formdefID == self.id).count() == 0:
|
||||
self.url_name = simplify(value)
|
||||
|
||||
def create_form(self): # XXX: merge create_form and create_view_form
|
||||
form = Form(enctype = "multipart/form-data", use_tokens = False) #, use_tokens = not self.confirmation)
|
||||
|
@ -78,15 +169,14 @@ class FormDef(SQLObject):
|
|||
|
||||
kwargs = {'required': field.required}
|
||||
if field.type == 'item':
|
||||
kwargs['options'] = field.extra.get('items', ['---'])
|
||||
if field.extra.get('show-as-radio'):
|
||||
kwargs['options'] = field.items or [(None, '---')]
|
||||
if field.show_as_radio:
|
||||
widget_class = RadiobuttonsWidget
|
||||
if widget_extra_attributes.has_key(field.type):
|
||||
for k in widget_extra_attributes[field.type]:
|
||||
v = field.extra.get(k)
|
||||
if v:
|
||||
kwargs[k] = v
|
||||
form.add(widget_class, 'f%d' % field.id, title = field.label, **kwargs)
|
||||
if hasattr(field, k):
|
||||
kwargs[k] = getattr(field, k)
|
||||
form.add(widget_class, 'f%s' % field.id, title = field.label, **kwargs)
|
||||
return form
|
||||
|
||||
def create_view_form(self, dict = {}):
|
||||
|
@ -116,14 +206,13 @@ class FormDef(SQLObject):
|
|||
|
||||
if widget_extra_attributes.has_key(field.type):
|
||||
for k in widget_extra_attributes[field.type]:
|
||||
v = field.extra.get(k)
|
||||
if v:
|
||||
kwargs[k] = v
|
||||
if hasattr(field, k):
|
||||
kwargs[k] = getattr(field, k)
|
||||
|
||||
if field.type == 'file':
|
||||
widget_class = FakeFileWidget
|
||||
|
||||
form.add(widget_class, 'f%d' % field.id, title = field.label,
|
||||
form.add(widget_class, 'f%s' % field.id, title = field.label,
|
||||
value = value, readonly = 'readonly', **kwargs)
|
||||
return form
|
||||
|
||||
|
@ -171,8 +260,8 @@ link: %(url)s
|
|||
mail_body += '\n\n' + '\n'.join(details)
|
||||
|
||||
emails.email(_("New form (%s)") % self.name, mail_body,
|
||||
self.receiver.emails[0].email,
|
||||
bcc = [x.email for x in self.receiver.emails[1:]])
|
||||
self.receiver.emails[0],
|
||||
bcc = self.receiver.emails[1:])
|
||||
|
||||
def notify_change_user(self, formdata, old_status):
|
||||
if not formdata.user:
|
||||
|
@ -181,7 +270,7 @@ link: %(url)s
|
|||
return
|
||||
|
||||
if self.receiver.emails:
|
||||
from_email = self.receiver.emails[0].email
|
||||
from_email = self.receiver.emails[0]
|
||||
else:
|
||||
from_email = None
|
||||
|
||||
|
@ -241,56 +330,5 @@ A form just changed, you can consult it with this link:
|
|||
mail_body += self.get_detailed_evolution(formdata)
|
||||
|
||||
emails.email(_('Form status change (%s)') % self.name, mail_body,
|
||||
[x.email for x in self.receiver.emails])
|
||||
|
||||
def migrateOldData(cls):
|
||||
import storage
|
||||
from roles import Role, get_logged_users_role
|
||||
from categories import Category
|
||||
|
||||
init_method = cls.__init__
|
||||
cls.__init__ = lambda x: x
|
||||
formdefs = storage.get_storage().values('formdefs')
|
||||
items = [x.__dict__ for x in formdefs]
|
||||
cls.__init__ = init_method
|
||||
|
||||
items.sort(lambda x,y: cmp(x['id'], y['id']))
|
||||
logged_users = get_logged_users_role()
|
||||
for item in items:
|
||||
formdef = FormDef(name = item['name'])
|
||||
formdef.url_name = item['id']
|
||||
try:
|
||||
formdef.receiver = Role.get(item['receiver'])
|
||||
except SQLObjectNotFound:
|
||||
formdef.receiver = None
|
||||
if item.get('category'):
|
||||
formdef.category = Category.get(item['category'])
|
||||
for role in item.get('roles', []):
|
||||
if role == 'logged-users':
|
||||
formdef.addRole(logged_users)
|
||||
continue
|
||||
formdef.addRole(Role.get(role))
|
||||
formdef.discussion = item.get('discussion', False)
|
||||
formdef.confirmation = item.get('confirmation', True)
|
||||
formdef.public = item.get('public', False)
|
||||
formdef.detailed_emails = item.get('detailed_emails', False)
|
||||
for i, field in enumerate(item['fields']):
|
||||
fieldObject = FormField(label = field['name'], type = field['type'],
|
||||
formdef = formdef)
|
||||
fieldObject.required = field.get('required', False)
|
||||
fieldObject.position = i
|
||||
d = field.copy()
|
||||
del d['name']
|
||||
del d['type']
|
||||
if d.has_key('required'):
|
||||
del d['required']
|
||||
if d:
|
||||
fieldObject.extra = d
|
||||
|
||||
# convert submitted forms
|
||||
name = 'form-%s' % item['id']
|
||||
newname = 'formtmp-%s' % formdef.id
|
||||
storage.get_storage().rename_table(name, newname)
|
||||
|
||||
migrateOldData = classmethod(migrateOldData)
|
||||
self.receiver.emails)
|
||||
|
||||
|
|
|
@ -42,14 +42,14 @@ class BackOfficeDirectory(Directory):
|
|||
|
||||
l = []
|
||||
if user:
|
||||
for formdef in FormDef.select(orderBy = FormDef.q.name):
|
||||
for formdef in FormDef.select(order_by = 'name'):
|
||||
if user.is_admin or formdef.receiver in user.roles:
|
||||
l.append(formdef)
|
||||
|
||||
cats = Category.select(orderBy = Category.q.name)
|
||||
cats = Category.select(order_by = 'name')
|
||||
one = False
|
||||
for c in cats:
|
||||
l2 = [x for x in l if x.categoryID == c.id]
|
||||
l2 = [x for x in l if x.category_id == c.id]
|
||||
if l2:
|
||||
"<h2>%s</h2>" % c.name
|
||||
"<ul>"
|
||||
|
@ -58,7 +58,7 @@ class BackOfficeDirectory(Directory):
|
|||
"</ul>"
|
||||
one = True
|
||||
|
||||
l2 = [x for x in l if not x.category]
|
||||
l2 = [x for x in l if not x.category_id]
|
||||
if one and l2:
|
||||
"<h2>%s</h2>" % _('Misc')
|
||||
if l2:
|
||||
|
@ -88,7 +88,7 @@ class FormDefUI:
|
|||
for f in self.formdef.fields:
|
||||
if f.type in ('title', 'subtitle', 'comment'):
|
||||
continue
|
||||
if f.extra.get('in_listing', True) == False:
|
||||
if not f.in_listing:
|
||||
continue
|
||||
fields.append(f)
|
||||
return fields
|
||||
|
@ -110,10 +110,10 @@ class FormDefUI:
|
|||
if f.type in ('title', 'subtitle', 'comment'):
|
||||
continue
|
||||
'<td>'
|
||||
if f.type == 'item':
|
||||
'<select onchange="updateListing()" name="f-%d">' % f.id
|
||||
if f.type == 'item' and f.items:
|
||||
'<select onchange="updateListing()" name="f-%s">' % f.id
|
||||
'<option value="">%s</option' % _('All')
|
||||
for item in f.extra['items']:
|
||||
for item in f.items:
|
||||
'<option>%s</option>' % item
|
||||
'</select>'
|
||||
'</td>'
|
||||
|
@ -136,8 +136,7 @@ class FormDefUI:
|
|||
fields = self.get_listing_fields()
|
||||
|
||||
if items is None:
|
||||
items = FormData.select(FormData.q.formdefID == self.formdef.id,
|
||||
orderBy = str('-receipt_time'))
|
||||
items = self.formdef.data_class().select(order_by = '-receipt_time')
|
||||
if url_action:
|
||||
url_action = '/' + url_action
|
||||
else:
|
||||
|
@ -158,10 +157,10 @@ class FormDefUI:
|
|||
_('No')
|
||||
'</td>'
|
||||
elif f.type == 'text':
|
||||
length = min( 120/len(fields), int(f.extra.get('cols', 30)))
|
||||
length = min( 120/len(fields), int(f.cols or 30))
|
||||
"<td>%s</td>" % ellipsize(filled.data.get(f.id, '') or '', length)
|
||||
elif f.type == 'file':
|
||||
'<td><a href="/%s/download?f=%d">%s</a></td>' % (link, f.id, filled.data[f.id])
|
||||
'<td><a href="/%s/download?f=%s">%s</a></td>' % (link, f.id, filled.data[f.id])
|
||||
else:
|
||||
"<td>%s</td>" % filled.data.get(f.id, '')
|
||||
"<td>%s</td>" % _(status_labels[filled.status])
|
||||
|
@ -171,8 +170,7 @@ class FormDefUI:
|
|||
request = get_request()
|
||||
length = int(request.environ['CONTENT_LENGTH'])
|
||||
data = request.stdin.read(length)
|
||||
items = FormData.select(FormData.q.formdefID == self.formdef.id,
|
||||
orderBy = '-receipt_time')
|
||||
items = self.formdef.data_class().select(order_by = '-receipt_time')
|
||||
for l in data.splitlines():
|
||||
if not l.strip(): # skip blank lines
|
||||
continue
|
||||
|
@ -180,7 +178,7 @@ class FormDefUI:
|
|||
if not v:
|
||||
continue
|
||||
if k[:2] == 'f-':
|
||||
items = [x for x in items if x.data.get(int(k[2:])) == v]
|
||||
items = [x for x in items if x.data.get(k[2:]) == v]
|
||||
elif k == 'status':
|
||||
items = [x for x in items if x.status == v]
|
||||
return self.tbody(url_action = url_action, items = items)
|
||||
|
@ -192,8 +190,8 @@ class FormPage(Directory):
|
|||
|
||||
def __init__(self, component):
|
||||
try:
|
||||
self.formdef = FormDef.byUrl_name(component)
|
||||
except errors.SQLObjectNotFound:
|
||||
self.formdef = FormDef.get_by_urlname(component)
|
||||
except KeyError:
|
||||
raise errors.TraversalError()
|
||||
|
||||
session = get_session()
|
||||
|
@ -234,9 +232,9 @@ class FormPage(Directory):
|
|||
if not s: return s
|
||||
return s.replace('\n', ' ').replace(';', ',')
|
||||
|
||||
fields = [x for x in self.formdef.fields if x.type not in ('title', 'subtitle', 'file')]
|
||||
fields = FormDefUI(self.formdef).get_listing_fields()
|
||||
|
||||
items = FormData.select(FormData.q.formdefID == self.formdef.id)
|
||||
items = self.formdef.data_class().select()
|
||||
for filled in items:
|
||||
';'.join([str(fixcsv(filled.data.get(x.id, ''))) for x in fields]) + '\r\n'
|
||||
response = get_response()
|
||||
|
|
|
@ -4,8 +4,6 @@ import time
|
|||
from quixote import get_request, get_response, get_session, redirect
|
||||
from quixote.directory import Directory, AccessControlled
|
||||
|
||||
from sqlobject import *
|
||||
|
||||
from wcs import errors
|
||||
from wcs import formdata
|
||||
from wcs import storage
|
||||
|
@ -18,7 +16,7 @@ from wcs.categories import Category
|
|||
from wcs.formdef import FormField, FormDef, status_labels
|
||||
from wcs.formdata import FormData
|
||||
from wcs.users import User
|
||||
from wcs.roles import get_logged_users_role
|
||||
from wcs.roles import logged_users_role
|
||||
|
||||
|
||||
from backoffice import BackOfficeDirectory, FormDefUI
|
||||
|
@ -36,22 +34,21 @@ class FormStatusPage(Directory):
|
|||
def __init__(self, formdef, component):
|
||||
self.formdef = formdef
|
||||
try:
|
||||
self.filled = FormData.get(component)
|
||||
except errors.SQLObjectNotFound:
|
||||
raise errors.TraversalError()
|
||||
if self.filled.formdef != self.formdef:
|
||||
self.filled = formdef.data_class().get(component)
|
||||
except KeyError:
|
||||
raise errors.TraversalError()
|
||||
|
||||
def _q_index [html] (self):
|
||||
session = get_session()
|
||||
mine = False
|
||||
if self.filled.userID != session.user:
|
||||
if self.filled.user_id == session.user:
|
||||
mine = True
|
||||
elif str(session.user).startswith(str('anonymous-')):
|
||||
anonylink = AnonymityLink.select(AND(
|
||||
AnonymityLink.q.name_identifier == session.name_identifier,
|
||||
AnonymityLink.q.formdataID == self.filled.id))
|
||||
if anonylink.count() == 0:
|
||||
anonylink = AnonymityLink.select(
|
||||
lambda x: x.name_identifier == session.name_identifier and
|
||||
x.formdata_type == 'form' and
|
||||
x.formdata_def_id == self.formdef.id)
|
||||
if len(anonylink) == 1:
|
||||
mine = True
|
||||
|
||||
if not self.formdef.public and not mine:
|
||||
|
@ -62,7 +59,7 @@ class FormStatusPage(Directory):
|
|||
get_response().breadcrumb.append( (str(self.filled.id) + '/', self.filled.id) )
|
||||
html_top('%s - %s' % (self.formdef.name, self.filled.id))
|
||||
if self.filled.receipt_time is not None:
|
||||
tm = self.filled.receipt_time.strftime(str("%Y-%m-%d %H:%M"))
|
||||
tm = time.strftime(str("%Y-%m-%d %H:%M"), self.filled.receipt_time)
|
||||
else:
|
||||
tm = '???'
|
||||
"<p>"
|
||||
|
@ -109,9 +106,12 @@ class FormStatusPage(Directory):
|
|||
'<h2>%s</h2>' % _('Log')
|
||||
'<dl id="evolutions">'
|
||||
for evo in self.filled.evolution:
|
||||
"<dt>%s" % evo.time.strftime(str("%Y-%m-%d %H:%M"))
|
||||
"<dt>%s" % time.strftime(str("%Y-%m-%d %H:%M"), evo.time)
|
||||
if evo.who:
|
||||
' <span class="user">%s</span>' % evo.who.name
|
||||
try:
|
||||
' <span class="user">%s</span>' % User.get(evo.who).name
|
||||
except KeyError:
|
||||
pass
|
||||
'</dt>'
|
||||
'<dd>'
|
||||
if evo.status:
|
||||
|
@ -135,8 +135,11 @@ class FormStatusPage(Directory):
|
|||
|
||||
def receipt [html] (self, always_include_user = False, show_status = True, form_url = ''):
|
||||
user = None
|
||||
if always_include_user or self.filled.userID != get_session().user:
|
||||
user = self.filled.user
|
||||
if always_include_user or self.filled.user_id != get_session().user:
|
||||
try:
|
||||
user = User.get(self.filled.user_id)
|
||||
except KeyError:
|
||||
user = None
|
||||
|
||||
'<dl id="receipt">'
|
||||
if user:
|
||||
|
@ -154,12 +157,12 @@ class FormStatusPage(Directory):
|
|||
else:
|
||||
_('No')
|
||||
elif f.type == 'file':
|
||||
'<a href="%sdownload?f=%d">%s</a>' % (form_url, f.id, self.filled.data[f.id])
|
||||
'<a href="%sdownload?f=%s">%s</a>' % (form_url, f.id, self.filled.data[f.id])
|
||||
elif f.type == 'text':
|
||||
if f.extra.get('pre', False):
|
||||
if f.pre:
|
||||
'<pre>'
|
||||
self.filled.data[f.id]
|
||||
if f.extra.get('pre', False):
|
||||
if f.pre:
|
||||
'</pre>'
|
||||
else:
|
||||
self.filled.data[f.id]
|
||||
|
@ -198,7 +201,7 @@ class FormStatusPage(Directory):
|
|||
get_response().breadcrumb.append( (str(self.filled.id) + '/status', self.filled.id) )
|
||||
html_top('%s - %s' % (self.formdef.name, self.filled.id))
|
||||
if self.filled.receipt_time is not None:
|
||||
tm = self.filled.receipt_time.strftime(str("%Y-%m-%d %H:%M"))
|
||||
tm = time.strftime(str("%Y-%m-%d %H:%M"), self.filled.receipt_time)
|
||||
else:
|
||||
tm = '???'
|
||||
"<p>"
|
||||
|
@ -239,12 +242,17 @@ class FormStatusPage(Directory):
|
|||
session = get_session()
|
||||
if session.user and not str(session.user).startswith('anonymous-'):
|
||||
who = session.user
|
||||
evo = formdata.FormDataEvolution(status = status, time = time.localtime(),
|
||||
formdata = self.filled)
|
||||
evo = formdata.Evolution()
|
||||
evo.status = status
|
||||
evo.time = time.localtime()
|
||||
evo.who = who
|
||||
evo.comment = comment
|
||||
if not self.filled.evolution:
|
||||
self.filled.evolution = []
|
||||
self.filled.evolution.append(evo)
|
||||
if status:
|
||||
self.filled.status = status
|
||||
self.filled.store()
|
||||
|
||||
if comment_only:
|
||||
self.formdef.notify_change_receiver(self.filled)
|
||||
|
@ -252,19 +260,16 @@ class FormStatusPage(Directory):
|
|||
self.formdef.notify_change_user(self.filled, old_status)
|
||||
|
||||
def download(self):
|
||||
if not self.formdef.public and not self.filled.userID == get_session().user:
|
||||
if not self.formdef.public and not self.filled.user_id == get_session().user:
|
||||
self.check_receiver()
|
||||
try:
|
||||
fn = get_request().form['f']
|
||||
f = FormField.get(fn)
|
||||
f = self.filled.data[fn]
|
||||
except (KeyError, ValueError):
|
||||
raise
|
||||
raise TraversalError()
|
||||
|
||||
if f.type != 'file':
|
||||
raise TraversalError()
|
||||
|
||||
file = self.filled.data[int(fn)]
|
||||
file = self.filled.data[fn]
|
||||
response = get_response()
|
||||
if file.content_type:
|
||||
response.set_content_type(file.content_type)
|
||||
|
@ -284,8 +289,8 @@ class FormPage(Directory):
|
|||
|
||||
def __init__(self, component):
|
||||
try:
|
||||
self.formdef = FormDef.byUrl_name(component)
|
||||
except errors.SQLObjectNotFound:
|
||||
self.formdef = FormDef.get_by_urlname(component)
|
||||
except KeyError:
|
||||
raise errors.TraversalError()
|
||||
|
||||
session = get_session()
|
||||
|
@ -298,7 +303,7 @@ class FormPage(Directory):
|
|||
if self.formdef.roles:
|
||||
if not session.user:
|
||||
raise errors.AccessUnauthorizedError()
|
||||
if get_logged_users_role() not in self.formdef.roles and not (user and user.is_admin):
|
||||
if logged_users_role().id not in self.formdef.roles and not (user and user.is_admin):
|
||||
for q in (user and user.roles) or []:
|
||||
if q in self.formdef.roles or q == self.formdef.receiver:
|
||||
break
|
||||
|
@ -355,24 +360,30 @@ class FormPage(Directory):
|
|||
if step == '0' and self.formdef.confirmation:
|
||||
return self.validating()
|
||||
else:
|
||||
filled = formdata.FormData(formdef = self.formdef)
|
||||
filled = self.formdef.data_class()()
|
||||
filled.data = self.formdef.get_data(form)
|
||||
filled.receipt_time = time.localtime()
|
||||
filled.status = 'new'
|
||||
session = get_session()
|
||||
if session and session.user and not str(session.user).startswith(str('anonymous-')):
|
||||
filled.user = User.get(session.user)
|
||||
filled.user_id = session.user
|
||||
else:
|
||||
a = AnonymityLink(formdata = filled)
|
||||
a = AnonymityLink()
|
||||
a.formdata_type = 'form'
|
||||
a.formdata_def_id = self.formdef.id
|
||||
a.formdata_id = filled.id
|
||||
if session.name_identifier:
|
||||
a.name_identifier = session.name_identifier
|
||||
# XXX nothing with anonylink.key ?
|
||||
a.store()
|
||||
filled.store()
|
||||
self.formdef.notify_new(filled)
|
||||
return self.receipt_page(filled)
|
||||
|
||||
def validating [html] (self):
|
||||
html_top(self.formdef.name)
|
||||
self.step(1)
|
||||
"""<p>%s</p>""" % _("Check values then click next.")
|
||||
"""<p>%s</p>""" % _("Check values then click submit.")
|
||||
form = self.formdef.create_view_form()
|
||||
token_widget = form.get_widget(form.TOKEN_NAME)
|
||||
token_widget._parsed = True
|
||||
|
@ -390,7 +401,7 @@ class FormPage(Directory):
|
|||
else:
|
||||
self.step(1)
|
||||
|
||||
tm = filled.receipt_time.strftime(str("%Y-%m-%d %H:%M"))
|
||||
tm = time.strftime(str("%Y-%m-%d %H:%M"), filled.receipt_time)
|
||||
"<p>"
|
||||
_('The form has been recorded on %s with the number %s.') % (tm, filled.id)
|
||||
"</p>"
|
||||
|
@ -454,13 +465,14 @@ class RootDirectory(AccessControlled, Directory):
|
|||
|
||||
backoffice_link = False
|
||||
l = []
|
||||
for formdef in FormDef.select(orderBy = FormDef.q.name):
|
||||
if user and formdef.receiver in user.roles:
|
||||
formdefs = FormDef.select(order_by = 'name')
|
||||
for formdef in formdefs:
|
||||
if user and formdef.receiver_id in (user.roles or []):
|
||||
backoffice_link = True
|
||||
if formdef.roles:
|
||||
if not session.user:
|
||||
continue
|
||||
if get_logged_users_role() not in formdef.roles:
|
||||
if logged_users_role().id not in formdef.roles:
|
||||
for q in (user and user.roles) or []:
|
||||
if q in formdef.roles:
|
||||
break
|
||||
|
@ -470,15 +482,15 @@ class RootDirectory(AccessControlled, Directory):
|
|||
if user and user.is_admin:
|
||||
backoffice_link = True
|
||||
|
||||
cats = Category.select(orderBy = Category.q.name)
|
||||
cats = Category.select(order_by = 'name')
|
||||
one = False
|
||||
for c in cats:
|
||||
l2 = [x for x in l if x.categoryID == c.id]
|
||||
l2 = [x for x in l if x.category_id == c.id]
|
||||
if l2:
|
||||
self.form_list(l2, title = c.name, session = session)
|
||||
one = True
|
||||
|
||||
l2 = [x for x in l if not x.categoryID]
|
||||
l2 = [x for x in l if not x.category_id]
|
||||
if l2:
|
||||
if one:
|
||||
title = _('Misc')
|
||||
|
@ -488,13 +500,20 @@ class RootDirectory(AccessControlled, Directory):
|
|||
|
||||
if session and session.user:
|
||||
l = []
|
||||
user_forms = []
|
||||
if str(session.user).startswith(str('anonymous-')):
|
||||
anonylinks = AnonymityLink.select(
|
||||
AnonymityLink.q.name_identifier == session.name_identifier)
|
||||
user_forms = [x.formdata for x in anonylinks if x.formdata]
|
||||
lambda x: x.name_identifier == session.name_identifier and
|
||||
x.formdata_type == 'form')
|
||||
for a in anonylinks:
|
||||
formdef = FormDef.get(a.formdata_def_id)
|
||||
user_forms.append(formdef.data_class().get(a.formdata_id))
|
||||
else:
|
||||
user_forms = FormData.select(FormData.q.userID == session.user,
|
||||
orderBy = FormData.q.receipt_time)
|
||||
for formdef in formdefs:
|
||||
user_forms.extend(formdef.data_class().select(
|
||||
lambda x: x.user_id == session.user))
|
||||
user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))
|
||||
|
||||
current = [x for x in user_forms if x.status in ('new', 'accepted')]
|
||||
if current:
|
||||
'<h2 id="submitted">%s</h2>' % _('Your Current Forms')
|
||||
|
@ -502,7 +521,7 @@ class RootDirectory(AccessControlled, Directory):
|
|||
for f in current:
|
||||
'<li><a href="%s/%s">%s</a>, %s, %s: %s</li>' % (
|
||||
f.formdef.url_name, f.id, f.formdef.name,
|
||||
f.receipt_time.strftime(str("%Y-%m-%d %H:%M")),
|
||||
time.strftime(str("%Y-%m-%d %H:%M"), f.receipt_time),
|
||||
_('status'),
|
||||
_(status_labels[f.status]) )
|
||||
'</ul>'
|
||||
|
@ -513,7 +532,7 @@ class RootDirectory(AccessControlled, Directory):
|
|||
for f in done:
|
||||
'<li><a href="%s/%s">%s</a>, %s, %s: %s</li>' % (
|
||||
f.formdef.url_name, f.id, f.formdef.name,
|
||||
f.receipt_time.strftime(str("%Y-%m-%d %H:%M")),
|
||||
time.strftime(str("%Y-%m-%d %H:%M"), f.receipt_time),
|
||||
_('status'),
|
||||
_(status_labels[f.status]) )
|
||||
'</ul>'
|
||||
|
|
|
@ -13,7 +13,7 @@ from wcs import misc
|
|||
from wcs import storage
|
||||
from wcs.form import *
|
||||
from wcs.template import *
|
||||
from wcs.users import User, NameIdentifier
|
||||
from wcs.users import User
|
||||
|
||||
|
||||
class RootDirectory(Directory):
|
||||
|
@ -108,9 +108,9 @@ class RootDirectory(Directory):
|
|||
def lookup_user(self, session, login):
|
||||
ni = login.nameIdentifier.content
|
||||
session.name_identifier = ni
|
||||
nis = NameIdentifier.select(NameIdentifier.q.name_identifier == ni)
|
||||
if nis.count():
|
||||
user = nis[0].user
|
||||
nis = list(User.select(lambda x: ni in x.name_identifiers))
|
||||
if nis:
|
||||
user = nis[0]
|
||||
else:
|
||||
if False and lasso.WSF_SUPPORT and misc.cfg.get('misc', {}).get('grab-user-with-wsf', False):
|
||||
disco = lasso.Discovery(login.server)
|
||||
|
@ -156,7 +156,7 @@ class RootDirectory(Directory):
|
|||
user = User()
|
||||
user.email = email
|
||||
user.name = name
|
||||
NameIdentifier(name_identifier = login.nameIdentifier.content, user = user)
|
||||
user.name_identifiers.append(login.nameIdentifier.content)
|
||||
user.lasso_dump = login.identity.dump()
|
||||
return user
|
||||
|
||||
|
|
115
wcs/roles.py
115
wcs/roles.py
|
@ -1,104 +1,17 @@
|
|||
from sqlobject import *
|
||||
from storage import StorableObject
|
||||
|
||||
def get_logged_users_role():
|
||||
return Role.select(AND(Role.q.name == 'Logged Users', Role.q.system == True))[0]
|
||||
class Role(StorableObject):
|
||||
_names = 'roles'
|
||||
name = None
|
||||
details = None
|
||||
emails = None
|
||||
|
||||
class RoleEmail(SQLObject):
|
||||
email = StringCol(length = 100)
|
||||
role = ForeignKey('Role')
|
||||
_cacheValues = False
|
||||
|
||||
|
||||
class Role(SQLObject):
|
||||
key = 'id'
|
||||
names = 'roles'
|
||||
_cacheValues = False
|
||||
|
||||
name = StringCol(length = 100)
|
||||
details = StringCol(default = None)
|
||||
emails = MultipleJoin('RoleEmail', joinMethodName = 'emails')
|
||||
system = BoolCol(default = False)
|
||||
|
||||
users = RelatedJoin('User')
|
||||
formdefs = RelatedJoin('FormDef')
|
||||
consultations = RelatedJoin('Consultation')
|
||||
|
||||
def initTable(cls):
|
||||
role = Role(name = N_('Logged Users'))
|
||||
role.details = 'System Group for Logged Users'
|
||||
role.system = True
|
||||
initTable = classmethod(initTable)
|
||||
|
||||
def migrateOldData(cls):
|
||||
import storage
|
||||
from users import User
|
||||
from formdef import FormDef
|
||||
from consultationdef import Consultation
|
||||
|
||||
init_method = cls.__init__
|
||||
cls.__init__ = lambda x: x
|
||||
roles = storage.get_storage().values('roles')
|
||||
items = [x.__dict__ for x in roles]
|
||||
cls.__init__ = init_method
|
||||
|
||||
new_ids_mapping = [('site-admin', 'site-admin')]
|
||||
for item in items:
|
||||
role = Role(name = item['name'])
|
||||
role.details = item.get('details')
|
||||
for email in item.get('emails', []):
|
||||
RoleEmail(email = email, role = role)
|
||||
new_ids_mapping.append( (item['id'], role.id) )
|
||||
|
||||
## fixing other tables to get the new ids
|
||||
# users
|
||||
user_init_method = User.__init__
|
||||
User.__init__ = lambda x: x
|
||||
users = storage.get_storage().values('users')
|
||||
for user in users:
|
||||
current_roles = user.__dict__['roles'][:]
|
||||
user.__dict__['roles'] = []
|
||||
for role in current_roles:
|
||||
if not role in [x[0] for x in new_ids_mapping]:
|
||||
continue # unknown role, skipping
|
||||
user.__dict__['roles'].append([x[1] for x in new_ids_mapping if x[0] == role][0])
|
||||
storage.get_storage().store(user)
|
||||
User.__init__ = user_init_method
|
||||
|
||||
# formdefs
|
||||
formdef_init_method = FormDef.__init__
|
||||
FormDef.__init__ = lambda x: x
|
||||
formdefs = storage.get_storage().values('formdefs')
|
||||
for formdef in formdefs:
|
||||
role = formdef.__dict__['receiver']
|
||||
if not role in [x[0] for x in new_ids_mapping]:
|
||||
formdef.__dict__['receiver'] = -1
|
||||
else:
|
||||
formdef.__dict__['receiver'] = [x[1] for x in new_ids_mapping if x[0] == role][0]
|
||||
|
||||
current_roles = formdef.__dict__.get('roles', [])[:]
|
||||
formdef.__dict__['roles'] = []
|
||||
for role in current_roles:
|
||||
if not role in [x[0] for x in new_ids_mapping]:
|
||||
continue # unknown role, skipping
|
||||
formdef.__dict__['roles'].append([x[1] for x in new_ids_mapping if x[0] == role][0])
|
||||
storage.get_storage().store(formdef)
|
||||
FormDef.__init__ = formdef_init_method
|
||||
|
||||
# consultations
|
||||
consultation_init_method = Consultation.__init__
|
||||
Consultation.__init__ = lambda x: x
|
||||
consultations = storage.get_storage().values('consultations')
|
||||
for consultation in consultations:
|
||||
role = consultation.__dict__['receiver']
|
||||
consultation.__dict__['receiver'] = [x[1] for x in new_ids_mapping if x[0] == role][0]
|
||||
current_roles = formdef.__dict__.get('roles', [])[:]
|
||||
consultation.__dict__['roles'] = []
|
||||
for role in current_roles:
|
||||
if not role in [x[0] for x in new_ids_mapping]:
|
||||
continue # unknown role, skipping
|
||||
consultation.__dict__['roles'].append([x[1] for x in new_ids_mapping \
|
||||
if x[0] == role][0])
|
||||
storage.get_storage().store(consultation)
|
||||
Consultation.__init__ = consultation_init_method
|
||||
migrateOldData = classmethod(migrateOldData)
|
||||
def __init__(self, name = None):
|
||||
StorableObject.__init__(self)
|
||||
self.name = name
|
||||
|
||||
def logged_users_role():
|
||||
volatile_role = Role.volatile()
|
||||
volatile_role.id = 'logged-users'
|
||||
volatile_role.name = _('Logged Users')
|
||||
return volatile_role
|
||||
|
|
26
wcs/root.ptl
26
wcs/root.ptl
|
@ -15,6 +15,7 @@ import template
|
|||
from form import *
|
||||
|
||||
from users import User
|
||||
from formdef import FormDef
|
||||
from formdata import FormData
|
||||
from anonylink import AnonymityLink
|
||||
|
||||
|
@ -68,24 +69,31 @@ class RootDirectory(Directory):
|
|||
else:
|
||||
return template.error_page("No Lasso Identity Dump (???)")
|
||||
token = form.get_widget('token').parse()
|
||||
users_with_token = User.select(User.q.identification_token == token)
|
||||
if users_with_token.count() == 0:
|
||||
users_with_token = list(User.select(lambda x: x.identification_token == token))
|
||||
if len(users_with_token) == 0:
|
||||
return template.error_page(_('Unknown Token'))
|
||||
|
||||
user = users_with_token[0]
|
||||
|
||||
NameIdentifier(name_identifier = session.name_identifier, user = user)
|
||||
user.name_identifiers.append(session.name_identifier)
|
||||
user.lasso_dump = str(lasso_dump)
|
||||
user.identification_token = None
|
||||
user.store()
|
||||
|
||||
old_name = session.user
|
||||
session.set_user(user.id)
|
||||
|
||||
for formdata in FormData.select(FormData.q.userID == old_name):
|
||||
formdata.user = user
|
||||
|
||||
for anonylink in AnonymityLink.select(
|
||||
AnonymityLink.q.name_identifier == session.name_identifier):
|
||||
anonylink.formdata.user = user
|
||||
lambda x: x.name_identifier == session.name_identifier):
|
||||
if anonylink.formdata_type == 'form':
|
||||
fdef = FormDef.get(anonylink.formdata_def_id)
|
||||
elif anonylink.formdata_type == 'consultation':
|
||||
fdef = Consultation.get(anonylink.formdata_def_id)
|
||||
else:
|
||||
continue # ?
|
||||
data = fdef.data_class().get(anonylink.formdata_id)
|
||||
data.user_id = user.id
|
||||
data.store()
|
||||
anonylink.remove_self()
|
||||
|
||||
return redirect('.')
|
||||
|
||||
|
|
137
wcs/sessions.py
137
wcs/sessions.py
|
@ -1,38 +1,12 @@
|
|||
from sqlobject import *
|
||||
|
||||
from quixote.session import Session, SessionManager
|
||||
from quixote.util import randbytes
|
||||
from storage import StorableObject
|
||||
|
||||
from extracols import DictPickleCol, ListPickleCol
|
||||
class BasicSession(Session, StorableObject):
|
||||
_names = 'sessions'
|
||||
|
||||
class SqlSession(SQLObject):
|
||||
_cacheValue = False
|
||||
|
||||
session_id = StringCol(notNone = True, alternateID = True, unique = True, length = 255)
|
||||
user = StringCol(default = None)
|
||||
remote_address = StringCol(default = None, length=30)
|
||||
creation_time = FloatCol(default = None)
|
||||
access_time = FloatCol(default = None)
|
||||
|
||||
name_identifier = StringCol(length = 100, default = None)
|
||||
anonymous_key = StringCol(length = 100, default = None)
|
||||
|
||||
after_url = StringCol(default = None)
|
||||
lasso_session_dump = StringCol(default = None)
|
||||
lasso_anonymous_identity_dump = StringCol(default = None)
|
||||
|
||||
tempfiles = DictPickleCol(default = None)
|
||||
form_tokens = ListPickleCol(default = None)
|
||||
|
||||
def _init(self, *args, **kw):
|
||||
return SQLObject._init(self, *args, **kw)
|
||||
|
||||
|
||||
class BasicSession(Session):
|
||||
name_identifier = None
|
||||
after_url = None
|
||||
anonymous_key = None
|
||||
tempfiles = None
|
||||
lasso_session_dump = None
|
||||
lasso_anonymous_identity_dump = None
|
||||
tempfiles = None
|
||||
|
@ -43,75 +17,64 @@ class BasicSession(Session):
|
|||
self.lasso_anonymous_identity_dump or Session.has_info(self)
|
||||
is_dirty = has_info
|
||||
|
||||
def get_session_id(self):
|
||||
return self.id
|
||||
|
||||
def set_session_id(self, session_id):
|
||||
self.id = session_id
|
||||
|
||||
def get_anonymous_key(self, generate = False):
|
||||
if self.anonymous_key:
|
||||
return self.anonymous_key
|
||||
if generate:
|
||||
self.anonymous_key = randbytes(20)
|
||||
self.anonymous_key = random.randint(0, 1000000000)
|
||||
return self.anonymous_key
|
||||
|
||||
session_id = property(get_session_id, set_session_id)
|
||||
|
||||
db_attributes = ['user', 'name_identifier', 'anonymous_key', 'after_url', 'lasso_session_dump',
|
||||
'lasso_anonymous_identity_dump', 'tempfiles']
|
||||
db_underscore_attributes = ['_remote_address', '_creation_time', '_access_time',
|
||||
'_form_tokens']
|
||||
|
||||
class SqlSessionMapping:
|
||||
def keys(self):
|
||||
return [x.session_id for x in SqlSession.select()]
|
||||
class StorageSessionManager(SessionManager):
|
||||
def __init__(self):
|
||||
SessionManager.__init__(self, session_class=BasicSession)
|
||||
|
||||
def values(self):
|
||||
return [self._from_db(x) for x in SqlSession.select()]
|
||||
|
||||
def items(self):
|
||||
return [(x.session_id, self._from_db(x)) for x in SqlSession.select()]
|
||||
|
||||
def _from_db(self, sqlsession):
|
||||
session = BasicSession(sqlsession.session_id)
|
||||
for attr in db_attributes:
|
||||
setattr(session, attr, getattr(sqlsession, attr))
|
||||
for attr in db_underscore_attributes:
|
||||
setattr(session, attr, getattr(sqlsession, attr[1:]))
|
||||
return session
|
||||
|
||||
def _to_db(self, session):
|
||||
try:
|
||||
sqlsession = SqlSession.bySession_id(session.id)
|
||||
except SQLObjectNotFound:
|
||||
sqlsession = SqlSession(session_id = session.id)
|
||||
|
||||
kw = {}
|
||||
for attr in db_attributes:
|
||||
kw[attr] = getattr(session, attr)
|
||||
for attr in db_underscore_attributes:
|
||||
kw[attr[1:]] = getattr(session, attr)
|
||||
sqlsession.set(**kw)
|
||||
|
||||
def get(self, session_id, default = None):
|
||||
if session_id is None:
|
||||
return default
|
||||
try:
|
||||
return self._from_db(SqlSession.bySession_id(session_id))
|
||||
except SQLObjectNotFound:
|
||||
return default
|
||||
|
||||
def __getitem__(self, k):
|
||||
return self.get(k)
|
||||
|
||||
def has_key(self, k):
|
||||
return (self.get(k) is not None)
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
def forget_changes(self, session):
|
||||
pass
|
||||
|
||||
def __delitem__(self, k):
|
||||
SqlSession.bySession_id(k).destroySelf()
|
||||
|
||||
def __getitem__(self, session_id):
|
||||
try:
|
||||
return BasicSession.get(session_id)
|
||||
except KeyError:
|
||||
raise KeyError
|
||||
|
||||
class SqlSessionManager(SessionManager):
|
||||
def __init__(self):
|
||||
SessionManager.__init__(self, BasicSession, SqlSessionMapping())
|
||||
def get(self, session_id, default = None):
|
||||
try:
|
||||
return BasicSession.get(session_id)
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def commit_changes(self, session):
|
||||
if session and session.has_info():
|
||||
self.sessions._to_db(session)
|
||||
if session and session.id:
|
||||
session.store()
|
||||
|
||||
def keys(self):
|
||||
return BasicSession.keys()
|
||||
|
||||
def values(self):
|
||||
return BasicSession.values()
|
||||
|
||||
def items(self):
|
||||
return BasicSession.items()
|
||||
|
||||
def has_key(self, session_id):
|
||||
return BasicSession.has_key(session_id)
|
||||
|
||||
def __setitem__(self, session_id, session):
|
||||
session.store()
|
||||
|
||||
def __delitem__(self, session_id):
|
||||
if not session_id:
|
||||
return
|
||||
try:
|
||||
BasicSession.remove_object(session_id)
|
||||
except OSError:
|
||||
raise KeyError
|
||||
|
|
162
wcs/storage.py
162
wcs/storage.py
|
@ -3,100 +3,98 @@ import cPickle
|
|||
|
||||
from quixote import get_publisher
|
||||
|
||||
class Storage:
|
||||
pass
|
||||
def lax_int(s):
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
|
||||
class Storable:
|
||||
def get_table_name(self):
|
||||
if hasattr(self, 'names'):
|
||||
return self.names
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
def fixkey(k):
|
||||
def fix_key(k):
|
||||
# insure key can be inserted in filesystem
|
||||
if not k: return k
|
||||
return str(k).replace('/', '-')
|
||||
|
||||
class FilesStorage:
|
||||
def __init__(self, base_dir):
|
||||
self.base_dir = base_dir
|
||||
if self.base_dir and not os.path.exists(self.base_dir):
|
||||
os.mkdir(self.base_dir)
|
||||
|
||||
def rename_table(self, obname, newname):
|
||||
dirname = os.path.join(self.base_dir, str(obname))
|
||||
newdirname = os.path.join(self.base_dir, str(newname))
|
||||
if os.path.exists(dirname):
|
||||
os.rename(dirname, newdirname)
|
||||
class StorableObject(object):
|
||||
def __init__(self):
|
||||
self.id = self.get_new_id()
|
||||
|
||||
def keys(self, obname):
|
||||
dirname = os.path.join(self.base_dir, str(obname))
|
||||
if not os.path.exists(dirname):
|
||||
def get_table_name(cls):
|
||||
return cls._names
|
||||
get_table_name = classmethod(get_table_name)
|
||||
|
||||
def get_objects_dir(cls):
|
||||
return os.path.join(get_publisher().app_dir, cls.get_table_name())
|
||||
get_objects_dir = classmethod(get_objects_dir)
|
||||
|
||||
def keys(cls):
|
||||
if not os.path.exists(cls.get_objects_dir()):
|
||||
return []
|
||||
return [fixkey(x) for x in os.listdir(dirname)]
|
||||
return [fix_key(x) for x in os.listdir(cls.get_objects_dir())]
|
||||
keys = classmethod(keys)
|
||||
|
||||
def count(cls):
|
||||
return len(cls.keys())
|
||||
count = classmethod(count)
|
||||
|
||||
def has_key(self, obname, key):
|
||||
return fixkey(key) in self.keys(obname)
|
||||
def select(cls, clause = None, order_by = None):
|
||||
objects = [cls.get(k) for k in cls.keys()]
|
||||
if clause:
|
||||
objects = [x for x in objects if clause(x)]
|
||||
if order_by:
|
||||
order_by = str(order_by)
|
||||
if order_by[0] == '-':
|
||||
reverse = True
|
||||
order_by = order_by[1:]
|
||||
else:
|
||||
reverse = False
|
||||
objects.sort(lambda x,y: cmp(getattr(x, order_by), getattr(y, order_by)))
|
||||
if reverse:
|
||||
objects.reverse()
|
||||
return objects
|
||||
select = classmethod(select)
|
||||
|
||||
def has_key(cls, id):
|
||||
return fix_key(id) in cls.keys()
|
||||
has_key = classmethod(has_key)
|
||||
|
||||
def retrieve(self, obname, key):
|
||||
if key is None:
|
||||
def get_new_id(cls):
|
||||
keys = cls.keys()
|
||||
if not keys:
|
||||
id = 1
|
||||
else:
|
||||
id = max([lax_int(x) for x in keys]) + 1
|
||||
if id == 0:
|
||||
id = len(keys)+1
|
||||
return id
|
||||
get_new_id = classmethod(get_new_id)
|
||||
|
||||
def get(cls, id):
|
||||
if not cls.has_key(id):
|
||||
raise KeyError()
|
||||
dirname = os.path.join(self.base_dir, str(obname))
|
||||
try:
|
||||
return cPickle.load(open(os.path.join(dirname, fixkey(key))))
|
||||
except IOError:
|
||||
raise KeyError()
|
||||
|
||||
def store(self, object):
|
||||
obname = object.names
|
||||
dirname = os.path.join(self.base_dir, obname)
|
||||
if not os.path.exists(dirname):
|
||||
os.mkdir(dirname)
|
||||
if not getattr(object, object.key):
|
||||
keys = self.keys(obname)
|
||||
key = len(keys) + 1
|
||||
while str(key) in keys:
|
||||
key += 1
|
||||
key = str(key)
|
||||
setattr(object, object.key, key)
|
||||
dirname = os.path.join(self.base_dir, str(obname))
|
||||
s = cPickle.dumps(object)
|
||||
file(os.path.join(dirname, fixkey(getattr(object, object.key))), 'w').write(s)
|
||||
s = file(os.path.join(cls.get_objects_dir(), fix_key(id))).read()
|
||||
o = cPickle.loads(s)
|
||||
o._names = cls._names
|
||||
if hasattr(cls, 'migrate'):
|
||||
o.migrate()
|
||||
return o
|
||||
get = classmethod(get)
|
||||
|
||||
def retrieve(self, obname, key):
|
||||
if key is None:
|
||||
raise KeyError()
|
||||
dirname = os.path.join(self.base_dir, str(obname))
|
||||
try:
|
||||
return cPickle.load(open(os.path.join(dirname, fixkey(key))))
|
||||
except (IOError, EOFError, cPickle.UnpicklingError):
|
||||
# XXX: logs error
|
||||
raise KeyError()
|
||||
def store(self):
|
||||
if not os.path.exists(self.get_objects_dir()):
|
||||
os.mkdir(self.get_objects_dir())
|
||||
s = cPickle.dumps(self)
|
||||
file(os.path.join(self.get_objects_dir(), fix_key(self.id)), 'w').write(s)
|
||||
|
||||
def values(self, obname):
|
||||
return [self.retrieve(obname, k) for k in self.keys(str(obname))]
|
||||
def volatile(cls):
|
||||
o = cls()
|
||||
o.id = None
|
||||
return o
|
||||
volatile = classmethod(volatile)
|
||||
|
||||
def items(self, obname):
|
||||
return [(k, self.retrieve(obname, k)) for k in self.keys(str(obname))]
|
||||
|
||||
def remove(self, object = None):
|
||||
obname = object.get_table_name()
|
||||
return self.remove_id(obname, fixkey(getattr(object, object.key)))
|
||||
|
||||
def remove_id(self, obname, key):
|
||||
dirname = os.path.join(self.base_dir, str(obname))
|
||||
os.unlink(os.path.join(dirname, fixkey(key)))
|
||||
|
||||
def remove_all(self, obname):
|
||||
dirname = os.path.join(self.base_dir, str(obname))
|
||||
if not os.path.exists(dirname):
|
||||
return
|
||||
for k in os.listdir(dirname):
|
||||
os.unlink(os.path.join(dirname, k))
|
||||
os.rmdir(dirname)
|
||||
|
||||
|
||||
def get_storage():
|
||||
return FilesStorage(get_publisher().app_dir)
|
||||
def remove_object(cls, id):
|
||||
os.unlink(os.path.join(cls.get_objects_dir(), fix_key(id)))
|
||||
remove_object = classmethod(remove_object)
|
||||
|
||||
def remove_self(self):
|
||||
os.unlink(os.path.join(self.get_objects_dir(), fix_key(self.id)))
|
||||
|
|
98
wcs/users.py
98
wcs/users.py
|
@ -1,86 +1,18 @@
|
|||
from sqlobject import *
|
||||
from storage import StorableObject
|
||||
|
||||
class NameIdentifier(SQLObject):
|
||||
name_identifier = StringCol()
|
||||
user = ForeignKey('User')
|
||||
_cacheValues = False
|
||||
class User(StorableObject):
|
||||
_names = 'users'
|
||||
|
||||
name = None
|
||||
email = None
|
||||
roles = None
|
||||
name_identifiers = None
|
||||
identification_token = None
|
||||
lasso_dump = None
|
||||
is_admin = False
|
||||
|
||||
class User(SQLObject):
|
||||
key = 'id'
|
||||
names = 'users'
|
||||
_cacheValues = False
|
||||
|
||||
name = StringCol(default = None, length = 100)
|
||||
email = StringCol(default = None, length = 100)
|
||||
roles = RelatedJoin('Role')
|
||||
name_identifiers = MultipleJoin('NameIdentifier', joinMethodName = 'name_identifiers')
|
||||
identification_token = StringCol(default = None)
|
||||
lasso_dump = StringCol(default = None)
|
||||
is_admin = BoolCol(default = False)
|
||||
|
||||
def migrateOldData(cls):
|
||||
import storage
|
||||
from roles import Role
|
||||
from formdef import FormDef
|
||||
from formdata import FormData
|
||||
from consultationdef import Consultation
|
||||
init_method = cls.__init__
|
||||
cls.__init__ = lambda x: x
|
||||
users = storage.get_storage().values('users')
|
||||
items = [x.__dict__ for x in users]
|
||||
cls.__init__ = init_method
|
||||
|
||||
items.sort(lambda x,y: cmp(x['id'], y['id']))
|
||||
new_ids_mapping = []
|
||||
for item in items:
|
||||
user = User(name = item['name'])
|
||||
user.email = item.get('email')
|
||||
user.identification_token = item.get('identification_token')
|
||||
user.lasso_dump = item.get('lasso_dump')
|
||||
if 'site-admin' in item.get('roles', []):
|
||||
user.is_admin = True
|
||||
for ni in item.get('name_identifiers', []):
|
||||
NameIdentifier(name_identifier = ni, user = user)
|
||||
for role in item.get('roles', []):
|
||||
if role == 'site-admin':
|
||||
continue
|
||||
user.addRole(Role.get(role))
|
||||
new_ids_mapping.append( (item['id'], user.id) )
|
||||
|
||||
## Fixing other tables to get the new id
|
||||
formdef_init_method = FormDef.__init__
|
||||
FormDef.__init__ = lambda x: x
|
||||
consultation_init_method = Consultation.__init__
|
||||
Consultation.__init__ = lambda x: x
|
||||
formdata_init_method = FormData.__init__
|
||||
FormData.__init__ = lambda x: x
|
||||
formdefs = storage.get_storage().values('formdefs')
|
||||
consultations = storage.get_storage().values('consultations')
|
||||
for elem in formdefs + consultations:
|
||||
if isinstance(elem, FormDef):
|
||||
formdataname = 'form-%s' % elem.__dict__['id']
|
||||
elif isinstance(elem, Consultation):
|
||||
formdataname = 'consultation-%s' % elem.__dict__['id']
|
||||
else:
|
||||
raise "unknown"
|
||||
for formdata in storage.get_storage().values(formdataname):
|
||||
if hasattr(formdata, 'user_id') and formdata.user_id:
|
||||
try:
|
||||
formdata.user_id = [x[1] for x in new_ids_mapping \
|
||||
if x[0] == formdata.user_id][0]
|
||||
except IndexError:
|
||||
pass
|
||||
if formdata.__dict__.has_key('evolution'):
|
||||
for evo in formdata.__dict__['evolution']:
|
||||
if not hasattr(evo, 'who') or not evo.who:
|
||||
continue
|
||||
evo.who = [x[1] for x in new_ids_mapping if x[0] == evo.who][0]
|
||||
storage.get_storage().store(formdata)
|
||||
|
||||
FormData.__init__ = formdata_init_method
|
||||
FormDef.__init__ = formdef_init_method
|
||||
Consultation.__init__ = consultation_init_method
|
||||
|
||||
migrateOldData = classmethod(migrateOldData)
|
||||
|
||||
def __init__(self, name = None):
|
||||
StorableObject.__init__(self)
|
||||
self.name = name
|
||||
self.name_identifiers = []
|
||||
self.roles = []
|
||||
|
|
Loading…
Reference in New Issue