From mcr@sandelman.ca:

Refactor to remove dajaxice.
  Modified the URLs that address schedules:
    - urls now always start with /agenda, removing the mix of /agenda and /schedule prefixes
    - urls to a given schedule include the schedule owner (by email address)
  Corrected issue that prevented deleting sessions.
  Changed comment mechanism in timeslot_edit.js
  Migration to change the email address for (System)

  Fixes bug #1426

Commit ready to merge
 - Legacy-Id: 8229
This commit is contained in:
Robert Sparks 2014-07-31 22:13:01 +00:00
parent a78cb8ce36
commit fc512680d8
42 changed files with 466 additions and 1254 deletions

View file

@ -1 +0,0 @@
__version__ = (0, 5, 5, 'beta')

View file

@ -1,137 +0,0 @@
import logging
from django.utils.importlib import import_module
log = logging.getLogger('dajaxice')
class DajaxiceFunction(object):
""" Basic representation of a dajaxice ajax function."""
def __init__(self, function, name, method):
self.function = function
self.name = name
self.method = method
def call(self, *args, **kwargs):
""" Call the function. """
return self.function(*args, **kwargs)
class DajaxiceModule(object):
""" Basic representation of a dajaxice module. """
def __init__(self, name=None):
self.name = name
self.functions = {}
self.submodules = {}
def add(self, name, function):
""" Add this function at the ``name`` deep. If the submodule already
exists, recusively call the add method into the submodule. If not,
create the module and call the add method."""
# If this is not the final function name (there are more modules)
# split the name again an register a new submodule.
if '.' in name:
module, extra = name.split('.', 1)
if module not in self.submodules:
self.submodules[module] = DajaxiceModule(module)
self.submodules[module].add(extra, function)
else:
self.functions[name] = function
class Dajaxice(object):
def __init__(self):
self._registry = {}
self._modules = None
def register(self, function, name=None, method='POST'):
"""
Register this function as a dajaxice function.
If no name is provided, the module and the function name will be used.
The final (customized or not) must be unique. """
method = self.clean_method(method)
# Generate a default name
if not name:
module = ''.join(str(function.__module__).rsplit('.ajax', 1))
name = '.'.join((module, function.__name__))
if ':' in name:
log.error('Ivalid function name %s.' % name)
return
# Check for already registered functions
if name in self._registry:
log.error('%s was already registered.' % name)
return
# Create the dajaxice function.
function = DajaxiceFunction(function=function,
name=name,
method=method)
# Register this new ajax function
self._registry[name] = function
def is_callable(self, name, method):
""" Return if the function callable or not. """
return name in self._registry and self._registry[name].method == method
def clean_method(self, method):
""" Clean the http method. """
method = method.upper()
if method not in ['GET', 'POST']:
method = 'POST'
return method
def get(self, name):
""" Return the dajaxice function."""
return self._registry[name]
@property
def modules(self):
""" Return an easy to loop module hierarchy with all the functions."""
if not self._modules:
self._modules = DajaxiceModule()
for name, function in self._registry.items():
self._modules.add(name, function)
return self._modules
LOADING_DAJAXICE = False
def dajaxice_autodiscover():
"""
Auto-discover INSTALLED_APPS ajax.py modules and fail silently when
not present. NOTE: dajaxice_autodiscover was inspired/copied from
django.contrib.admin autodiscover
"""
global LOADING_DAJAXICE
if LOADING_DAJAXICE:
return
LOADING_DAJAXICE = True
import imp
from django.conf import settings
for app in settings.INSTALLED_APPS:
try:
app_path = import_module(app).__path__
except AttributeError:
continue
try:
imp.find_module('ajax', app_path)
except ImportError:
continue
import_module("%s.ajax" % app)
LOADING_DAJAXICE = False

View file

@ -1,38 +0,0 @@
from django.conf import settings
from Dajaxice import Dajaxice, dajaxice_autodiscover
class DajaxiceConfig(object):
""" Provide an easy to use way to read the dajaxice configuration and
return the default values if no configuration is present."""
default_config = {'DAJAXICE_XMLHTTPREQUEST_JS_IMPORT': True,
'DAJAXICE_JSON2_JS_IMPORT': True,
'DAJAXICE_EXCEPTION': 'DAJAXICE_EXCEPTION',
'DAJAXICE_MEDIA_PREFIX': 'dajaxice'}
def __getattr__(self, name):
""" Return the customized value for a setting (if it exists) or the
default value if not. """
if name in self.default_config:
if hasattr(settings, name):
return getattr(settings, name)
return self.default_config.get(name)
return None
@property
def dajaxice_url(self):
return r'^%s/' % self.DAJAXICE_MEDIA_PREFIX
@property
def django_settings(self):
return settings
@property
def modules(self):
return dajaxice_functions.modules
dajaxice_functions = Dajaxice()
dajaxice_config = DajaxiceConfig()

View file

@ -1,48 +0,0 @@
import functools
from dajaxice.core import dajaxice_functions
def dajaxice_register(*dargs, **dkwargs):
""" Register some function as a dajaxice function
For legacy purposes, if only a function is passed register it a simple
single ajax function using POST, i.e:
@dajaxice_register
def ajax_function(request):
...
After 0.5, dajaxice allow to customize the http method and the final name
of the registered function. This decorator covers both the legacy and
the new functionality, i.e:
@dajaxice_register(method='GET')
def ajax_function(request):
...
@dajaxice_register(method='GET', name='my.custom.name')
def ajax_function(request):
...
You can also register the same function to use a different http method
and/or use a different name.
@dajaxice_register(method='GET', name='users.get')
@dajaxice_register(method='POST', name='users.update')
def ajax_function(request):
...
"""
if len(dargs) and not dkwargs:
function = dargs[0]
dajaxice_functions.register(function)
return function
def decorator(function):
@functools.wraps(function)
def wrapper(request, *args, **kwargs):
return function(request, *args, **kwargs)
dajaxice_functions.register(function, *dargs, **dkwargs)
return wrapper
return decorator

View file

@ -1,10 +0,0 @@
class DajaxiceError(Exception):
pass
class FunctionNotCallableError(DajaxiceError):
pass
class DajaxiceImportError(DajaxiceError):
pass

View file

@ -1,75 +0,0 @@
import os
import tempfile
from django.contrib.staticfiles import finders
from django.template import Context
from django.template.loader import get_template
from django.core.exceptions import SuspiciousOperation
class VirtualStorage(finders.FileSystemStorage):
"""" Mock a FileSystemStorage to build tmp files on demand."""
def __init__(self, *args, **kwargs):
self._files_cache = {}
super(VirtualStorage, self).__init__(*args, **kwargs)
def get_or_create_file(self, path):
if path not in self.files:
return ''
data = getattr(self, self.files[path])()
try:
current_file = open(self._files_cache[path])
current_data = current_file.read()
current_file.close()
if current_data != data:
os.remove(path)
raise Exception("Invalid data")
except Exception:
handle, tmp_path = tempfile.mkstemp()
tmp_file = open(tmp_path, 'w')
tmp_file.write(data)
tmp_file.close()
self._files_cache[path] = tmp_path
return self._files_cache[path]
def exists(self, name):
return name in self.files
def listdir(self, path):
folders, files = [], []
for f in self.files:
if f.startswith(path):
f = f.replace(path, '', 1)
if os.sep in f:
folders.append(f.split(os.sep, 1)[0])
else:
files.append(f)
return folders, files
def path(self, name):
try:
path = self.get_or_create_file(name)
except ValueError:
raise SuspiciousOperation("Attempted access to '%s' denied." % name)
return os.path.normpath(path)
class DajaxiceStorage(VirtualStorage):
files = {os.path.join('dajaxice', 'dajaxice.core.js'): 'dajaxice_core_js'}
def dajaxice_core_js(self):
from dajaxice.core import dajaxice_autodiscover, dajaxice_config
dajaxice_autodiscover()
c = Context({'dajaxice_config': dajaxice_config})
return get_template(os.path.join('dajaxice', 'dajaxice.core.js')).render(c)
class DajaxiceFinder(finders.BaseStorageFinder):
storage = DajaxiceStorage()

View file

@ -1 +0,0 @@
# Don't delete me

View file

