moving away from SQLObject; back to good ol' custom pickle storage. Also added

some nice javascript from scriptaculous.
This commit is contained in:
Frédéric Péters 2005-09-05 19:17:22 +00:00
parent b2384151f1
commit 52b04ed34f
36 changed files with 4809 additions and 1108 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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.

1041
root/js/prototype.js vendored Normal file

File diff suppressed because it is too large Load Diff

699
root/js/scriptaculous/controls.js vendored Normal file
View File

@ -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);
}
}
};

545
root/js/scriptaculous/dragdrop.js vendored Normal file
View File

@ -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("&");
}
}

707
root/js/scriptaculous/effects.js vendored Normal file
View File

@ -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);
}

View File

@ -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();

View File

@ -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); }
}
});

View File

@ -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;
}

View File

@ -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():

View File

@ -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"> [ """

View File

@ -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, '&#8593;')
'</td><td>'
if i+1 != len(self.consultation.fields):
'<a class="arrow" href="../fields/%d/up">%s</a>' % (field.id, '&#8595;')
'</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)

View File

@ -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, '&#8593;')
'</td><td>'
if i+1 != len(self.formdef.fields):
'<a class="arrow" href="../fields/%d/down">%s</a>' % (field.id, '&#8595;')
'</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()

View File

@ -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>"""

View File

@ -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"> [ '

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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':

View File

@ -1,6 +1,5 @@
import quixote
from quixote.errors import *
from sqlobject import SQLObjectNotFound
import template

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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>'

View File

@ -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

View File

@ -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

View File

@ -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('.')

View File

@ -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

View File

@ -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)))

View File

@ -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 = []