@ -1,145 +0,0 @@
{% load url from future %}
var Dajaxice = {
{% with module=dajaxice_config.modules top='top' %}
{% include "dajaxice/dajaxice_function_loop.js" %}
{% endwith %}
{% for name, module in dajaxice_config.modules.submodules.items %}
{% include "dajaxice/dajaxice_module_loop.js" %},
{% endfor %}
get_cookie: function(name)
{
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].toString().replace(/^\s+/, "").replace(/\s+$/, "");
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
},
call: function(dajaxice_function, method, dajaxice_callback, argv, custom_settings)
{
var custom_settings = custom_settings || {},
error_callback = Dajaxice.get_setting('default_exception_callback');
if('error_callback' in custom_settings && typeof(custom_settings['error_callback']) == 'function'){
error_callback = custom_settings['error_callback'];
}
var send_data = 'argv='+encodeURIComponent(JSON.stringify(argv)),
oXMLHttpRequest = new XMLHttpRequest,
endpoint = '{% url 'dajaxice-endpoint' %}'+dajaxice_function+'/';
if(method == 'GET'){
endpoint = endpoint + '?' + send_data;
}
oXMLHttpRequest.open(method, endpoint);
oXMLHttpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
oXMLHttpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest");
oXMLHttpRequest.setRequestHeader("X-CSRFToken", Dajaxice.get_cookie('{{ dajaxice_config.django_settings.CSRF_COOKIE_NAME }}'));
oXMLHttpRequest.onreadystatechange = function() {
if (this.readyState == XMLHttpRequest.DONE) {
if(this.responseText == Dajaxice.EXCEPTION || !(this.status in Dajaxice.valid_http_responses())){
error_callback();
}
else{
var response;
try {
response = JSON.parse(this.responseText);
}
catch (exception) {
response = this.responseText;
}
dajaxice_callback(response);
}
}
}
if(method == 'POST'){
oXMLHttpRequest.send(send_data);
}
else{
oXMLHttpRequest.send();
}
return oXMLHttpRequest;
},
setup: function(settings)
{
this.settings = settings;
},
get_setting: function(key){
if(this.settings == undefined || this.settings[key] == undefined){
return Dajaxice.default_settings[key];
}
return this.settings[key];
},
valid_http_responses: function(){
return {200: null, 301: null, 302: null, 304: null}
},
EXCEPTION: '{{ dajaxice_config.DAJAXICE_EXCEPTION }}',
default_settings: {'default_exception_callback': function(){ console.log('Dajaxice: Something went wrong.')}}
};
window['Dajaxice'] = Dajaxice;
{% comment %}
/*
XMLHttpRequest.js Compiled with Google Closure
XMLHttpRequest.js Copyright (C) 2008 Sergey Ilinsky (http://www.ilinsky.com)
This work is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This work is distributed in the hope that it will be useful,
but without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
{% endcomment %}
{% if dajaxice_config.DAJAXICE_XMLHTTPREQUEST_JS_IMPORT %}
(function(){function n(){this._object=h&&!p?new h:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[]}function a(){return new n}function j(b){a.onreadystatechange&&a.onreadystatechange.apply(b);b.dispatchEvent({type:"readystatechange",bubbles:!1,cancelable:!1,timeStamp:new Date+0})}function o(b){try{b.responseText=b._object.responseText}catch(a){}try{var d;var g=b._object,c=g.responseXML,f=g.responseText;i&&(f&&c&&!c.documentElement&&g.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/))&&
(c=new window.ActiveXObject("Microsoft.XMLDOM"),c.async=!1,c.validateOnParse=!1,c.loadXML(f));d=c&&(i&&0!==c.parseError||!c.documentElement||c.documentElement&&"parsererror"==c.documentElement.tagName)?null:c;b.responseXML=d}catch(h){}try{b.status=b._object.status}catch(k){}try{b.statusText=b._object.statusText}catch(j){}}function l(b){b._object.onreadystatechange=new window.Function}var h=window.XMLHttpRequest,m=!!window.controllers,i=window.document.all&&!window.opera,p=i&&window.navigator.userAgent.match(/MSIE 7.0/);
a.prototype=n.prototype;m&&h.wrapped&&(a.wrapped=h.wrapped);a.UNSENT=0;a.OPENED=1;a.HEADERS_RECEIVED=2;a.LOADING=3;a.DONE=4;a.prototype.readyState=a.UNSENT;a.prototype.responseText="";a.prototype.responseXML=null;a.prototype.status=0;a.prototype.statusText="";a.prototype.priority="NORMAL";a.prototype.onreadystatechange=null;a.onreadystatechange=null;a.onopen=null;a.onsend=null;a.onabort=null;a.prototype.open=function(b,e,d,g,c){delete this._headers;arguments.length<3&&(d=true);this._async=d;var f=
this,h=this.readyState,k=null;if(i&&d){k=function(){if(h!=a.DONE){l(f);f.abort()}};window.attachEvent("onunload",k)}a.onopen&&a.onopen.apply(this,arguments);arguments.length>4?this._object.open(b,e,d,g,c):arguments.length>3?this._object.open(b,e,d,g):this._object.open(b,e,d);this.readyState=a.OPENED;j(this);this._object.onreadystatechange=function(){if(!m||d){f.readyState=f._object.readyState;o(f);if(f._aborted)f.readyState=a.UNSENT;else if(f.readyState==a.DONE){delete f._data;l(f);i&&d&&window.detachEvent("onunload",
k);h!=f.readyState&&j(f);h=f.readyState}}}};a.prototype.send=function(b){a.onsend&&a.onsend.apply(this,arguments);arguments.length||(b=null);if(b&&b.nodeType){b=window.XMLSerializer?(new window.XMLSerializer).serializeToString(b):b.xml;this._headers["Content-Type"]||this._object.setRequestHeader("Content-Type","application/xml")}this._data=b;a:{this._object.send(this._data);if(m&&!this._async){this.readyState=a.OPENED;for(o(this);this.readyState<a.DONE;){this.readyState++;j(this);if(this._aborted)break a}}}};
a.prototype.abort=function(){a.onabort&&a.onabort.apply(this,arguments);if(this.readyState>a.UNSENT)this._aborted=true;this._object.abort();l(this);this.readyState=a.UNSENT;delete this._data};a.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()};a.prototype.getResponseHeader=function(b){return this._object.getResponseHeader(b)};a.prototype.setRequestHeader=function(b,a){if(!this._headers)this._headers={};this._headers[b]=a;return this._object.setRequestHeader(b,
a)};a.prototype.addEventListener=function(a,e,d){for(var g=0,c;c=this._listeners[g];g++)if(c[0]==a&&c[1]==e&&c[2]==d)return;this._listeners.push([a,e,d])};a.prototype.removeEventListener=function(a,e,d){for(var g=0,c;c=this._listeners[g];g++)if(c[0]==a&&c[1]==e&&c[2]==d)break;c&&this._listeners.splice(g,1)};a.prototype.dispatchEvent=function(a){a={type:a.type,target:this,currentTarget:this,eventPhase:2,bubbles:a.bubbles,cancelable:a.cancelable,timeStamp:a.timeStamp,stopPropagation:function(){},preventDefault:function(){},
initEvent:function(){}};a.type=="readystatechange"&&this.onreadystatechange&&(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[a]);for(var e=0,d;d=this._listeners[e];e++)d[0]==a.type&&!d[2]&&(d[1].handleEvent||d[1]).apply(this,[a])};a.prototype.toString=function(){return"[object XMLHttpRequest]"};a.toString=function(){return"[XMLHttpRequest]"};window.Function.prototype.apply||(window.Function.prototype.apply=function(a,e){e||(e=[]);a.__func=this;a.__func(e[0],e[1],e[2],e[3],
e[4]);delete a.__func});window.XMLHttpRequest=a})();
{% endif %}
{% comment %}
/*
http://www.json.org/json2.js Compiled with Google Closure
This code was released under Public Domain by json.org , Thanks!
I hope that in the future this code won't be neccessary, but today all browsers doesn't supports JSON.stringify().
*/
{% endcomment %}
{% if dajaxice_config.DAJAXICE_JSON2_JS_IMPORT %}
var JSON;JSON||(JSON={});
(function(){function k(a){return 10>a?"0"+a:a}function o(a){p.lastIndex=0;return p.test(a)?'"'+a.replace(p,function(a){var c=r[a];return"string"===typeof c?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function m(a,j){var c,d,h,n,g=e,f,b=j[a];b&&("object"===typeof b&&"function"===typeof b.toJSON)&&(b=b.toJSON(a));"function"===typeof i&&(b=i.call(j,a,b));switch(typeof b){case "string":return o(b);case "number":return isFinite(b)?String(b):"null";case "boolean":case "null":return String(b);case "object":if(!b)return"null";
e+=l;f=[];if("[object Array]"===Object.prototype.toString.apply(b)){n=b.length;for(c=0;c<n;c+=1)f[c]=m(c,b)||"null";h=0===f.length?"[]":e?"[\n"+e+f.join(",\n"+e)+"\n"+g+"]":"["+f.join(",")+"]";e=g;return h}if(i&&"object"===typeof i){n=i.length;for(c=0;c<n;c+=1)"string"===typeof i[c]&&(d=i[c],(h=m(d,b))&&f.push(o(d)+(e?": ":":")+h))}else for(d in b)Object.prototype.hasOwnProperty.call(b,d)&&(h=m(d,b))&&f.push(o(d)+(e?": ":":")+h);h=0===f.length?"{}":e?"{\n"+e+f.join(",\n"+e)+"\n"+g+"}":"{"+f.join(",")+
"}";e=g;return h}}"function"!==typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+k(this.getUTCMonth()+1)+"-"+k(this.getUTCDate())+"T"+k(this.getUTCHours())+":"+k(this.getUTCMinutes())+":"+k(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var q=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
p=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,e,l,r={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},i;"function"!==typeof JSON.stringify&&(JSON.stringify=function(a,j,c){var d;l=e="";if(typeof c==="number")for(d=0;d<c;d=d+1)l=l+" ";else typeof c==="string"&&(l=c);if((i=j)&&typeof j!=="function"&&(typeof j!=="object"||typeof j.length!=="number"))throw Error("JSON.stringify");return m("",{"":a})});
"function"!==typeof JSON.parse&&(JSON.parse=function(a,e){function c(a,d){var g,f,b=a[d];if(b&&typeof b==="object")for(g in b)if(Object.prototype.hasOwnProperty.call(b,g)){f=c(b,g);f!==void 0?b[g]=f:delete b[g]}return e.call(a,d,b)}var d,a=String(a);q.lastIndex=0;q.test(a)&&(a=a.replace(q,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){d=eval("("+a+")");return typeof e==="function"?c({"":d},""):d}throw new SyntaxError("JSON.parse");})})();
{% endif %}

View file

@ -1,5 +0,0 @@
{% for function_name, function in module.functions.items %}
{{ function_name }}: function(callback_function, argv, custom_settings){
return Dajaxice.call('{{ function.name }}', '{{ function.method }}', callback_function, argv, custom_settings);
}{% if not forloop.last or top or module.submodules %},{% endif %}
{% endfor %}

View file

@ -1,11 +0,0 @@
{{ name }}: {
{% include "dajaxice/dajaxice_function_loop.js" %}
{% with parent_foorloop=forloop %}
{% for name, sub_module in module.submodules.items %}
{% with filename="dajaxice/dajaxice_module_loop.js" module=sub_module %}
{% include filename %}
{% endwith %}
{% if not forloop.last %},{% endif %}
{% endfor %}
}
{% endwith %}

View file

@ -1,35 +0,0 @@
import logging
from django import template
from django.middleware.csrf import get_token
from django.conf import settings
from django.core.files.storage import get_storage_class
staticfiles_storage = get_storage_class(settings.STATICFILES_STORAGE)()
register = template.Library()
log = logging.getLogger('dajaxice')
@register.simple_tag(takes_context=True)
def dajaxice_js_import(context, csrf=True):
""" Return the js script tag for the dajaxice.core.js file
If the csrf argument is present and it's ``nocsrf`` dajaxice will not
try to mark the request as if it need the csrf token. By default use
the dajaxice_js_import template tag will make django set the csrftoken
cookie on the current request."""
csrf = csrf != 'nocsrf'
request = context.get('request')
if request and csrf:
get_token(request)
elif csrf:
log.warning("The 'request' object must be accesible within the "
"context. You must add 'django.contrib.messages.context"
"_processors.request' to your TEMPLATE_CONTEXT_PROCESSORS "
"and render your views using a RequestContext.")
url = staticfiles_storage.url('dajaxice/dajaxice.core.js')
return '<script src="%s" type="text/javascript" charset="utf-8"></script>' % url

View file

@ -1,165 +0,0 @@
from django.test import TestCase
from django.conf import settings
from dajaxice.core import DajaxiceConfig
from dajaxice.core.Dajaxice import DajaxiceModule, DajaxiceFunction, Dajaxice
from dajaxice.exceptions import FunctionNotCallableError
class DajaxiceModuleTest(TestCase):
def setUp(self):
self.module = DajaxiceModule()
def test_constructor(self):
self.assertEqual(self.module.functions, {})
self.assertEqual(self.module.submodules, {})
def test_add_function(self):
function = lambda x: x
self.module.add('test', function)
self.assertEqual(self.module.functions, {'test': function})
self.assertEqual(self.module.submodules, {})
self.module.add('foo.bar', function)
self.assertEqual(self.module.functions, {'test': function})
self.assertEqual(self.module.submodules.keys(), ['foo'])
self.assertEqual(self.module.submodules['foo'].functions, {'bar': function})
class DajaxiceFunctionTest(TestCase):
def test_constructor(self):
class CalledEception(Exception):
pass
def callback():
raise CalledEception
function = DajaxiceFunction(callback, 'foo', 'POST')
self.assertEqual(function.function, callback)
self.assertEqual(function.name, 'foo')
self.assertEqual(function.method, 'POST')
self.assertRaises(CalledEception, function.call)
class DajaxiceTest(TestCase):
def setUp(self):
self.dajaxice = Dajaxice()
self.function = lambda x: x
def test_constructor(self):
self.assertEqual(self.dajaxice._registry, {})
def test_register(self):
self.dajaxice.register(self.function, 'foo')
self.assertTrue('foo' in self.dajaxice._registry)
self.assertEqual(type(self.dajaxice._registry['foo']), DajaxiceFunction)
def bar_function():
return
self.dajaxice.register(bar_function)
self.assertTrue('dajaxice.tests.bar_function' in self.dajaxice._registry)
self.assertEqual(type(self.dajaxice._registry['dajaxice.tests.bar_function']), DajaxiceFunction)
def test__is_callable(self):
self.dajaxice.register(self.function, 'foo')
self.dajaxice.register(self.function, 'bar', method='GET')
self.assertTrue(self.dajaxice.is_callable('foo', 'POST'))
self.assertTrue(self.dajaxice.is_callable('bar', 'GET'))
self.assertFalse(self.dajaxice.is_callable('foo', 'GET'))
self.assertFalse(self.dajaxice.is_callable('bar', 'POST'))
self.assertFalse(self.dajaxice.is_callable('test', 'POST'))
self.assertFalse(self.dajaxice.is_callable('test', 'GET'))
def test_clean_method(self):
self.assertEqual(self.dajaxice.clean_method('post'), 'POST')
self.assertEqual(self.dajaxice.clean_method('get'), 'GET')
self.assertEqual(self.dajaxice.clean_method('POST'), 'POST')
self.assertEqual(self.dajaxice.clean_method('GET'), 'GET')
self.assertEqual(self.dajaxice.clean_method('other'), 'POST')
def test_modules(self):
self.dajaxice.register(self.function, 'foo')
self.dajaxice.register(self.function, 'bar')
self.assertEqual(type(self.dajaxice.modules), DajaxiceModule)
self.assertEqual(self.dajaxice.modules.functions.keys(), ['foo', 'bar'])
class DajaxiceConfigTest(TestCase):
def setUp(self):
self.config = DajaxiceConfig()
def test_defaults(self):
self.assertTrue(self.config.DAJAXICE_XMLHTTPREQUEST_JS_IMPORT)
self.assertTrue(self.config.DAJAXICE_JSON2_JS_IMPORT)
self.assertEqual(self.config.DAJAXICE_EXCEPTION, 'DAJAXICE_EXCEPTION')
self.assertEqual(self.config.DAJAXICE_MEDIA_PREFIX, 'dajaxice')
dajaxice_url = r'^%s/' % self.config.DAJAXICE_MEDIA_PREFIX
self.assertEqual(self.config.dajaxice_url, dajaxice_url)
self.assertEqual(type(self.config.modules), DajaxiceModule)
class DjangoIntegrationTest(TestCase):
urls = 'dajaxice.tests.urls'
def setUp(self):
settings.INSTALLED_APPS += ('dajaxice.tests',)
def test_calling_not_registered_function(self):
self.failUnlessRaises(FunctionNotCallableError, self.client.post, '/dajaxice/dajaxice.tests.this_function_not_exist/')
def test_calling_registered_function(self):
response = self.client.post('/dajaxice/dajaxice.tests.test_foo/')
self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(response.content, '{"foo": "bar"}')
def test_calling_registered_function_with_params(self):
response = self.client.post('/dajaxice/dajaxice.tests.test_foo_with_params/', {'argv': '{"param1":"value1"}'})
self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(response.content, '{"param1": "value1"}')
def test_bad_function(self):
response = self.client.post('/dajaxice/dajaxice.tests.test_ajax_exception/')
self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(response.content, "DAJAXICE_EXCEPTION")
def test_get_register(self):
response = self.client.get('/dajaxice/dajaxice.tests.test_get_register/')
self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(response.content, '{"foo": "user"}')
def test_get_custom_name_register(self):
response = self.client.get('/dajaxice/get_user_data/')
self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(response.content, '{"bar": "user"}')
def test_multi_register(self):
response = self.client.get('/dajaxice/get_multi/')
self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(response.content, '{"foo": "multi"}')
response = self.client.post('/dajaxice/post_multi/')
self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(response.content, '{"foo": "multi"}')

View file

@ -1,43 +0,0 @@
from django.utils import simplejson
from dajaxice.decorators import dajaxice_register
@dajaxice_register
def test_registered_function(request):
return ""
@dajaxice_register
def test_string(request):
return simplejson.dumps({'string': 'hello world'})
@dajaxice_register
def test_ajax_exception(request):
raise Exception()
@dajaxice_register
def test_foo(request):
return simplejson.dumps({'foo': 'bar'})
@dajaxice_register
def test_foo_with_params(request, param1):
return simplejson.dumps({'param1': param1})
@dajaxice_register(method='GET')
def test_get_register(request):
return simplejson.dumps({'foo': 'user'})
@dajaxice_register(method='GET', name="get_user_data")
def test_get_with_name_register(request):
return simplejson.dumps({'bar': 'user'})
@dajaxice_register(method='GET', name="get_multi")
@dajaxice_register(name="post_multi")
def test_multi_register(request):
return simplejson.dumps({'foo': 'multi'})

View file

@ -1 +0,0 @@
Django

View file

@ -1,10 +0,0 @@
from django.conf.urls.defaults import *
from dajaxice.core import dajaxice_autodiscover, dajaxice_config
dajaxice_autodiscover()
urlpatterns = patterns('',
#Dajaxice URLS
url(dajaxice_config.dajaxice_url, include('dajaxice.urls')),
)

View file

@ -1,7 +0,0 @@
from django.conf.urls import patterns, url, include
from .views import DajaxiceRequest
urlpatterns = patterns('dajaxice.views',
url(r'^(.+)/$', DajaxiceRequest.as_view(), name='dajaxice-call-endpoint'),
url(r'', DajaxiceRequest.as_view(), name='dajaxice-endpoint'),
)

View file

@ -1,8 +0,0 @@
from django.http import QueryDict
def deserialize_form(data):
"""
Create a new QueryDict from a serialized form.
"""
return QueryDict(query_string=unicode(data).encode('utf-8'))

View file

@ -1,60 +0,0 @@
import logging
from django.conf import settings
import json
from django.views.generic.base import View
from django.http import HttpResponse, Http404
from dajaxice.exceptions import FunctionNotCallableError
from dajaxice.core import dajaxice_functions, dajaxice_config
log = logging.getLogger('dajaxice')
def safe_dict(d):
"""
Recursively clone json structure with UTF-8 dictionary keys
http://www.gossamer-threads.com/lists/python/bugs/684379
"""
if isinstance(d, dict):
return dict([(k.encode('utf-8'), safe_dict(v)) for k, v in d.iteritems()])
elif isinstance(d, list):
return [safe_dict(x) for x in d]
else:
return d
class DajaxiceRequest(View):
""" Handle all the dajaxice xhr requests. """
def dispatch(self, request, name=None):
if not name:
raise Http404
# Check if the function is callable
if dajaxice_functions.is_callable(name, request.method):
function = dajaxice_functions.get(name)
data = getattr(request, function.method).get('argv', '')
# Clean the argv
if data != 'undefined':
try:
data = safe_dict(json.loads(data))
except Exception:
data = {}
else:
data = {}
# Call the function. If something goes wrong, handle the Exception
try:
response = function.call(request, **data)
except Exception:
if settings.DEBUG:
raise
response = dajaxice_config.DAJAXICE_EXCEPTION
return HttpResponse(response, content_type="application/x-json")
else:
raise FunctionNotCallableError(name)

View file

@ -1,136 +1,72 @@
import json
import datetime
from django.shortcuts import get_object_or_404, redirect
from django.http import HttpResponse
from django.http import QueryDict
from django.http import Http404
from django.views.decorators.http import require_POST
from dajaxice.decorators import dajaxice_register
from ietf.ietfauth.utils import role_required, has_role, user_is_person
from ietf.meeting.helpers import get_meeting, get_schedule, get_schedule_by_id, agenda_permissions
from ietf.ietfauth.utils import role_required, has_role
from ietf.meeting.helpers import get_meeting, get_schedule, agenda_permissions, get_person_by_email, get_schedule_by_name
from ietf.meeting.models import TimeSlot, Session, Schedule, Room, Constraint, ScheduledSession, ResourceAssociation
from ietf.meeting.views import edit_timeslots, edit_agenda
from ietf.name.models import TimeSlotTypeName
import debug # pyflakes:ignore
def dajaxice_core_js(request):
# this is a slightly weird hack to get, we seem to need this because
# we're not using the built-in static files support
from dajaxice.finders import DajaxiceStorage
return HttpResponse(DajaxiceStorage().dajaxice_core_js(), content_type="application/javascript")
def is_truthy_enough(value):
return not (value == "0" or value == 0 or value=="false")
@dajaxice_register
def readonly(request, meeting_num, schedule_id):
meeting = get_meeting(meeting_num)
schedule = get_schedule_by_id(meeting, schedule_id)
# look up a schedule by number, owner and schedule name, returning an API error if it can not be found
def get_meeting_schedule(num, owner, name):
meeting = get_meeting(num)
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
if schedule is None or person is None or meeting is None:
meeting_pk = 0
person_pk = 0
schedule_pk =0
# to make diagnostics more meaningful, log what we found
if meeting:
meeting_pk = meeting.pk
if person:
person_pk = person.pk
if schedule:
schedule_pk=schedule.pk
return HttpResponse(json.dumps({'error' : 'invalid meeting=%s/person=%s/schedule=%s' % (num,owner,name),
'meeting': meeting_pk,
'person': person_pk,
'schedule': schedule_pk}),
content_type="application/json",
status=404);
return meeting, person, schedule
# should asking if an agenda is read-only require any kind of permission?
def agenda_permission_api(request, num, owner, name):
meeting = get_meeting(num)
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
save_perm = False
secretariat = False
write_perm = False
cansee = False
canedit = False
owner_href = ""
cansee,canedit = agenda_permissions(meeting, schedule, request.user)
read_only = not canedit
if schedule is not None:
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
owner_href = request.build_absolute_uri(schedule.owner.json_url())
user = request.user
if has_role(user, "Secretariat"):
secretariat = True
write_perm = True
if has_role(request.user, "Area Director") or secretariat:
save_perm = True
if has_role(user, "Area Director"):
write_perm = True
if user_is_person(user, schedule.owner):
read_only = False
# FIXME: the naming here needs improvement, one can have
# read_only == True and write_perm == True?
return json.dumps(
{'secretariat': secretariat,
'write_perm': write_perm,
'owner_href': request.build_absolute_uri(schedule.owner.json_url()),
'read_only': read_only})
@dajaxice_register
def update_timeslot_pinned(request, schedule_id, scheduledsession_id, pinned=False):
if not has_role(request.user,('Area Director','Secretariat')):
return json.dumps({'error':'no permission'})
schedule = get_object_or_404(Schedule, pk = int(schedule_id))
meeting = schedule.meeting
cansee,canedit = agenda_permissions(meeting, schedule, request.user)
if not canedit:
return json.dumps({'error':'no permission'})
if scheduledsession_id is not None:
ss_id = int(scheduledsession_id)
if ss_id == 0:
return json.dumps({'error':'no permission'})
ss = get_object_or_404(schedule.scheduledsession_set, pk=ss_id)
ss.pinned = pinned
ss.save()
return json.dumps({'message':'valid'})
@dajaxice_register
def update_timeslot_purpose(request,
meeting_num,
timeslot_id=None,
purpose =None,
room_id = None,
duration= None,
time = None):
if not has_role(request.user,'Secretariat'):
return json.dumps({'error':'no permission'})
meeting = get_meeting(meeting_num)
ts_id = int(timeslot_id)
time_str = time
if ts_id == 0:
try:
time = datetime.datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')
except:
return json.dumps({'error':'invalid time: %s' % (time_str)})
try:
room = meeting.room_set.get(pk = int(room_id))
except Room.DoesNotExist:
return json.dumps({'error':'invalid room id'})
timeslot = TimeSlot(meeting=meeting,
location = room,
time = time,
duration = duration)
else:
try:
timeslot = TimeSlot.objects.get(pk=ts_id)
except:
return json.dumps({'error':'invalid timeslot'})
try:
timeslottypename = TimeSlotTypeName.objects.get(pk = purpose)
except:
return json.dumps({'error':'invalid timeslot type',
'extra': purpose})
timeslot.type = timeslottypename
try:
timeslot.save()
except:
return json.dumps({'error':'failed to save'})
try:
# really should return 201 created, but dajaxice sucks.
json_dict = timeslot.json_dict(request.build_absolute_uri('/'))
return json.dumps(json_dict)
except:
return json.dumps({'error':'failed to save'})
return HttpResponse(json.dumps({'secretariat': secretariat,
'save_perm': save_perm,
'read_only': canedit==False,
'owner_href': owner_href}),
content_type="application/json")
#############################################################################
## ROOM API
@ -239,16 +175,43 @@ def timeslot_addslot(request, meeting):
newslot.meeting = meeting
newslot.save()
newslot.create_concurrent_timeslots()
# no longer create concurrent timeslots, because they will default, when there is
# no timeslots, to unavailable, which can be created later on.
# newslot.create_concurrent_timeslots()
# XXX FIXME: timeslot_dayurl is undefined. Placeholder:
timeslot_dayurl = None
# timeslot_dayurl = None
# XXX FIXME: newroom is undefined. Placeholder:
newroom = None
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
return redirect(timeslot_dayurl, meeting.number, newroom.pk)
# newroom = None
values = newslot.json_dict(request.build_absolute_uri('/'))
response = HttpResponse(json.dumps(values),
content_type="application/json",
status=201)
response['Location'] = values['href']
return response
@role_required('Secretariat')
def timeslot_updslot(request, meeting, slotid):
slot = get_object_or_404(meeting.timeslot_set, pk=slotid)
# at present, updates to the purpose only is supported.
# updates to time or duration would need likely need to be
# propogated to the entire vertical part of the grid, and nothing
# needs to do that yet.
if request.method == 'POST':
put_vars = request.POST
slot.type_id = put_vars["purpose"]
else:
return redirect(edit_timeslots, meeting.number)
put_vars = QueryDict(request.body)
slot.type_id = put_vars.get("purpose")
slot.save()
# need to return the new object.
dict1 = slot.json_dict(request.build_absolute_uri('/'))
dict1['message'] = 'valid'
return HttpResponse(json.dumps(dict1),
content_type="application/json")
@role_required('Secretariat')
def timeslot_delslot(request, meeting, slotid):
@ -276,10 +239,8 @@ def timeslot_sloturl(request, num=None, slotid=None):
slot = get_object_or_404(meeting.timeslot_set, pk=slotid)
return HttpResponse(json.dumps(slot.json_dict(request.build_absolute_uri('/'))),
content_type="application/json")
elif request.method == 'POST':
# not yet implemented!
#return timeslot_updslot(request, meeting)
return HttpResponse(status=406)
elif request.method == 'POST' or request.method == 'PUT':
return timeslot_updslot(request, meeting, slotid)
elif request.method == 'DELETE':
return timeslot_delslot(request, meeting, slotid)
@ -311,9 +272,9 @@ def agenda_add(request, meeting):
newagenda.save()
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
return redirect(agenda_infourl, meeting.number, newagenda.name)
return redirect(agenda_infourl, meeting.number, newagenda.owner_email(), newagenda.name)
else:
return redirect(edit_agenda, meeting.number, newagenda.name)
return redirect(edit_agenda, meeting.number, newagenda.owner_email(), newagenda.name)
@require_POST
def agenda_update(request, meeting, schedule):
@ -325,16 +286,13 @@ def agenda_update(request, meeting, schedule):
if not user.is_authenticated():
return HttpResponse({'error':'no permission'}, status=403)
cansee,canedit = agenda_permissions(meeting, schedule, request.user)
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
#read_only = not canedit ## not used
def is_truthy_enough(value):
return not (value == "0" or value == 0 or value=="false")
# TODO: Secretariat should always get canedit
if not (canedit or has_role(user, "Secretariat")):
if not (canedit or secretariat):
return HttpResponse({'error':'no permission'}, status=403)
if "public" in request.POST:
schedule.public = is_truthy_enough(request.POST["public"])
@ -355,7 +313,7 @@ def agenda_update(request, meeting, schedule):
return HttpResponse(json.dumps(schedule.json_dict(request.build_absolute_uri('/'))),
content_type="application/json")
else:
return redirect(edit_agenda, meeting.number, schedule.name)
return redirect(edit_agenda, meeting.number, schedule.owner_email(), schedule.name)
@role_required('Secretariat')
def agenda_del(request, meeting, schedule):
@ -378,11 +336,13 @@ def agenda_infosurl(request, num=None):
# unacceptable action
return HttpResponse(status=406)
def agenda_infourl(request, num=None, name=None):
meeting = get_meeting(num)
#log.debug("agenda: %s / %s" % (meeting, name))
def agenda_infourl(request, num=None, owner=None, name=None):
meeting = get_meeting(num)
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
if schedule is None:
raise Http404("No meeting information for meeting %s schedule %s available" % (num,name))
schedule = get_schedule(meeting, name)
#debug.log("results in agenda: %u / %s" % (schedule.id, request.method))
if request.method == 'GET':
@ -469,8 +429,9 @@ def sessions_json(request, num):
## Scheduledsesion
#############################################################################
# this creates an entirely *NEW* scheduledsession
def scheduledsessions_post(request, meeting, schedule):
cansee,canedit = agenda_permissions(meeting, schedule, request.user)
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
if not canedit:
return HttpResponse(json.dumps({'error':'no permission to modify this agenda'}),
status = 403,
@ -495,7 +456,6 @@ def scheduledsessions_post(request, meeting, schedule):
return HttpResponse(json.dumps({'error':'invalid extendedfrom value: %u' % val}),
status = 406,
content_type="application/json")
ss1.save()
ss1_dict = ss1.json_dict(request.build_absolute_uri('/'))
response = HttpResponse(json.dumps(ss1_dict),
@ -513,9 +473,8 @@ def scheduledsessions_get(request, num, schedule):
content_type="application/json")
# this returns the list of scheduled sessions for the given named agenda
def scheduledsessions_json(request, num, name):
meeting = get_meeting(num)
schedule = get_schedule(meeting, name)
def scheduledsessions_json(request, num, owner, name):
meeting, person, schedule = get_meeting_schedule(num, owner, name)
if request.method == 'GET':
return scheduledsessions_get(request, meeting, schedule)
@ -526,28 +485,38 @@ def scheduledsessions_json(request, num, name):
status = 406,
content_type="application/json")
def scheduledsession_update(request, meeting, schedule, scheduledsession_id):
cansee,canedit = agenda_permissions(meeting, schedule, request.user)
if not canedit or True:
return HttpResponse(json.dumps({'error':'no permission to update this agenda'}),
status = 403,
content_type="application/json")
def scheduledsession_delete(request, meeting, schedule, scheduledsession_id):
cansee,canedit = agenda_permissions(meeting, schedule, request.user)
# accepts both POST and PUT in order to implement Postel Doctrine.
def scheduledsession_update(request, meeting, schedule, ss):
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
if not canedit:
return HttpResponse(json.dumps({'error':'no permission to update this agenda'}),
status = 403,
content_type="application/json")
scheduledsessions = schedule.scheduledsession_set.filter(pk = scheduledsession_id)
if request.method == 'POST':
put_vars = request.POST
ss.pinned = is_truthy_enough(put_vars["pinned"])
else:
put_vars = QueryDict(request.body)
ss.pinned = is_truthy_enough(put_vars.get("pinned"))
ss.save()
return HttpResponse(json.dumps({'message':'valid'}),
content_type="application/json")
def scheduledsession_delete(request, meeting, schedule, ss):
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
if not canedit:
return HttpResponse(json.dumps({'error':'no permission to update this agenda'}),
status = 403,
content_type="application/json")
# in case there is, somehow, more than one item with the same pk.. XXX
scheduledsessions = schedule.scheduledsession_set.filter(pk = ss.pk)
if len(scheduledsessions) == 0:
return HttpResponse(json.dumps({'error':'no such object'}),
status = 404,
content_type="application/json")
count=0
for ss in scheduledsessions:
ss.delete()
@ -557,37 +526,40 @@ def scheduledsession_delete(request, meeting, schedule, scheduledsession_id):
status = 200,
content_type="application/json")
def scheduledsession_get(request, meeting, schedule, scheduledsession_id):
cansee,canedit = agenda_permissions(meeting, schedule, request.user)
def scheduledsession_get(request, meeting, schedule, ss):
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
if not cansee:
return HttpResponse(json.dumps({'error':'no permission to see this agenda'}),
status = 403,
content_type="application/json")
scheduledsessions = schedule.scheduledsession_set.filter(pk = scheduledsession_id)
if len(scheduledsessions) == 0:
return HttpResponse(json.dumps({'error':'no such object'}),
status = 404,
content_type="application/json")
sess1_dict = scheduledsessions[0].json_dict(request.build_absolute_uri('/'))
sess1_dict = ss.json_dict(request.build_absolute_uri('/'))
return HttpResponse(json.dumps(sess1_dict, sort_keys=True, indent=2),
content_type="application/json")
# this returns the list of scheduled sessions for the given named agenda
def scheduledsession_json(request, num, name, scheduledsession_id):
meeting = get_meeting(num)
schedule = get_schedule(meeting, name)
# this return a specific session, updates a session or deletes a SPECIFIC scheduled session
def scheduledsession_json(request, num, owner, name, scheduledsession_id):
meeting, person, schedule = get_meeting_schedule(num, owner, name)
scheduledsession_id = int(scheduledsession_id)
scheduledsessions = schedule.scheduledsession_set.filter(pk = scheduledsession_id)
if len(scheduledsessions) == 0:
return HttpResponse(json.dumps({'error' : 'invalid scheduledsession'}),
content_type="application/json",
status=404);
ss = scheduledsessions[0]
if request.method == 'GET':
return scheduledsession_get(request, meeting, schedule, scheduledsession_id)
elif request.method == 'PUT':
return scheduledsession_update(request, meeting, schedule, scheduledsession_id)
return scheduledsession_get(request, meeting, schedule, ss)
elif request.method == 'PUT' or request.method=='POST':
return scheduledsession_update(request, meeting, schedule, ss)
elif request.method == 'DELETE':
return scheduledsession_delete(request, meeting, schedule, scheduledsession_id)
return scheduledsession_delete(request, meeting, schedule, ss)
#############################################################################
## Constraints API
#############################################################################
# Would like to cache for 1 day, but there are invalidation issues.
#@cache_page(86400)

View file

@ -14,6 +14,7 @@ import debug # pyflakes:ignore
from ietf.group.models import Group
from ietf.ietfauth.utils import has_role, user_is_person
from ietf.person.models import Person
from ietf.meeting.models import Meeting
from ietf.utils.history import find_history_active_at
@ -125,6 +126,19 @@ def get_schedule_by_id(meeting, schedid):
schedule = get_object_or_404(meeting.schedule_set, id=int(schedid))
return schedule
# seems this belongs in ietf/person/utils.py?
def get_person_by_email(email):
# email == None may actually match people who haven't set an email!
if email is None:
return None
return Person.objects.filter(email__address=email).distinct().first()
def get_schedule_by_name(meeting, owner, name):
if owner is not None:
return meeting.schedule_set.filter(owner = owner, name = name).first()
else:
return meeting.schedule_set.filter(name = name).first()
def meeting_updated(meeting):
meeting_time = datetime.datetime(*(meeting.date.timetuple()[:7]))
ts = max(meeting.timeslot_set.aggregate(Max('modified'))["modified__max"] or meeting_time,
@ -137,12 +151,14 @@ def agenda_permissions(meeting, schedule, user):
# do this in positive logic.
cansee = False
canedit = False
secretariat = False
if schedule.public:
if has_role(user, 'Secretariat'):
cansee = True
elif has_role(user, 'Secretariat'):
secretariat = True
# NOTE: secretariat is not superuser for edit!
elif schedule.public:
cansee = True
# secretariat is not superuser for edit!
elif schedule.visible and has_role(user, ['Area Director', 'IAB Chair', 'IRTF Chair']):
cansee = True
@ -150,7 +166,7 @@ def agenda_permissions(meeting, schedule, user):
cansee = True
canedit = True
return cansee, canedit
return cansee, canedit, secretariat
def session_constraint_expire(session):
from django.core.urlresolvers import reverse

View file

@ -456,7 +456,7 @@ class Schedule(models.Model):
return u"%s:%s(%s)" % (self.meeting, self.name, self.owner)
def base_url(self):
return "/meeting/%s/agenda/%s" % (self.meeting.number, self.name)
return "/meeting/%s/agenda/%s/%s" % (self.meeting.number, self.owner_email(), self.name)
# temporary property to pacify the places where Schedule.scheduledsession_set is used
@property
@ -674,8 +674,9 @@ class ScheduledSession(models.Model):
return ""
def json_url(self):
return "/meeting/%s/schedule/%s/session/%u.json" % (self.schedule.meeting.number,
self.schedule.name, self.id)
return "/meeting/%s/agenda/%s/%s/session/%u.json" % (self.schedule.meeting.number,
self.schedule.owner_email(),
self.schedule.name, self.id)
def json_dict(self, host_scheme):
ss = dict()

View file

@ -13,13 +13,6 @@ from ietf.utils.mail import outbox
class ApiTests(TestCase):
def test_dajaxice_core_js(self):
# this is vital for Dajaxice to work and we have hacked it
# slightly to avoid copying static files around, so make sure
# we can fetch it
r = self.client.get("/dajaxice/dajaxice.core.js")
self.assertEqual(r.status_code, 200)
def test_update_agenda(self):
meeting = make_meeting_test_data()
schedule = Schedule.objects.get(meeting__number=42,name="test-agenda")
@ -35,6 +28,7 @@ class ApiTests(TestCase):
def do_unschedule(scheduledsession):
url = urlreverse("ietf.meeting.ajax.scheduledsession_json",
kwargs=dict(num=scheduledsession.session.meeting.number,
owner=scheduledsession.schedule.owner_email(),
name=scheduledsession.schedule.name,
scheduledsession_id=scheduledsession.pk,))
return self.client.delete(url)
@ -42,6 +36,7 @@ class ApiTests(TestCase):
def do_schedule(schedule,session,timeslot):
url = urlreverse("ietf.meeting.ajax.scheduledsessions_json",
kwargs=dict(num=session.meeting.number,
owner=schedule.owner_email(),
name=schedule.name,))
post_data = '{ "session_id": "%s", "timeslot_id": "%s" }'%(session.pk,timeslot.pk)
return self.client.post(url,post_data,content_type='application/x-www-form-urlencoded')
@ -50,6 +45,7 @@ class ApiTests(TestCase):
session = scheduledsession.session
url = urlreverse("ietf.meeting.ajax.scheduledsessions_json",
kwargs=dict(num=session.meeting.number,
owner=schedule.owner_email(),
name=schedule.name,))
post_data = '{ "session_id": "%s", "timeslot_id": "%s", "extendedfrom_id": "%s" }'%(session.pk,scheduledsession.timeslot.slot_to_the_right.pk,scheduledsession.pk)
@ -208,7 +204,8 @@ class ApiTests(TestCase):
self.assertEqual(set([x['short_name'] for x in info]),set(['mars','ames']))
schedule = meeting.agenda
url = urlreverse("ietf.meeting.ajax.scheduledsessions_json",kwargs=dict(num=meeting.number,name=schedule.name))
url = urlreverse("ietf.meeting.ajax.scheduledsessions_json",
kwargs=dict(num=meeting.number,owner=schedule.owner_email(),name=schedule.name))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
info = json.loads(r.content)
@ -249,7 +246,7 @@ class ApiTests(TestCase):
# create slot
self.client.login(username="secretary", password="secretary+password")
r = self.client.post(url, post_data)
self.assertEqual(r.status_code, 302)
self.assertEqual(r.status_code, 201)
self.assertTrue(meeting.timeslot_set.filter(time=slot_time))
self.assertEqual(meeting.timeslot_set.count(),prior_slotcount+1)
@ -274,7 +271,9 @@ class ApiTests(TestCase):
meeting = make_meeting_test_data()
url = urlreverse("ietf.meeting.ajax.agenda_infourl",
kwargs=dict(num=meeting.number, name=meeting.agenda.name))
kwargs=dict(num=meeting.number,
owner=meeting.agenda.owner_email(),
name=meeting.agenda.name))
r = self.client.get(url)
info = json.loads(r.content)
@ -308,6 +307,7 @@ class ApiTests(TestCase):
url = urlreverse("ietf.meeting.ajax.agenda_infourl",
kwargs=dict(num=meeting.number,
owner=meeting.agenda.owner_email(),
name=meeting.agenda.name))
post_data = {
@ -336,6 +336,7 @@ class ApiTests(TestCase):
url = urlreverse("ietf.meeting.ajax.agenda_infourl",
kwargs=dict(num=meeting.number,
owner=meeting.agenda.owner_email(),
name=meeting.agenda.name))
# unauthorized delete
self.client.login(username="plain", password="plain+password")
@ -393,115 +394,84 @@ class ApiTests(TestCase):
def test_read_only(self):
meeting = make_meeting_test_data()
data = {
'argv': json.dumps({
"meeting_num": meeting.number,
"schedule_id": meeting.agenda.pk,
})}
# Secretariat
self.client.login(username="secretary", password="secretary+password")
r = self.client.post('/dajaxice/ietf.meeting.readonly/', data)
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.agenda.owner.email_address(), meeting.agenda.name);
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
info = json.loads(r.content)
self.assertEqual(info['secretariat'], True)
self.assertEqual(urlsplit(info['owner_href'])[2], "/person/%s.json" % meeting.agenda.owner_id)
self.assertEqual(info['read_only'], True)
self.assertEqual(info['write_perm'], True)
self.assertEqual(info['save_perm'], True)
# owner
self.client.login(username=meeting.agenda.owner.user.username,
password=meeting.agenda.owner.user.username+"+password")
r = self.client.post('/dajaxice/ietf.meeting.readonly/', data)
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.agenda.owner.email_address(), meeting.agenda.name);
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
info = json.loads(r.content)
self.assertEqual(info['secretariat'], False)
self.assertEqual(info['read_only'], False)
self.assertEqual(info['write_perm'], False)
self.assertEqual(info['save_perm'], False)
def test_update_timeslot_pinned(self):
meeting = make_meeting_test_data()
scheduled = ScheduledSession.objects.filter(
session__meeting=meeting, session__group__acronym="mars").first()
url = '/dajaxice/ietf.meeting.update_timeslot_pinned/'
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.agenda.owner_email(), meeting.agenda.name, scheduled.pk)
post_data = {
'argv': json.dumps({
"schedule_id": meeting.agenda.pk,
"scheduledsession_id": scheduled.pk,
"pinned": True,
})}
"pinned": True
}
# unauthorized post
r = self.client.post(url, post_data)
self.assertEqual(r.status_code, 200)
self.assertTrue("error" in json.loads(r.content))
# unauthorized post gets failure (no redirect)
r = self.client.put(url, post_data)
self.assertEqual(r.status_code, 403,
"post to %s should have failed, no permission, got: %u/%s" %
(url, r.status_code, r.content))
self.assertTrue(not ScheduledSession.objects.get(pk=scheduled.pk).pinned)
# set pinned
meeting.agenda.owner = Person.objects.get(user__username="secretary")
meeting.agenda.save()
# need to rebuild URL, since the agenda owner has changed.
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.agenda.owner_email(), meeting.agenda.name, scheduled.pk)
self.client.login(username="secretary", password="secretary+password")
r = self.client.post(url, post_data)
self.assertEqual(r.status_code, 200)
r = self.client.put(url, post_data)
self.assertEqual(r.status_code, 200,
"post to %s should have worked, but got: %u/%s" %
(url, r.status_code, r.content))
self.assertTrue(ScheduledSession.objects.get(pk=scheduled.pk).pinned)
class UnusedButExposedApiTests(TestCase):
class TimeSlotEditingApiTests(TestCase):
def test_manipulate_timeslot_via_dajaxice(self):
def test_manipulate_timeslot(self):
meeting = make_meeting_test_data()
slot_time = datetime.date.today()
slot = meeting.timeslot_set.all()[0]
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type.name,'Session')
url = '/dajaxice/ietf.meeting.update_timeslot_purpose/'
create_post_data = {
'argv' : json.dumps({
"meeting_num" : meeting.number,
"timeslot_id" : 0,
"purpose" : "plenary",
"room_id" : meeting.room_set.first().id,
"time" : slot_time.strftime("%Y-%m-%d %H:%M:%S"),
"duration" : 3600
})}
prior_timeslot_count = meeting.timeslot_set.count()
# Create as nobody should fail
r = self.client.post(url, create_post_data)
self.assertEqual(r.status_code, 200)
info = json.loads(r.content)
self.assertTrue('error' in info and info['error']=='no permission')
self.assertEqual(meeting.timeslot_set.count(),prior_timeslot_count)
# Successful create
self.client.login(username="secretary", password="secretary+password")
r = self.client.post(url, create_post_data)
self.assertEqual(r.status_code, 200)
info = json.loads(r.content)
self.assertFalse('error' in info)
self.assertTrue('roomtype' in info)
self.assertEqual(info['roomtype'],'plenary')
self.assertEqual(meeting.timeslot_set.count(),prior_timeslot_count+1)
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
kwargs=dict(num=meeting.number, slotid=slot.pk))
modify_post_data = {
'argv' : json.dumps({
"meeting_num" : meeting.number,
"timeslot_id" : meeting.timeslot_set.get(time=slot_time).id,
"purpose" : "session"
})}
"purpose" : "plenary"
}
# Fail as non-secretariat
self.client.login(username="plain", password="plain+password")
r = self.client.post(url, modify_post_data)
self.assertEqual(r.status_code, 200)
info = json.loads(r.content)
self.assertTrue('error' in info and info['error']=='no permission')
self.assertEqual(meeting.timeslot_set.get(time=slot_time).type.name,'Plenary')
self.assertEqual(r.status_code, 403)
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type.name,'Session')
# Successful change of purpose
self.client.login(username="secretary", password="secretary+password")
r = self.client.post(url, modify_post_data)
self.assertEqual(r.status_code, 200)
self.assertEqual(meeting.timeslot_set.get(time=slot_time).type.name,'Session')
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type.name,'Plenary')

View file

@ -55,7 +55,7 @@ class ScheduleEditTests(LiveServerTestCase):
self.assertEqual(ScheduledSession.objects.filter(session__meeting__number=42,session__group__acronym='mars').count(),1)
self.login()
url = self.absreverse('ietf.meeting.views.edit_agenda',kwargs=dict(num='42',name='test-agenda'))
url = self.absreverse('ietf.meeting.views.edit_agenda',kwargs=dict(num='42',name='test-agenda',owner='plain@example.com'))
self.driver.get(url)
q = PyQuery(self.driver.page_source)

View file

@ -163,12 +163,16 @@ class EditTests(TestCase):
meeting = make_meeting_test_data()
# try to get non-existing agenda
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number, name="foo"))
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number,
owner=meeting.agenda.owner_email(),
name="foo"))
r = self.client.get(url)
self.assertEqual(r.status_code, 404)
# save as
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number))
# save as new name (requires valid existing agenda)
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number,
owner=meeting.agenda.owner_email(),
name=meeting.agenda.name))
self.client.login(username="ad", password="ad+password")
r = self.client.post(url, {
'savename': "foo",
@ -177,11 +181,13 @@ class EditTests(TestCase):
self.assertEqual(r.status_code, 302)
# get
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number, name="foo"))
schedule = meeting.get_schedule_by_name("foo")
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number,
owner=schedule.owner_email(),
name="foo"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
schedule = meeting.get_schedule_by_name("foo")
schedule.visible = True
schedule.public = False
schedule.save()

View file

@ -20,14 +20,16 @@ urlpatterns = patterns('',
(r'^agenda.ics$', views.ical_agenda),
(r'^agenda/week-view.html$', views.week_view),
(r'^week-view.html$', views.week_view),
(r'^(?P<num>\d+)/schedule/edit$', views.edit_agenda),
(r'^(?P<num>\d+)/schedule/(?P<name>[A-Za-z0-9-:_]+)/edit$', views.edit_agenda),
(r'^(?P<num>\d+)/schedule/(?P<name>[A-Za-z0-9-:_]+)/details$', views.edit_agenda_properties),
(r'^(?P<num>\d+)/schedule/(?P<name>[A-Za-z0-9-:_]+)(?P<ext>.html)?/?$', views.agenda),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+)/edit$', views.edit_agenda),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+)/details$', views.edit_agenda_properties),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+).(?P<ext>.html)?/?$', views.agenda),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+)/permissions$', ajax.agenda_permission_api),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+)/session/(?P<scheduledsession_id>\d+).json$', ajax.scheduledsession_json),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+)/sessions.json$', ajax.scheduledsessions_json),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+).json$', ajax.agenda_infourl),
(r'^(?P<num>\d+)/agenda/edit$', views.edit_agenda),
(r'^(?P<num>\d+)/agenda(?P<ext>.html)?/?$', views.agenda),
(r'^(?P<num>\d+)/(?P<base>agenda-utc)(?P<ext>.html)?/?$', views.agenda),
(r'^(?P<num>\d+)/schedule/(?P<name>[A-Za-z0-9-:_]+)/sessions.json$', ajax.scheduledsessions_json),
(r'^(?P<num>\d+)/schedule/(?P<name>[A-Za-z0-9-:_]+)/session/(?P<scheduledsession_id>\d+).json$', ajax.scheduledsession_json),
(r'^(?P<num>\d+)/requests.html$', RedirectView.as_view(url='/meeting/%(num)s/requests', permanent=True)),
(r'^(?P<num>\d+)/requests$', views.meeting_requests),
(r'^(?P<num>\d+)/agenda(?P<ext>.txt)$', views.agenda),
@ -41,9 +43,8 @@ urlpatterns = patterns('',
(r'^(?P<num>\d+)/timeslots$', ajax.timeslot_slotsurl),
(r'^(?P<num>\d+)/timeslots.json$', ajax.timeslot_slotsurl),
(r'^(?P<num>\d+)/timeslot/(?P<slotid>\d+).json$', ajax.timeslot_sloturl),
(r'^(?P<num>\d+)/agendas/(?P<name>[A-Za-z0-9-:_]+).json$', ajax.agenda_infourl),
(r'^(?P<num>\d+)/agendas$', ajax.agenda_infosurl),
(r'^(?P<num>\d+)/agendas.json$', ajax.agenda_infosurl),
(r'^(?P<num>\d+)/agendas$', ajax.agenda_infosurl),
(r'^(?P<num>\d+)/agendas.json$', ajax.agenda_infosurl),
(r'^(?P<num>\d+)/week-view.html$', views.week_view),
(r'^(?P<num>\d+)/agenda/week-view.html$', views.week_view),
(r'^(?P<num>\d+)/agenda/(?P<session>[A-Za-z0-9-]+)-drafts.pdf$', views.session_draft_pdf),

View file

@ -26,7 +26,7 @@ from ietf.doc.models import Document, State
from ietf.group.models import Group
from ietf.ietfauth.utils import role_required, has_role
from ietf.meeting.models import Meeting, TimeSlot, Session, Schedule, Room
from ietf.meeting.helpers import get_areas
from ietf.meeting.helpers import get_areas, get_person_by_email, get_schedule_by_name
from ietf.meeting.helpers import build_all_agenda_slices, get_wg_name_list
from ietf.meeting.helpers import get_all_scheduledsessions_from_schedule
from ietf.meeting.helpers import get_modified_from_scheduledsessions
@ -92,13 +92,14 @@ class SaveAsForm(forms.Form):
savename = forms.CharField(max_length=100)
@role_required('Area Director','Secretariat')
def agenda_create(request, num=None, name=None):
meeting = get_meeting(num)
schedule = get_schedule(meeting, name)
def agenda_create(request, num=None, owner=None, name=None):
meeting = get_meeting(num)
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
if schedule is None:
# here we have to return some ajax to display an error.
raise Http404("No meeting information for meeting %s schedule %s available" % (num,name))
raise Http404("No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name))
# authorization was enforced by the @group_require decorator above.
@ -152,7 +153,7 @@ def agenda_create(request, num=None, name=None):
# now redirect to this new schedule.
return redirect(edit_agenda, meeting.number, newschedule.name)
return redirect(edit_agenda, meeting.number, newschedule.owner_email(), newschedule.name)
@role_required('Secretariat')
@ -225,14 +226,20 @@ def edit_roomurl(request, num, roomid):
#@role_required('Area Director','Secretariat')
# disable the above security for now, check it below.
@ensure_csrf_cookie
def edit_agenda(request, num=None, name=None):
def edit_agenda(request, num=None, owner=None, name=None):
if request.method == 'POST':
return agenda_create(request, num, name)
return agenda_create(request, num, owner, name)
user = request.user
meeting = get_meeting(num)
schedule = get_schedule(meeting, name)
user = request.user
meeting = get_meeting(num)
person = get_person_by_email(owner)
if name is None:
schedule = meeting.agenda
else:
schedule = get_schedule_by_name(meeting, person, name)
if schedule is None:
raise Http404("No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name))
meeting_base_url = request.build_absolute_uri(meeting.base_url())
site_base_url = request.build_absolute_uri('/')[:-1] # skip the trailing slash
@ -241,9 +248,9 @@ def edit_agenda(request, num=None, name=None):
rooms = rooms.all()
saveas = SaveAsForm()
saveasurl=reverse(edit_agenda,
args=[meeting.number, schedule.name])
args=[meeting.number, schedule.owner_email(), schedule.name])
can_see, can_edit = agenda_permissions(meeting, schedule, user)
can_see, can_edit,secretariat = agenda_permissions(meeting, schedule, user)
if not can_see:
return HttpResponse(render_to_string("meeting/private_agenda.html",
@ -294,13 +301,15 @@ AgendaPropertiesForm = modelform_factory(Schedule, fields=('name','visible', 'pu
@role_required('Area Director','Secretariat')
@ensure_csrf_cookie
def edit_agenda_properties(request, num=None, name=None):
meeting = get_meeting(num)
schedule = get_schedule(meeting, name)
def edit_agenda_properties(request, num=None, owner=None, name=None):
meeting = get_meeting(num)
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
if schedule is None:
raise Http404("No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name))
form = AgendaPropertiesForm(instance=schedule)
cansee, canedit = agenda_permissions(meeting, schedule, request.user)
cansee, canedit, secretariat = agenda_permissions(meeting, schedule, request.user)
if not (canedit or has_role(request.user,'Secretariat')):
return HttpResponseForbidden("You may not edit this agenda")
@ -320,7 +329,7 @@ def edit_agenda_properties(request, num=None, name=None):
def edit_agendas(request, num=None, order=None):
#if request.method == 'POST':
# return agenda_create(request, num, name)
# return agenda_create(request, num, owner, name)
meeting = get_meeting(num)
user = request.user

View file

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
orm.Email.objects.filter(person__name='(System)').update(address='system@datatracker.ietf.org')
def backwards(self, orm):
orm.Email.objects.filter(person__name='(System)').update(address='(System)')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'person.alias': {
'Meta': {'object_name': 'Alias'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"})
},
u'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
u'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
},
u'person.personhistory': {
'Meta': {'object_name': 'PersonHistory'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"orm['person.Person']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['person']
symmetrical = True

View file

@ -94,8 +94,6 @@ STATIC_ROOT = os.path.abspath(BASE_DIR + "/../static/")
WSGI_APPLICATION = "ietf.wsgi.application"
DAJAXICE_MEDIA_PREFIX = "dajaxice"
AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', )
#DATABASE_ROUTERS = ["ietf.legacy_router.LegacyRouter"]
@ -175,7 +173,6 @@ ROOT_URLCONF = 'ietf.urls'
TEMPLATE_DIRS = (
BASE_DIR + "/templates",
BASE_DIR + "/secr/templates",
BASE_DIR+"/../django-dajaxice/dajaxice/templates",
)
TEMPLATE_CONTEXT_PROCESSORS = (
@ -236,7 +233,6 @@ INSTALLED_APPS = (
'ietf.secr.sreq',
'ietf.nomcom',
'ietf.dbtemplate',
'dajaxice',
)
INTERNAL_IPS = (

View file

@ -3,8 +3,6 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% load humanize %}
{% load dajaxice_templatetags %}
{% block title %}IETF {{ meeting.number }} Meeting Agenda{% endblock %}
{% load agenda_custom_tags %}
{% block pagehead %}
@ -21,8 +19,6 @@
<script type='text/javascript' src='/js/jquery-ui-1.9.0.custom/jquery.ui.accordion.js'></script>
<script type='text/javascript' src='/js/jquery-ui-1.9.0.custom/jquery.ui.draggable.js'></script>
{% dajaxice_js_import %}
<script type='text/javascript' src='/js/agenda/agenda_listeners.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_helpers.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_property_utils.js'></script>
@ -61,11 +57,11 @@
<td class="agenda_mark agenda_data"> <span class="styled_button agenda_official_mark {{agenda.official_class}}">{{ agenda.official_token }}</span></td>
<td class="agenda_data"> <span class="agenda_owner">{{ agenda.owner }}</span> </td>
<td class="agenda_data">
<a href="{% url "ietf.meeting.views.edit_agenda" agenda.meeting.number agenda.name %}"><span class="agenda_name">{{ agenda.name }}</span></a>
<a href="{% url "ietf.meeting.views.edit_agenda" agenda.meeting.number agenda.owner_email agenda.name %}"><span class="agenda_name">{{ agenda.name }}</span></a>
</td>
<td class="agenda_data"> <span class="agenda_visible {{agenda.visible_class}}">{{ agenda.visible_token }}</span></a></td>
<td class="agenda_data"> <span class="agenda_public {{agenda.public_class}}">{{ agenda.public_token }}</span></td>
<td class="agenda_mark agenda_data"> <a href="{% url "ietf.meeting.views.edit_agenda_properties" agenda.meeting.number agenda.name %}"><span class="styled_button agenda_edit">EDIT</span></a> </td>
<td class="agenda_mark agenda_data"> <a href="{% url "ietf.meeting.views.edit_agenda_properties" agenda.meeting.number agenda.owner_email agenda.name %}"><span class="styled_button agenda_edit">EDIT</span></a> </td>
<td class="agenda_mark agenda_data"> <span class="styled_button agenda_delete">DEL</span> </td>
</tr>
{% endfor %}

View file

@ -3,8 +3,6 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% load humanize %}
{% load dajaxice_templatetags %}
{% block morecss %}
{% for area in area_list %}
.{{ area.upcase_acronym}}-scheme, .meeting_event th.{{ area.upcase_acronym}}-scheme, #{{ area.upcase_acronym }}-groups, #selector-{{ area.upcase_acronym }} { color:{{ area.fg_color }}; background-color: {{ area.bg_color }} }
@ -31,8 +29,6 @@
<script type='text/javascript' src='/js/jquery-ui-1.9.0.custom/minified/jquery.ui.accordion.min.js'></script>
<script type='text/javascript' src='/js/jquery-ui-1.9.0.custom/minified/jquery.ui.draggable.min.js'></script>
{% dajaxice_js_import %}
<script type='text/javascript' src='/js/spin/dist/spin.min.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_edit.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_helpers.js'></script>
@ -48,8 +44,9 @@ var schedule_id = {{ schedule.id }};
var schedule_owner_href = "{{ schedule.owner_email }}";
var schedule_name = "{{ schedule.name }}";
var meeting_base_url = "{{ meeting_base_url }}";
var schedule_owner_email = "{{ schedule.owner_email }}";
var site_base_url = "{{ site_base_url }}";
var scheduledsession_post_href = "{% url "ietf.meeting.ajax.scheduledsessions_json" meeting.number schedule.name %}";
var scheduledsession_post_href = "{% url "ietf.meeting.ajax.scheduledsessions_json" meeting.number schedule.owner_email schedule.name %}";
var total_days = {{time_slices|length}};
var total_rooms = {{rooms|length}};

View file

@ -3,8 +3,6 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% load humanize %}
{% load dajaxice_templatetags %}
{% block morecss %}
{% for area in area_list %}
.{{ area.upcase_acronym}}-scheme, .meeting_event th.{{ area.upcase_acronym}}-scheme, #{{ area.upcase_acronym }}-groups, #selector-{{ area.upcase_acronym }} { color:{{ area.fg_color }}; background-color: {{ area.bg_color }} }

View file

@ -3,8 +3,6 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% load humanize %}
{% load dajaxice_templatetags %}
{% block title %}IETF {{ meeting.number }} Meeting Agenda: {{schedule.owner}} / {{ schedule.name }}{% endblock %}
{% load agenda_custom_tags %}
{% block pagehead %}
@ -21,8 +19,6 @@
<script type='text/javascript' src='/js/jquery-ui-1.9.0.custom/jquery.ui.accordion.js'></script>
<script type='text/javascript' src='/js/jquery-ui-1.9.0.custom/jquery.ui.draggable.js'></script>
{% dajaxice_js_import %}
<script type='text/javascript' src='/js/agenda/agenda_listeners.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_helpers.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_property_utils.js'></script>

View file

@ -3,8 +3,6 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% load humanize %}
{% load dajaxice_templatetags %}
{% block title %}IETF {{ meeting.number }} Meeting Agenda: Timeslot/Room Availability{% endblock %}
{% load agenda_custom_tags %}
{% block pagehead %}
@ -30,8 +28,6 @@
<script type='text/javascript' src='/js/jquery-ui-timepicker/jquery-ui-sliderAccess.js.js'></script>
<link rel='stylesheet' type='text/css' href='/css/jquery-ui-timepicker-addon.css' />
{% dajaxice_js_import %}
<script type='text/javascript' src='/js/spin/dist/spin.min.js'></script>
{% endblock js %}

View file

@ -3,8 +3,6 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% load humanize %}
{% load dajaxice_templatetags %}
{% block title %}IETF {{ meeting.number }} Meeting Agenda: Timeslot/Room Availability{% endblock %}
{% load agenda_custom_tags %}
{% block pagehead %}
@ -30,8 +28,6 @@
<script type='text/javascript' src='/js/jquery-ui-timepicker/jquery-ui-sliderAccess.js.js'></script>
<link rel='stylesheet' type='text/css' href='/css/jquery-ui-timepicker-addon.css' />
{% dajaxice_js_import %}
<script type='text/javascript' src='/js/spin/dist/spin.min.js'></script>
<script type='text/javascript' src='/js/agenda/timeslot_edit.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_objects.js'></script>
@ -41,21 +37,17 @@
<script type='text/javascript'>
{% comment %}
function my_js_callback(data){
alert(data.message);
}<!-- dajaxice example --> {% endcomment %}
var meeting_number = "{{ meeting.number }}";
var meeting_base_url = "{{ meeting_base_url }}";
var site_base_url = "{{ site_base_url }}";
var meeting_slots_href = "{% url "ietf.meeting.ajax.timeslot_slotsurl" meeting.number %}";
total_days = {{time_slices|length}};
total_rooms = {{rooms|length}};
first_day = new Date("{% with timeslots|first as day %} {{ day.time }} {% endwith %}"); /* needed for the datepicker */
function setup_slots(promiselist){
var ts_promise = load_timeslots("{% url "ietf.meeting.ajax.timeslot_slotsurl" meeting.number %}");
var ts_promise = load_timeslots(meeting_slots_href);
promiselist.push(ts_promise);
{% for day in time_slices %}

View file

@ -18,9 +18,6 @@ try:
except KeyError:
pass
from dajaxice.core import dajaxice_autodiscover
dajaxice_autodiscover()
sitemaps = {
'liaison': LiaisonMap,
'ipr': IPRMap,
@ -63,8 +60,6 @@ urlpatterns = patterns('',
# Google webmaster tools verification url
(r'^googlea30ad1dacffb5e5b.html', TemplateView.as_view(template_name='googlea30ad1dacffb5e5b.html')),
(r'^%s/dajaxice.core.js' % settings.DAJAXICE_MEDIA_PREFIX, 'ietf.meeting.ajax.dajaxice_core_js'),
(r'^%s/' % settings.DAJAXICE_MEDIA_PREFIX, include('dajaxice.urls')),
)
if settings.SERVER_MODE in ('development', 'test'):

View file

@ -136,7 +136,7 @@ function read_only_result(msg) {
$("#read_only").css("display", "none");
}
if(msg.write_perm) {
if(msg.save_perm) {
$(".agenda_save_box").css("display", "block");
if(read_only) {
$(".agenda_save_box").css("position", "fixed");
@ -160,23 +160,14 @@ function read_only_result(msg) {
}
function read_only_check() {
Dajaxice.ietf.meeting.readonly(read_only_result,
{'meeting_num': meeting_number,
'schedule_id': schedule_id
});
}
var read_only_url = meeting_base_url + "/agenda/" + schedule_owner_email + "/" + schedule_name + "/permissions";
console.log("Loading readonly status from: ", read_only_url);
var read_only_load = $.ajax(read_only_url);
function dajaxice_callback(message) {
/* if the message is empty, we got nothing back from the server, which probably
means you are offline.
*/
console.log("callback: ",message);
if(message == ""){
alert("No response from server. Network may be unavailable");
}
else{
stop_spin();
}
read_only_load.success(function(newobj, status, jqXHR) {
last_json_reply = newobj;
read_only_result(newobj);
});
}
function print_all_ss(objs){

View file

@ -698,36 +698,6 @@ function info_name_select_change(){
console.log("selecting new item:", last_session.title);
}
function XMLHttpGetRequest(url, sync) {
var oXMLHttpRequest = new XMLHttpRequest;
oXMLHttpRequest.open('GET', url, sync);
oXMLHttpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest");
oXMLHttpRequest.setRequestHeader("X-CSRFToken", Dajaxice.get_cookie('csrftoken'));
return oXMLHttpRequest;
}
function retrieve_session_by_id(session_id) {
var session_obj = {};
var oXMLHttpRequest = XMLHttpGetRequest(meeting_base_url+'/session/'+session_id+".json", false);
oXMLHttpRequest.send();
if(oXMLHttpRequest.readyState == XMLHttpRequest.DONE) {
try{
last_json_txt = oXMLHttpRequest.responseText;
session_obj = JSON.parse(oXMLHttpRequest.responseText);
last_json_reply = session_obj;
}
catch(exception){
console.log("retrieve_session_by_id("+session_id+") exception: "+exception);
}
}
return session_obj;
}
function dajaxice_error(a){
console.log("dajaxice_error");
}
function set_pin_session_button(scheduledsession) {
$("#pin_slot").unbind('click');
if(scheduledsession == undefined) {
@ -736,6 +706,7 @@ function set_pin_session_button(scheduledsession) {
}
state = scheduledsession.pinned;
//console.log("button set to: ",state);
$("#pin_slot").attr('disabled',false);
if(state) {
$("#pin_slot").html("unPin");
$("#agenda_pin_slot").addClass("button_down");
@ -755,25 +726,11 @@ function set_pin_session_button(scheduledsession) {
function update_pin_session(scheduledsession, state) {
start_spin();
console.log("Calling dajaxice", scheduledsession, state);
Dajaxice.ietf.meeting.update_timeslot_pinned(function(message) {
console.log("dajaxice callback");
scheduledsession.set_pinned(state, function(schedulesession) {
stop_spin();
if(message.message != "valid") {
alert("Update of pinned failed");
return;
}
scheduledsession.pinned = state;
session = scheduledsession.session()
session.pinned = state;
session.repopulate_event(scheduledsession.domid());
set_pin_session_button(scheduledsession);
},
{
'schedule_id': schedule_id,
'scheduledsession_id': scheduledsession.scheduledsession_id,
'pinned': state
});
});
}
function enable_button(divid, buttonid, func) {
@ -898,7 +855,7 @@ function draw_constraints(session) {
highlight_conflict(conflict);
}
});
$.each(group_set, function(index) {
group = group_set[index];

View file

@ -423,6 +423,7 @@ function load_timeslots(href) {
// ScheduledSession is DJANGO name for this object, but needs to be renamed.
// It represents a TimeSlot that can be assigned in this schedule.
// { "scheduledsession_id": "{{s.id}}",
// "href: "{{s.href}}",
// "timeslot_id":"{{s.timeslot.id}}",
// "session_id" :"{{s.session.id}}",
// "extendedfrom_id" :refers to another scheduledsession by ss.id
@ -486,6 +487,27 @@ ScheduledSlot.prototype.saveit = function() {
return saveit;
};
ScheduledSlot.prototype.set_pinned = function(state, completefunc) {
var ss = this;
var pinned_struct = { "pinned" : state };
var pinned_update = $.ajax(this.href, {
"content-type": "text/json",
"type": "PUT",
"data": pinned_struct,
});
pinned_update.success(function(result, status, jqXHR) {
if(result.message != "valid") {
alert("Update of pinned failed");
return;
}
ss.pinned = state;
completefunc(this);
});
};
function remove_from_slot_status(domid, ss_id) {
var found_at;
if(agenda_globals.__debug_session_move) {

View file

@ -21,7 +21,7 @@
function delete_agenda(event) {
var agenda_url = $(event.target).closest('tr').attr('href');
var agenda_url = $(event.target).closest('tr').attr('href') + ".json";
event.preventDefault();
$("#agenda_delete_dialog").dialog({

View file

@ -16,33 +16,36 @@
//////////////-GLOBALS----////////////////////////////////////////
/* ////////////-GLOBALS----/////////////////////////////////////// */
var agenda_globals;
var days = [];
var legend_status = {}; // agenda area colors.
var legend_status = {}; /* agenda area colors. */
var duplicate_sessions = {};
/* the following are initialized in the timeslot_edit.html template */
/* var meeting_slots_href = URL to get/post new timeslots. */
/********* colors ************************************/
var highlight = "red"; // when we click something and want to highlight it.
var dragging_color = "blue"; // color when draging events.
var none_color = ''; // unset the color.
var highlight = "red"; /* when we click something and want to highlight it. */
var dragging_color = "blue"; /* color when draging events. */
var none_color = ''; /* unset the color. */
var color_droppable_empty_slot = 'rgb(0, 102, 153)';
// these are used for debugging only.
var last_json_txt = ""; // last txt from a json call.
var last_json_reply = []; // last parsed content
var last_json_txt = ""; /* last txt from a json call. */
var last_json_reply = []; /* last parsed content */
var hidden_rooms = [];
var total_rooms = 0; // the number of rooms
var total_rooms = 0; /* the number of rooms */
var hidden_days = [];
var total_days = 0; // the number of days
var total_days = 0; /* the number of days */
/****************************************************/
/////////////-END-GLOBALS-///////////////////////////////////////
/* ///////////-END-GLOBALS-///////////////////////////////////// */
/* refactor this out into the html */
$(document).ready(function() {
@ -226,23 +229,22 @@ function delete_slot(event) {
}
function fill_timeslots() {
// add no_timeslot class to all timeslots, it will be removed
// when an item is placed into the slot.
/* add no_timeslot class to all timeslots, it will be removed */
/* when an item is placed into the slot. */
$(".agenda_slot").addClass("no_timeslot");
$.each(agenda_globals.timeslot_bydomid, function(key) {
ts = agenda_globals.timeslot_bydomid[key];
insert_timeslotedit_cell(ts);
});
// now add a create option for every slot which hasn't got a timeslot
/* now add a create option for every slot which hasn't got a timeslot */
$.each($(".no_timeslot"),function(slot) {
create_timeslotedit_cell(this);
});
}
function build_select_box(roomtype, domid, slot_id, select_id) {
//console.log("updating for", ts);
/* console.log("updating for", ts); */
roomtypesession="";
roomtypeother="";
roomtypeplenary="";
@ -295,31 +297,33 @@ function insert_timeslotedit_cell(ts) {
var select_id = domid + "_select";
var roomtypeclass = build_select_box(roomtype, domid, slot_id, select_id);
/* console.log("Creating box for old ", select_id); */
$("#"+select_id).off(); /* removes all old events */
$("#"+select_id).change(function(eventObject) {
start_spin();
var newpurpose = $("#"+select_id).val()
console.log("setting id: #"+select_id+" to "+newpurpose+" ("+roomtypeclass+")");
// how does dajaxice relay an error?
Dajaxice.ietf.meeting.update_timeslot_purpose(
function(json) {
if(json == "") {
console.log("No reply from server....");
} else {
stop_spin();
for(var key in json) {
ts[key]=json[key];
}
console.log("server replied, updating cell contents: "+ts.roomtype);
insert_timeslotedit_cell(ts);
}
},
{
'meeting_num': meeting_number,
'timeslot_id': ts.timeslot_id,
'purpose': newpurpose,
});
var purpose_struct = { "purpose" : newpurpose };
var purpose_update = $.ajax(ts.href, {
"content-type": "text/json",
"type": "PUT",
"data": purpose_struct,
});
purpose_update.success(function(result, status, jqXHR) {
if(result.message != "valid") {
alert("Update of pinned failed");
return;
}
stop_spin();
for(var key in result) {
ts[key]=result[key];
}
console.log("server replied, updating cell contents: "+ts.roomtype);
insert_timeslotedit_cell(ts);
});
});
}
@ -335,7 +339,7 @@ function create_timeslotedit_cell(slot_id) {
var duration=object.attr('slot_duration');
var domid= object.attr('id');
//$(slot_id).removeClass("agenda_slot_unavailable")
/* $(slot_id).removeClass("agenda_slot_unavailable") */
$(slot_id).removeClass("agenda_slot_other")
$(slot_id).removeClass("agenda_slot_session")
$(slot_id).removeClass("agenda_slot_plenary")
@ -343,33 +347,41 @@ function create_timeslotedit_cell(slot_id) {
var select_id = domid + "_select";
var roomtypeclass = build_select_box(roomtype, "default", slot_id, select_id);
/* console.log("Creating box for new ", $("#"+select_id)); */
$("#"+select_id).off(); /* removes all old events */
$("#"+select_id).change(function(eventObject) {
start_spin();
var newpurpose = $("#"+select_id).val()
console.log("creating setting id: #"+select_id+" to "+newpurpose+" ("+roomtypeclass+")");
/* console.log("creating new slot id: #"+select_id+" to "+newpurpose+" (was "+roomtypeclass+")"); */
var ts = {
'room_id': room,
'time' : time,
'duration':duration,
/* 'purpose': newpurpose, */
'type': newpurpose,
};
Dajaxice.ietf.meeting.update_timeslot_purpose(
function(json) {
if(json == "") {
console.log("No reply from server....");
} else {
stop_spin();
for(var key in json) {
ts[key]=json[key];
}
console.log("server replied, updating cell contents: "+ts.roomtype);
insert_timeslotedit_cell(ts);
}
},
{
'timeslot_id': "0", /* 0 indicates to make a new one */
'meeting_num': meeting_number,
'room_id': room,
'time' : time,
'duration':duration,
'purpose': newpurpose,
});
var new_timeslot_promise = $.ajax(meeting_slots_href, {
"content-type": "text/json",
"type": "POST",
"data": ts,
});
new_timeslot_promise.success(function(result, status, jqXHR) {
stop_spin();
if(jqXHR.status != 201) {
__debug_object = jqXHR;
alert("creation of new timeslot failed");
return;
}
ts_obj = make_timeslot(result);
/* change the domid of the unavailable slot to that which we just created */
$(slot_id).attr('id', ts_obj.domid);
insert_timeslotedit_cell(ts_obj);
});
});
}

View file

@ -501,8 +501,6 @@ YAHOO.util.Event.onContentReady("wgs", function () {
//]]>
</script>
<script src="/dajaxice/dajaxice.core.js" type="text/javascript" charset="utf-8"></script>
<script type='text/javascript' src='/js/agenda/agenda_edit.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_helpers.js'></script>
<script type='text/javascript' src='/js/agenda/agenda_objects.js'></script>