# HG changeset patch
# User wakimd
# Date 1297421771 -3600
# Node ID b3703b98ce32f39f3f3537fbb20c6d8d3b247788
# Parent a5f517ac1c03df553466ea048eef5406e506498a# Parent 03d02cf0bea7bceadca07af8d842cf591e25ca71
Merge
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/management/commands/__init__.py
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/management/commands/testrunserver.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/management/commands/testrunserver.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,79 @@
+from django.core.management.commands.runserver import Command as RunserverCommand
+from django.core.servers.basehttp import WSGIServer, WSGIRequestHandler
+import sys
+import threading
+from threading import Event
+from django.conf import settings
+
+def run(addr, port, wsgi_handler, keep_running=None, ready_event=None):
+ server_address = (addr, port)
+ httpd = WSGIServer(server_address, WSGIRequestHandler)
+ httpd.set_app(wsgi_handler)
+ if keep_running is None:
+ if ready_event is not None:
+ ready_event.set()
+ httpd.serve_forever()
+ else:
+ if ready_event is not None:
+ ready_event.set()
+ while keep_running():
+ httpd.handle_request()
+
+
+
+class Command(RunserverCommand):
+ def handle(self, addrport='', keep_running=None, ready_event=None, *args, **options):
+ import django
+ from django.core.servers.basehttp import AdminMediaHandler, WSGIServerException
+ from django.core.handlers.wsgi import WSGIHandler
+ if args:
+ raise CommandError('Usage is testrunserver %s' % self.args)
+ if not addrport:
+ addr = ''
+ port = '8000'
+ else:
+ try:
+ addr, port = addrport.split(':')
+ except ValueError:
+ addr, port = '', addrport
+ if not addr:
+ addr = '127.0.0.1'
+
+ if not port.isdigit():
+ raise CommandError("%r is not a valid port number." % port)
+
+ admin_media_path = options.get('admin_media_path', '')
+ shutdown_message = options.get('shutdown_message', '')
+ quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
+
+ from django.conf import settings
+ from django.utils import translation
+ print "Validating models..."
+ self.validate(display_num_errors=True)
+ print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
+ print "Development server is running at http://%s:%s/" % (addr, port)
+ print "Quit the server with %s." % quit_command
+
+ translation.activate(settings.LANGUAGE_CODE)
+
+ try:
+ handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
+ run(addr, int(port), handler, keep_running, ready_event)
+ except WSGIServerException, e:
+ # Use helpful error messages instead of ugly tracebacks.
+ ERRORS = {
+ 13: "You don't have permission to access that port.",
+ 98: "That port is already in use.",
+ 99: "That IP address can't be assigned-to.",
+ }
+ try:
+ error_text = ERRORS[e.args[0].args[0]]
+ except (AttributeError, KeyError):
+ error_text = str(e)
+ sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
+ # Need to use an OS exit because sys.exit doesn't work in a thread
+ except KeyboardInterrupt:
+ if shutdown_message:
+ print shutdown_message
+
+
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/settings.py
--- a/src/ldt/ldt/settings.py Fri Feb 04 18:41:55 2011 +0100
+++ b/src/ldt/ldt/settings.py Fri Feb 11 11:56:11 2011 +0100
@@ -14,6 +14,7 @@
INSTALLED_APPS = (
'jogging',
+ 'django_extensions',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@@ -25,8 +26,10 @@
'ldt',
'ldt.core',
'ldt.ldt_utils',
+ 'ldt.text',
'ldt.user',
'ldt.management',
+ 'oauth_provider',
)
MIDDLEWARE_CLASSES = (
@@ -58,7 +61,7 @@
MEDIA_URL = getattr(settings, 'MEDIA_URL', '')
MEDIA_ROOT = getattr(settings, 'MEDIA_ROOT', '')
SITE_ID = getattr(settings, 'SITE_ID', 1)
-DEBUG = getattr(settings, 'DEBUG', False)
+DEBUG = getattr(settings, 'DEBUG', True)
MANAGERS = settings.MANAGERS
INSTALLED_APPS = settings.INSTALLED_APPS
LANGUAGES = settings.LANGUAGES
@@ -73,6 +76,7 @@
GLOBAL_LOG_LEVEL = LOG_LEVEL
GLOBAL_LOG_HANDLERS = [logging.FileHandler(LOG_FILE)]
+TEST_WEBSERVER_ADDRPORT = getattr(settings, 'TEST_WEBSERVER_ADDRPORT', '127.0.0.1:8000')
ACCOUNT_ACTIVATION_DAYS = getattr(settings, 'ACCOUNT_ACTIVATION_DAYS', 7)
LDT_MEDIA_PREFIX = getattr(settings, 'LDT_MEDIA_PREFIX', MEDIA_URL + 'ldt/')
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/templates/404.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/templates/404.html Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,1 @@
+
Not Found!
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/test/__init__.py
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/test/client.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/test/client.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,502 @@
+#from django.test.client import Client as DClient
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse, SimpleCookie
+from django.test.client import encode_multipart, encode_file, Client, BOUNDARY, \
+ MULTIPART_CONTENT, CONTENT_TYPE_RE
+from django.utils.encoding import smart_str
+from django.utils.http import urlencode
+from ldt.utils import Property
+from oauth2 import Request, Consumer, Token, SignatureMethod_HMAC_SHA1, \
+ generate_nonce, SignatureMethod_PLAINTEXT
+from oauth_provider.consts import OUT_OF_BAND
+from urlparse import urlsplit, urlunsplit, urlparse, urlunparse, parse_qs
+import httplib2
+from django.utils.importlib import import_module
+import logging
+import re
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+class WebClient(object):
+ """
+ A class that can act as a client for testing purposes.
+
+ It allows the user to compose GET and POST requests, and
+ obtain the response that the server gave to those requests.
+ The server Response objects are annotated with the details
+ of the contexts and templates that were rendered during the
+ process of serving the request.
+
+ Client objects are stateful - they will retain cookie (and
+ thus session) details for the lifetime of the Client instance.
+
+ This is not intended as a replacement for Twill/Selenium or
+ the like - it is here to allow testing against the
+ contexts and templates produced by a view, rather than the
+ HTML rendered to the end-user.
+ """
+ def __init__(self, **defaults):
+ self.handler = httplib2.Http()
+ #self.defaults = defaults
+ self.cookies = SimpleCookie()
+ #self.exc_info = None
+ #self.errors = StringIO()
+ self.__baseurltuple = ()
+ self.__login_url = None
+
+ @Property
+ def baseurl():
+
+ def fget(self):
+ return self.__baseurltuple
+
+ def fset(self, value):
+ if isinstance(value, tuple):
+ self.__baseurltuple = value
+ else:
+ self.__baseurltuple = urlsplit(unicode(value))
+
+ return locals()
+
+ @Property
+ def login_url():
+
+ def fget(self):
+ return self.__login_url
+
+ def fset(self, value):
+ self.__login_url = value
+
+ return locals()
+
+
+ def _mergeurl(self, urltuple):
+ res = ["" for i in range(5)]
+ for i in range(min(len(self.baseurl), len(urltuple))):
+ res[i] = self.baseurl[i] or urltuple[i]
+
+ return urlunsplit(res)
+
+ def _process_response(self, response, content):
+ resp = HttpResponse(content=content, status=response.status, content_type=response['content-type'])
+ if 'set-cookie' in response:
+ self.cookies.load(response['set-cookie'])
+ resp.cookies.load(response['set-cookie'])
+
+ resp.client = self
+ resp.raw_response = response
+ for key,value in response.items():
+ resp[key] = value
+
+ return resp
+
+ def _handle_redirects(self, response):
+
+ response.redirect_chain = []
+
+ r = response.raw_response.previous
+ while not r is None:
+ response.redirect_chain.append((r['content-location'],r.status))
+ r = r.previous
+
+ return response
+
+
+ def get(self, path, data={}, follow=False, **extra):
+ """
+ Requests a response from the server using GET.
+ """
+ parsed = list(urlsplit(path))
+ parsed[3] = urlencode(data, doseq=True) or parsed[3]
+
+ fullpath = self._mergeurl(parsed)
+ self.handler.follow_redirects = follow
+
+ headers = {}
+ if len(self.cookies) > 0:
+ headers['Cookie'] = self.cookies.output()
+
+ if extra:
+ headers.update(extra)
+
+ response, content = self.handler.request(fullpath, method="GET", headers=headers)
+
+ resp = self._process_response(response, content)
+
+ if follow:
+ resp = self._handle_redirects(resp)
+ return resp
+
+
+ def post(self, path, data={}, content_type="application/x-www-form-urlencoded",
+ follow=False, **extra):
+ """
+ Requests a response from the server using POST.
+ """
+ if content_type == MULTIPART_CONTENT:
+ post_data = encode_multipart(BOUNDARY, data)
+ elif content_type == "application/x-www-form-urlencoded":
+ post_data = urlencode(data)
+ else:
+ # Encode the content so that the byte representation is correct.
+ match = CONTENT_TYPE_RE.match(content_type)
+ if match:
+ charset = match.group(1)
+ else:
+ charset = settings.DEFAULT_CHARSET
+ post_data = smart_str(data, encoding=charset)
+
+ parsed = list(urlsplit(path))
+ fullpath = self._mergeurl(parsed)
+ self.handler.follow_redirects = follow
+
+ headers = {}
+ headers['Content-type'] = content_type
+ if len(self.cookies) > 0:
+ headers['Cookie'] = self.cookies.output()
+
+ if extra:
+ headers.update(extra)
+
+ response,content = self.handler.request(fullpath, method="POST", headers=headers, body=post_data)
+
+ resp = self._process_response(response, content)
+
+ if follow:
+ resp = self._handle_redirects(response)
+ return resp
+
+ def login(self, **credentials):
+ """
+ Sets the Client to appear as if it has successfully logged into a site.
+
+ Returns True if login is possible; False if the provided credentials
+ are incorrect, or the user is inactive, or if the sessions framework is
+ not available.
+ """
+ resp = self.post(path=self.login_url, data=credentials, follow=False, **{"X-Requested-With" : "XMLHttpRequest"})
+ return resp.status_code == 302
+
+
+ def logout(self):
+ """
+ Removes the authenticated user's cookies and session object.
+
+ Causes the authenticated user to be logged out.
+ """
+# session = import_module(settings.SESSION_ENGINE).SessionStore()
+# session_cookie = self.cookies.get(settings.SESSION_COOKIE_NAME)
+# if session_cookie:
+# session.delete(session_key=session_cookie.value)
+ self.cookies = SimpleCookie()
+
+
+ def head(self, path, data={}, follow=False, **extra):
+ """
+ Request a response from the server using HEAD.
+ """
+ parsed = list(urlsplit(path))
+ parsed[3] = urlencode(data, doseq=True) or parsed[3]
+
+ fullpath = self._mergeurl(parsed)
+ self.handler.follow_redirects = follow
+
+ headers = {}
+ if len(self.cookies) > 0:
+ headers['Cookie'] = self.cookies.output()
+
+ if extra:
+ headers.update(extra)
+
+ response, content = self.handler.request(fullpath, method="HEAD", headers=headers)
+
+ resp = self._process_response(response, content)
+
+ if follow:
+ resp = self._handle_redirects(resp)
+ return resp
+
+
+
+ def options(self, path, data={}, follow=False, **extra):
+ """
+ Request a response from the server using OPTIONS.
+ """
+ parsed = list(urlsplit(path))
+ parsed[3] = urlencode(data, doseq=True) or parsed[3]
+
+ fullpath = self._mergeurl(parsed)
+ self.handler.follow_redirects = follow
+
+ headers = {}
+ if len(self.cookies) > 0:
+ headers['Cookie'] = self.cookies.output()
+
+ if extra:
+ headers.update(extra)
+
+ response, content = self.handler.request(fullpath, method="OPTIONS", headers=headers)
+
+ resp = self._process_response(response, content)
+
+ if follow:
+ resp = self._handle_redirects(resp)
+ return resp
+
+
+ def put(self, path, data={}, content_type="application/x-www-form-urlencoded",
+ follow=False, **extra):
+ """
+ Send a resource to the server using PUT.
+ """
+ if content_type is MULTIPART_CONTENT:
+ put_data = encode_multipart(BOUNDARY, data)
+ elif content_type == "application/x-www-form-urlencoded":
+ put_data = urlencode(data)
+ else:
+ put_data = data
+
+ parsed = list(urlsplit(path))
+ parsed[3] = urlencode(data, doseq=True) or parsed[3]
+ fullpath = self._mergeurl(parsed)
+ self.handler.follow_redirects = follow
+
+ headers = {}
+ headers['Content-type'] = content_type
+ if len(self.cookies) > 0:
+ headers['Cookie'] = self.cookies.output()
+
+ if extra:
+ headers.update(extra)
+
+ response,content = self.handler.request(fullpath, method="PUT", headers=headers, body=put_data)
+
+ resp = self._process_response(response, content)
+
+ if follow:
+ resp = self._handle_redirects(response)
+ return resp
+
+
+ def delete(self, path, data={}, follow=False, **extra):
+ """
+ Send a DELETE request to the server.
+ """
+ parsed = list(urlsplit(path))
+ parsed[3] = urlencode(data, doseq=True) or parsed[3]
+
+ fullpath = self._mergeurl(parsed)
+ self.handler.follow_redirects = follow
+
+ headers = {}
+ if len(self.cookies) > 0:
+ headers['Cookie'] = self.cookies.output()
+
+ if extra:
+ headers.update(extra)
+
+ response, content = self.handler.request(fullpath, method="DELETE", headers=headers)
+
+ resp = self._process_response(response, content)
+
+ if follow:
+ resp = self._handle_redirects(resp)
+ return resp
+
+
+
+
+class OAuthPayload(object):
+
+ def __init__(self, servername="testserver"):
+ self._token = None
+ self._servername = servername
+ self._oauth_parameters = {
+ 'oauth_version': '1.0'
+ }
+ self._oauth_parameters_extra = {
+ 'oauth_callback': 'http://127.0.0.1/callback',
+ 'scope':'all'
+ }
+ self.errors = StringIO()
+
+ def _get_signed_request(self, method, path, params):
+
+ parameters = params.copy()
+ parameters.update(self._oauth_parameters)
+ oauth_request = Request.from_consumer_and_token(consumer=self._consumer, token=self._token, http_method=method, http_url=path, parameters=parameters)
+ oauth_request.sign_request(SignatureMethod_HMAC_SHA1(), consumer=self._consumer, token=self._token)
+
+ return oauth_request
+
+
+ def set_consumer(self, key, secret):
+ self._consumer = Consumer(key, secret)
+ self._oauth_parameters['oauth_consumer_key'] = key
+
+ def set_scope(self, value):
+ self._oauth_parameters_extra['scope'] = value
+
+ def inject_oauth_data(self, path, method, data):
+
+ path_parsed = urlparse(path)
+
+ if method=='GET' and (data is None or len(data) == 0):
+ new_data = parse_qs(path_parsed[4])
+ elif data is None:
+ new_data = {}
+ else:
+ new_data = data.copy()
+
+ clean_path = ['']*6
+ clean_path[0] = 'http'
+ clean_path[1] = self._servername
+ for i in range(0,4):
+ clean_path[i] = path_parsed[i] or clean_path[i]
+ path = urlunparse(clean_path)
+
+ oauth_request = self._get_signed_request(method, path, new_data)
+
+ new_data.update(oauth_request)
+
+ return new_data
+
+ def login(self, client, login_method, **credential):
+
+
+ self._oauth_parameters.update(self._oauth_parameters_extra)
+ #Obtaining a Request Token
+ resp = client.get(reverse('oauth_request_token'), follow=True)
+ if resp.status_code == 200:
+ self._token = Token.from_string(resp.content)
+ else:
+ self.errors.write("oauth_request_token response status code fail : " + repr(resp))
+ return False
+
+ #Requesting User Authorization
+ res = login_method(client, **credential)
+ if not res:
+ self.errors.write("login failed : " + repr(credential))
+ return False
+
+ resp = client.get(reverse('oauth_user_authorization'))
+ if resp.status_code != 200:
+ self.errors.write("oauth_user_authorization get response status code fail : " + repr(resp))
+ return False
+
+ #"X-Requested-With" : "XMLHttpRequest"
+ resp = client.post(reverse('oauth_user_authorization'), {'authorize_access':1}, **{"X-Requested-With" : "XMLHttpRequest"})
+ if resp.status_code != 302:
+ self.errors.write("oauth_user_authorization post response status code fail : " + repr(resp))
+ return False
+
+ location_splitted = urlsplit(resp["Location"])
+ location_query_dict = parse_qs(location_splitted[3])
+ self._token.verifier = location_query_dict['oauth_verifier']
+
+
+ #Obtaining an Access Token
+ resp = client.get(reverse('oauth_access_token'))
+ if resp.status_code == 200:
+ self._token = Token.from_string(resp.content)
+ for key in self._oauth_parameters_extra.keys():
+ if key in self._oauth_parameters:
+ del(self._oauth_parameters[key])
+ return True
+ else:
+ self.errors.write("oauth_access_token get response status code fail : " + repr(resp))
+ return False
+
+ def logout(self):
+ self._token = None
+
+METHOD_MAPPING = {
+ 'get' : 'GET',
+ 'post' : 'POST',
+ 'put' : 'PUT',
+ 'head' : 'HEAD',
+ 'options' : 'OPTIONS',
+ 'delete' : 'DELETE'
+}
+
+def _generate_request_wrapper(meth):
+ def request_wrapper(inst, *args, **kwargs):
+ path = args[0] if len(args) > 0 else kwargs.get('path','')
+ data = args[1] if len(args) > 1 else kwargs.get('data',{})
+ args = args[2:]
+ if 'path' in kwargs:
+ del(kwargs['path'])
+ if 'data' in kwargs:
+ del(kwargs['data'])
+ data = inst._oauth_data.inject_oauth_data(path, METHOD_MAPPING[meth.__name__], data)
+ return meth(inst,path=path, data=data, *args, **kwargs)
+ return request_wrapper
+
+def _generate_login_wrapper(meth):
+ def login_wrapper(inst, **credential):
+ return inst._oauth_data.login(inst, meth, **credential)
+ return login_wrapper
+
+def _generate_logout_wrapper(meth):
+ def logout_wrapper(inst):
+ inst._oauth_data.logout()
+ meth(inst)
+ return logout_wrapper
+
+class OAuthMetaclass(type):
+
+ def __new__(cls, name, bases, attrs):
+ newattrs = {}
+ def set_consumer(inst, key, secret):
+ inst._oauth_data.set_consumer(key,secret)
+ newattrs['set_consumer'] = set_consumer
+ def set_scope(inst, scope):
+ inst._oauth_data.set_scope(scope)
+ newattrs['set_scope'] = set_scope
+
+ for attrname, attrvalue in attrs.iteritems():
+ if attrname in ('get', 'post', 'head', 'options', 'put', 'delete'):
+ newattrs[attrname] = _generate_request_wrapper(attrvalue)
+ elif attrname == 'login':
+ newattrs[attrname] = _generate_login_wrapper(attrvalue)
+ elif attrname == 'logout':
+ newattrs[attrname] = _generate_logout_wrapper(attrvalue)
+ else:
+ newattrs[attrname] = attrvalue
+
+ for klass in bases:
+ for attrname, attrvalue in klass.__dict__.iteritems():
+ if attrname in newattrs:
+ continue
+ if attrname in ('get', 'post', 'head', 'options', 'put', 'delete'):
+ newattrs[attrname] = _generate_request_wrapper(attrvalue)
+ elif attrname == 'login':
+ newattrs[attrname] = _generate_login_wrapper(attrvalue)
+ elif attrname == 'logout':
+ newattrs[attrname] = _generate_logout_wrapper(attrvalue)
+
+ init_method = newattrs.get("__init__", None)
+
+ def new_init(inst, *args, **kwargs):
+ inst._oauth_data = OAuthPayload(attrs.get('servername','testserver'))
+ if init_method is not None:
+ init_method(*args,**kwargs)
+ else:
+ super(inst.__class__,inst).__init__(*args,**kwargs)
+ newattrs["__init__"] = new_init
+
+ return super(OAuthMetaclass, cls).__new__(cls, name, bases, newattrs)
+
+
+
+class OAuthClient(Client):
+ __metaclass__ = OAuthMetaclass
+
+
+class OAuthWebClient(WebClient):
+ __metaclass__ = OAuthMetaclass
+ servername = '127.0.0.1:8000'
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/test/testcases.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/test/testcases.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,82 @@
+from django.conf import settings
+from django.core.management import call_command
+from django.test.testcases import TestCase, TransactionTestCase
+import django
+import threading
+import time
+import httplib
+from ldt.test.client import WebClient, OAuthClient, OAuthWebClient
+
+
+def launch_webserver(addrport='', keep_running = None, ready_event = None):
+ call_command('testrunserver',addrport, keep_running, ready_event)
+
+class WebTestCase(TransactionTestCase):
+
+# def __init__(self, clientKlass = None):
+# super(WebTestCase,self).__init__()
+# if clientKlass is not None:
+# self.client = clientKlass(self.client)
+
+ def set_login_url(self, value):
+ self.client.login_url = value
+
+ def _pre_setup(self):
+ super(WebTestCase,self)._pre_setup()
+ self._keep_webserver = True
+ self._lock = threading.Lock()
+ self._ready_event = threading.Event()
+
+ self.baseurl = "http://"+settings.TEST_WEBSERVER_ADDRPORT
+ self.client = WebClient()
+ self.client.baseurl = self.baseurl
+ login_url = '/' + settings.LOGIN_URL[len(settings.BASE_URL):].lstrip('/')
+ self.client.login_url = login_url
+
+ def keep_runningserver():
+ with self._lock:
+ return self._keep_webserver
+
+ def launch_server():
+ launch_webserver(settings.TEST_WEBSERVER_ADDRPORT, keep_running=keep_runningserver, ready_event = self._ready_event)
+
+ #launch_server()
+ self._t = threading.Thread(target=launch_server)
+ self._t.start()
+ self._ready_event.wait()
+ time.sleep(0.1)
+
+
+ def _post_teardown(self):
+ with self._lock:
+ self._keep_webserver = False
+ conn = httplib.HTTPConnection(settings.TEST_WEBSERVER_ADDRPORT)
+ conn.request("HEAD", "/")
+ conn.getresponse()
+ self._t.join()
+ super(WebTestCase,self)._post_teardown()
+
+class OAuthTestCase(TestCase):
+
+ def set_consumer(self, key, secret):
+ self.client.set_consumer(key, secret)
+
+ def _pre_setup(self):
+ super(OAuthTestCase,self)._pre_setup()
+ self.client = OAuthClient()
+
+class OAuthWebTestCase(WebTestCase):
+
+ def set_consumer(self, key, secret):
+ self.client.set_consumer(key, secret)
+
+ def _pre_setup(self):
+ super(OAuthWebTestCase,self)._pre_setup()
+ self.client = OAuthWebClient()
+ self.client.baseurl = self.baseurl
+ login_url = '/' + settings.LOGIN_URL[len(settings.BASE_URL):].lstrip('/')
+ self.client.login_url = login_url
+
+
+
+
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/__init__.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,16 @@
+import lucene
+from django.conf import settings
+
+lucene.initVM(lucene.CLASSPATH)
+
+STORE = lucene.SimpleFSDirectory(lucene.File(settings.INDEX_PATH))
+ANALYZER = lucene.PerFieldAnalyzerWrapper(lucene.StandardAnalyzer(lucene.Version.LUCENE_CURRENT))
+ANALYZER.addAnalyzer("tags",lucene.FrenchAnalyzer(lucene.Version.LUCENE_CURRENT))
+ANALYZER.addAnalyzer("title",lucene.FrenchAnalyzer(lucene.Version.LUCENE_CURRENT))
+ANALYZER.addAnalyzer("abstract",lucene.FrenchAnalyzer(lucene.Version.LUCENE_CURRENT))
+ANALYZER.addAnalyzer("all",lucene.FrenchAnalyzer(lucene.Version.LUCENE_CURRENT))
+ANALYZER.addAnalyzer("type_doc",lucene.FrenchAnalyzer(lucene.Version.LUCENE_CURRENT))
+
+
+VERSION = (1,0)
+VERSION_STR = unicode(".".join(map(lambda i:"%01d" % (i,), VERSION)))
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/admin.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/admin.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from django.conf import settings
+from models import *
+
+admin.site.register(Annotation)
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/annotindexer.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/annotindexer.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,46 @@
+from django.conf import settings
+from models import *
+import lucene
+from ldt.text import STORE
+from ldt.text import ANALYZER
+import lxml.etree
+
+
+class AnnotIndexer(object):
+
+ def __init__(self, annotList, writer):
+ self.__annotList = annotList
+ self.__writer = writer
+
+
+ def index_all(self):
+ for annot in self.__annotList:
+ self.index_annotation(annot)
+
+
+ def index_annotation(self, annotation):
+
+ doc = lucene.Document()
+
+ doc.add(lucene.Field("annotation_id", annotation.external_id, lucene.Field.Store.YES, lucene.Field.Index.NOT_ANALYZED))
+
+ annottags = annotation.get_tag_list()
+ tags = ""
+
+ if annottags is None or len(annottags) == 0:
+ tags = ""
+ else:
+ for tag in annottags:
+ tags += tag + ";"
+
+ doc.add(lucene.Field("type_doc", "text-annotation", lucene.Field.Store.NO, lucene.Field.Index.ANALYZED))
+ doc.add(lucene.Field("tags", tags, lucene.Field.Store.NO, lucene.Field.Index.ANALYZED))
+ doc.add(lucene.Field("title", annotation.title, lucene.Field.Store.NO, lucene.Field.Index.ANALYZED))
+ doc.add(lucene.Field("abstract", annotation.description, lucene.Field.Store.NO, lucene.Field.Index.ANALYZED))
+ doc.add(lucene.Field("text", annotation.text, lucene.Field.Store.NO, lucene.Field.Index.ANALYZED))
+ doc.add(lucene.Field("all", " ".join([tags, annotation.title, annotation.description, annotation.text]), lucene.Field.Store.NO, lucene.Field.Index.ANALYZED))
+
+ self.__writer.addDocument(doc)
+
+ self.__writer.close()
+
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/fixtures/base_data.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/base_data.json Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,36 @@
+[
+ {
+ "pk": 6,
+ "model": "text.annotation",
+ "fields": {
+ "update_date": "2010-11-16 17:30:50",
+ "description": "texte de description",
+ "title": "titre de l'annotation",
+ "color": "#DDDDDD",
+ "text": "texte selectionne lors de la creation de l'annotation",
+ "creator": "wakimd",
+ "uri": "",
+ "creation_date": "2010-11-16 17:01:41",
+ "contributor": "oaubert",
+ "tags_field": "tag3,tag1,",
+ "external_id": "z2c1d1fa-629d-4520-a3d2-955b4f2582c0"
+ }
+ },
+ {
+ "pk": 7,
+ "model": "text.annotation",
+ "fields": {
+ "update_date": "2010-11-16 17:30:50",
+ "description": "texte de description",
+ "title": "titre de l'annotation",
+ "color": "#DDDDDD",
+ "text": "texte selectionne lors de la creation de l'annotation",
+ "creator": "wakimd",
+ "uri": "http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168",
+ "creation_date": "2010-11-16 17:01:41",
+ "contributor": "oaubert",
+ "tags_field": "tag3,tag1,",
+ "external_id": "mypersonnalid2"
+ }
+ }
+]
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/fixtures/filter_data.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/filter_data.json Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,70 @@
+[
+ {
+ "pk": 300,
+ "model": "text.annotation",
+ "fields": {
+ "update_date": "2011-01-05 17:29:06",
+ "description": null,
+ "title": "titre de l'annotation",
+ "color": "#AAAAAA",
+ "text": "texte selectionne lors de la creation de l'annotation",
+ "creator": "wakimd",
+ "uri": "http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168",
+ "creation_date": "2011-01-05 17:29:06",
+ "contributor": null,
+ "tags_field": "",
+ "external_id": "k2c1d1fa-629d-4520-a3d2-955b4f2582c0"
+ }
+ },
+ {
+ "pk": 301,
+ "model": "text.annotation",
+ "fields": {
+ "update_date": "2011-01-05 17:29:26",
+ "description": null,
+ "title": "titre de l'annotation2",
+ "color": "#BBBBBB",
+ "text": "texte selectionne lors de la creation de l'annotation2",
+ "creator": "wakimd",
+ "uri": "http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168",
+ "creation_date": "2011-01-05 17:29:26",
+ "contributor": null,
+ "tags_field": "",
+ "external_id": "l2c1d1fa-629d-4520-a3d2-955b4f2582c0"
+ }
+ },
+ {
+ "pk": 302,
+ "model": "text.annotation",
+ "fields": {
+ "update_date": "2011-01-05 17:29:40",
+ "description": null,
+ "title": "titre3",
+ "color": "#CCCCCC",
+ "text": "texte3",
+ "creator": "wakimd",
+ "uri": "http://blabla",
+ "creation_date": "2011-01-05 17:29:40",
+ "contributor": null,
+ "tags_field": "",
+ "external_id": "m2c1d1fa-629d-4520-a3d2-955b4f2582c0"
+ }
+ },
+ {
+ "pk": 6,
+ "model": "text.annotation",
+ "fields": {
+ "update_date": "2010-11-16 17:30:50",
+ "description": "texte de description",
+ "title": "titre de l'annotation",
+ "color": "#DDDDDD",
+ "text": "texte selectionne lors de la creation de l'annotation",
+ "creator": "wakimd",
+ "uri": "",
+ "creation_date": "2010-11-16 17:01:41",
+ "contributor": "oaubert",
+ "tags_field": "tag3,tag1,",
+ "external_id": "z2c1d1fa-629d-4520-a3d2-955b4f2582c0"
+ }
+ }
+]
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/fixtures/oauth_data.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/oauth_data.json Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,67 @@
+[
+ {
+ "pk": 2,
+ "model": "auth.user",
+ "fields": {
+ "username": "jane",
+ "first_name": "",
+ "last_name": "",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": false,
+ "last_login": "2010-12-12 00:04:07",
+ "groups": [], "user_permissions": [],
+ "password": "sha1$45b4e$a4990018063ad9d7aeafaffa349ae61f2b087ed8",
+ "email": "jane@example.com",
+ "date_joined": "2010-12-12 00:04:07"
+ }
+ },
+ {
+ "pk": 1,
+ "model": "oauth_provider.resource",
+ "fields": {
+ "url": "/api/1.0/text/delete/",
+ "name": "delete",
+ "is_readonly": true
+ }
+ },
+ {
+ "pk": 2,
+ "model": "oauth_provider.resource",
+ "fields": {
+ "url": "/api/1.0/text/create/",
+ "name": "create",
+ "is_readonly": true
+ }
+ },
+ {
+ "pk": 3,
+ "model": "oauth_provider.resource",
+ "fields": {
+ "url": "/api/1.0/text/update/",
+ "name": "update",
+ "is_readonly": true
+ }
+ },
+ {
+ "pk": 4,
+ "model": "oauth_provider.resource",
+ "fields": {
+ "url": "",
+ "name": "all",
+ "is_readonly": true
+ }
+ },
+ {
+ "pk": 1,
+ "model": "oauth_provider.consumer",
+ "fields": {
+ "status": 1,
+ "name": "example.com",
+ "secret": "kd94hf93k423kf44",
+ "user": 2,
+ "key": "dpf43f3p2l4k3l03",
+ "description": ""
+ }
+ }
+]
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/fixtures/test_data.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/test_data.json Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,40 @@
+[
+ {
+ "pk": 2,
+ "model": "auth.user",
+ "fields": {
+ "username": "jane",
+ "first_name": "",
+ "last_name": "",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": false,
+ "last_login": "2010-12-12 00:04:07",
+ "groups": [], "user_permissions": [],
+ "password": "sha1$45b4e$a4990018063ad9d7aeafaffa349ae61f2b087ed8",
+ "email": "jane@example.com",
+ "date_joined": "2010-12-12 00:04:07"
+ }
+ },
+ {
+ "pk": 1,
+ "model": "oauth_provider.resource",
+ "fields": {
+ "url": "/api/1.0/text/update/",
+ "name": "all",
+ "is_readonly": true
+ }
+ },
+ {
+ "pk": 1,
+ "model": "oauth_provider.consumer",
+ "fields": {
+ "status": 1,
+ "name": "",
+ "secret": "kd94hf93k423kf44",
+ "user": 2,
+ "key": "dpf43f3p2l4k3l03",
+ "description": ""
+ }
+ }
+]
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/models.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/models.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,153 @@
+from django.conf import settings
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from ldt.core.models import Document, Owner
+from django.contrib.auth.models import User
+import tagging.fields
+from tagging.models import Tag
+from utils import generate_uuid
+import os.path
+import uuid
+import lxml
+import lucene
+from ldt.ldt_utils import STORE, ANALYZER
+from annotindexer import AnnotIndexer
+#from django.core.management.validation import max_length
+
+def Property(func):
+ return property(**func())
+
+
+class Annotation(models.Model):
+ external_id = models.CharField(max_length=1024, null=False, unique=True, default=generate_uuid, verbose_name=_('annotation.external_id'))
+ uri = models.CharField(max_length=1024, verbose_name=_('annotation.uri'))
+ tags_field = tagging.fields.TagField(max_length=2048, null=True, blank=True, verbose_name=_('annotation.tags'))
+ title = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('annotation.title'))
+ description = models.TextField(null=True, blank=True, verbose_name=_('annotation.description'))
+ text = models.TextField(null=True, blank=True, verbose_name=_('annotation.text'))
+ color = models.CharField(max_length=1024, verbose_name=_('annotation.color'))
+ creator = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('creator.title'))
+ contributor = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('contributor.title'))
+ creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('annotation.creation_date'))
+ update_date = models.DateTimeField(auto_now=True, verbose_name=_('annotation.update_date'))
+
+ @Property
+ def tags():
+
+ def fget(self):
+ return ",".join(self.tag_list)
+
+ def fset(self, value):
+ values = None
+ if isinstance(value, (list,tuple)):
+ values = list(value)
+ elif value is not None:
+ values = [v.lower().strip() for v in unicode(value).split(",")]
+
+ if values is not None:
+ self.tags_field = ",".join(values) + ","
+ else:
+ self.tags_field = None
+
+ return locals()
+
+ @Property
+ def tag_list():
+ def fget(self):
+ return [t.name for t in Tag.objects.get_for_object(self)]
+
+ return locals()
+
+
+ def get_tag_list(self):
+ tags = []
+ if self.tags:
+ tags_list = unicode(self.tags)
+ for t in tags_list.split(","):
+ tags.append(t.strip())
+ return tags
+ #return self.tags
+
+
+ def __unicode__(self):
+ return unicode(self.external_id) + u": " + unicode(self.title)
+
+ def serialize(self, root_element=None):
+
+ if root_element is not None:
+ iri = root_element
+ else :
+ iri = lxml.etree.Element('iri')
+ doc = lxml.etree.ElementTree(iri)
+
+
+ textannotation = lxml.etree.SubElement(iri, 'text-annotation')
+ id = lxml.etree.SubElement(textannotation,'id')
+ id.text = self.external_id
+ uri = lxml.etree.SubElement(textannotation,'uri')
+ uri.text = self.uri
+
+ if self.tags:
+ tags = lxml.etree.SubElement(textannotation, 'tags')
+ for t in self.get_tag_list():
+ tag = lxml.etree.SubElement(tags, 'tag')
+ tag.text = t
+
+ content = lxml.etree.SubElement(textannotation,'content')
+ color = lxml.etree.SubElement(content,'color')
+ color.text = self.color
+ description = lxml.etree.SubElement(content,'description')
+ description.text = self.description
+ title = lxml.etree.SubElement(content,'title')
+ title.text = self.title
+ text = lxml.etree.SubElement(content,'text')
+ text.text = self.text
+
+ meta = lxml.etree.SubElement(textannotation,'meta')
+ contributor = lxml.etree.SubElement(meta, "contributor")
+ contributor.text = self.contributor
+ creator = lxml.etree.SubElement(meta, "creator")
+ creator.text = self.creator
+ creationdate = lxml.etree.SubElement(meta, "created")
+ creationdate.text = str(self.creation_date)
+ updatedate = lxml.etree.SubElement(meta, "modified")
+ updatedate.text = str(self.update_date)
+
+ if root_element is not None:
+ return root_element
+ else:
+ return doc
+
+
+ @staticmethod
+ def create_annotation(external_id, uri=None, tags=None, title=None, description=None, text=None, color=None, creator=None, contributor=None, creation_date=None, update_date=None):
+ annotation = Annotation(external_id=external_id, uri=uri, tags=tags, title=title, description=description, text=text, color=color, creator=creator, contributor=contributor, creation_date=creation_date, update_date=update_date)
+ annotation.save()
+ annotation.index_annot()
+
+ return annotation
+
+
+ def delete(self):
+ super(Annotation, self).delete()
+ lucene.getVMEnv().attachCurrentThread()
+ writer = lucene.IndexWriter(STORE, ANALYZER, True, lucene.IndexWriter.MaxFieldLength.UNLIMITED)
+ writer.deleteDocuments(lucene.Term("external_id", self.external_id))
+ writer.close()
+
+ def index_annot(self):
+ lucene.getVMEnv().attachCurrentThread()
+ writer = lucene.IndexWriter(STORE, ANALYZER, True, lucene.IndexWriter.MaxFieldLength.UNLIMITED)
+ annotl = [self,]
+ indexer = AnnotIndexer(annotl,writer)
+ indexer.index_all()
+ writer.close()
+
+ def update_index(self):
+ lucene.getVMEnv().attachCurrentThread()
+ writer = lucene.IndexWriter(STORE, ANALYZER, True, lucene.IndexWriter.MaxFieldLength.UNLIMITED)
+ writer.deleteDocuments(lucene.Term("external_id", self.external_id))
+ writer.close()
+ self.index_annot()
+
+
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/tests/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/__init__.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,3 @@
+from base_tests import *
+from oauth_tests import *
+from server_tests import *
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/tests/base_tests.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/base_tests.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,217 @@
+#encoding:UTF-8
+
+""" Run these tests with 'python manage.py test text' """
+
+from django.conf import settings
+from django.contrib.auth.models import *
+from django.db import transaction
+from django.test import TestCase
+from ldt.core.models import Owner
+from ldt.test.testcases import *
+from ldt.text import VERSION_STR
+from ldt.text.models import *
+from ldt.text.views import *
+import datetime
+import lxml.etree
+import time
+import unittest
+import urllib
+import uuid
+from oauth_provider.models import Resource, Consumer
+
+
+CONSUMER_KEY = 'dpf43f3p2l4k3l03'
+CONSUMER_SECRET = 'kd94hf93k423kf44'
+
+
+# This test creates an annotation and checks that:
+# 1. the annotation was created in the database (by trying to access it through a Django object)
+# 2. the returned xml contains correct data
+class CreateTest(OAuthTestCase):
+
+ fixtures = ['base_data','oauth_data']
+
+ def setUp(self):
+
+ self.id = 'f2c1d1fa-629d-4520-a3d2-955b4f2582c0'
+ self.text = "texte selectionne lors de la creation de l\'annotation"
+ self.content = str(''+self.id+'http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168tag1tag2#AAAAAA'+self.text+'oaubert79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.417550oaubert79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.420459')
+ self.error_content = 'z2c1d1fa-629d-4520-a3d2-955b4f2582c0http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168tag1tag2#AAAAAAoaubert79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.417550oaubert79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.420459'
+
+ self.set_consumer(CONSUMER_KEY, CONSUMER_SECRET)
+ self.client.login(username='jane', password='toto')
+
+ def test_create_annotation(self):
+
+ response = self.client.post('/api/'+ VERSION_STR +'/text/create/', {'content':self.content})
+ self.assertEqual(response.status_code,200)
+ annot1 = lxml.etree.fromstring(response.content)
+ self.assertEqual(annot1.xpath("/iri/text-annotation/id/text()")[0],self.id)
+ self.assertEqual(annot1.xpath("/iri/text-annotation/content/text/text()")[0],self.text)
+ self.assertEqual(len(annot1.xpath("/iri/text-annotation/tags/tag")),2)
+ self.assertEqual(annot1.xpath("/iri/text-annotation/meta/created/text()")[0][:11], str(datetime.datetime.now())[:11])
+
+ annot2 = Annotation.objects.get(external_id=self.id)
+ self.assertEqual(annot2.text,self.text)
+ self.assertEqual(len(annot2.tags.split(",")),2)
+ self.assertEqual(str(annot2.creation_date)[:11], str(datetime.datetime.now())[:11])
+
+ def test_error_create(self):
+
+ response = self.client.post('/api/'+ VERSION_STR +'/text/create/', {'content':self.error_content})
+ self.assertEqual(response.status_code, 409)
+
+
+# This test creates an annotation, then gets it, and checks that the returned xml contains correct data
+class GetTest(TestCase):
+
+ fixtures = ['base_data']
+
+ def setUp(self):
+
+ self.id = 'z2c1d1fa-629d-4520-a3d2-955b4f2582c0'
+ self.color = '#DDDDDD'
+ self.creation_date = '2010-11-16 17:01:41'
+ self.title = "titre de l'annotation"
+
+ def test_get_annotation(self):
+
+ response = self.client.get('http://127.0.0.1:8000/api/'+ VERSION_STR +'/text/get/'+self.id+'')
+ self.assertEqual(response.status_code,200)
+ annot1 = lxml.etree.fromstring(response.content)
+ self.assertEqual(annot1.xpath("/iri/text-annotation/id/text()")[0],self.id)
+ self.assertTrue('tag3','tag1' in annot1.xpath("/iri/text-annotation/tags/tag/text()"))
+ self.assertTrue('mytag','tag2new' in annot1.xpath("/iri/text-annotation/tags/tag/text()"))
+ self.assertEqual(annot1.xpath("/iri/text-annotation/content/color/text()")[0],self.color)
+ self.assertEqual(annot1.xpath("/iri/text-annotation/content/title/text()")[0],self.title)
+ self.assertEqual(annot1.xpath("/iri/text-annotation/meta/created/text()")[0], self.creation_date)
+
+ def test_error_get(self):
+
+ response = self.client.get('http://127.0.0.1:8000/api/'+ VERSION_STR +'/text/get/2')
+ self.assertEqual(response.status_code,404)
+
+
+class FilterTest(TestCase):
+
+ fixtures = ['filter_data']
+
+ def setUp(self):
+
+ self.user = 'wakimd'
+ self.uri = "http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168"
+ self.limit = "2,1"
+ self.filter = 'lors'
+
+ def test_filter_all(self):
+
+ response = self.client.get('/api/'+ VERSION_STR +'/text/filter/', {'uri':self.uri,'creator':self.user,'limit':"1,1", 'filter':self.filter})
+ self.assertEqual(response.status_code,200)
+ doc = lxml.etree.fromstring(response.content)
+ self.assertEqual(len(doc.xpath("/iri/text-annotation")),1)
+ for elem in doc.xpath("/iri/text-annotation/meta/creator/text()"):
+ self.assertEqual(elem,self.user)
+ for elem in doc.xpath("/iri/text-annotation/uri/text()"):
+ self.assertEqual(elem,self.uri)
+ for elem in doc.xpath("/iri/text-annotation/content/text/text()"):
+ self.assertTrue(self.filter in elem)
+
+ def test_filter_creator_limit(self):
+
+ response = self.client.get('/api/'+ VERSION_STR +'/text/filter/', {'creator':self.user,'limit':self.limit})
+ self.assertEqual(response.status_code,200)
+ doc = lxml.etree.fromstring(response.content)
+ if self.limit is not None:
+ self.assertEqual(str(len(doc.xpath("/iri/text-annotation"))),self.limit[0])
+ for elem in doc.xpath("/iri/text-annotation/meta/creator/text()"):
+ self.assertEqual(elem,self.user)
+
+ def test_filter_uri(self):
+
+ response = self.client.get('/api/'+ VERSION_STR +'/text/filter/', {'uri':self.uri})
+ self.assertEqual(response.status_code,200)
+ doc = lxml.etree.fromstring(response.content)
+ for elem in doc.xpath("/iri/text-annotation/uri/text()"):
+ self.assertEqual(elem,self.uri)
+
+ def test_filter_annotation_filter(self):
+
+ response = self.client.get('/api/'+ VERSION_STR +'/text/filter/', {'uri':self.uri,'filter':self.filter})
+ self.assertEqual(response.status_code,200)
+ doc = lxml.etree.fromstring(response.content)
+ for elem in doc.xpath("/iri/text-annotation/content/text/text()"):
+ self.assertTrue(self.filter in elem)
+ for elem in doc.xpath("/iri/text-annotation/uri/text()"):
+ self.assertEqual(elem,self.uri)
+
+ def test_filter_none(self):
+
+ response = self.client.get('/api/'+ VERSION_STR +'/text/filter/', {'uri':'uri','creator':'creator','filter':'filter'})
+ self.assertEqual(response.status_code,200)
+ self.assertEqual(response.content, '\n')
+
+
+
+# This test deletes an annotation, and checks that:
+# 1. the annotation doesn't exist anymore in the database (by trying to access it through Django objects)
+# 2. the returned xml contains an empty string
+class DeleteTest(OAuthTestCase):
+
+ fixtures = ['base_data','oauth_data']
+
+ def setUp(self):
+
+ self.id = 'z2c1d1fa-629d-4520-a3d2-955b4f2582c0'
+
+ self.set_consumer(CONSUMER_KEY, CONSUMER_SECRET)
+ self.client.login(username='jane', password='toto')
+
+ def test_delete_annotation(self):
+
+ response = self.client.delete('/api/'+ VERSION_STR +'/text/delete/'+self.id+'')
+ self.assertEqual(response.content, "")
+ self.assertEqual(response.status_code,200)
+
+ self.assertRaises(Annotation.DoesNotExist, Annotation.objects.get, external_id=self.id)
+
+ def test_error_delete(self):
+
+ response = self.client.delete('/api/'+ VERSION_STR +'/text/ldt/delete/1')
+ self.assertEqual(response.status_code,404)
+
+
+# This test creates an annotation, then updates it with new content, and checks that the returned xml contains the updated data
+class UpdateTest(OAuthTestCase):
+
+ fixtures = ['oauth_data','base_data']
+
+ def setUp(self):
+
+ self.id = 'z2c1d1fa-629d-4520-a3d2-955b4f2582c0'
+ self.color = '#DDDDDD'
+ self.description = "texte de description update"
+ self.text = "texte selectionne a nouveau lors de la creation de l\'annotation"
+ self.contributor = "oaubert"
+ self.content = 'tag2newmytag'+self.color+''+self.description+''+self.text+''+self.contributor+'80cd0532-1dda-4130-b351-6a181130a7c92010-11-06 12:33:53.420459'
+
+ self.set_consumer(CONSUMER_KEY, CONSUMER_SECRET)
+ self.client.login(username='jane', password='toto')
+
+ def test_update_annotation(self):
+
+ response = self.client.put('/api/'+ VERSION_STR +'/text/update/'+self.id+'', {'content':self.content})
+ self.assertEqual(response.status_code,200)
+ annot = Annotation.objects.get(external_id=self.id)
+ self.assertEqual(str(annot.update_date)[:11],str(datetime.datetime.now())[:11])
+ self.assertEqual(annot.external_id,self.id)
+ self.assertTrue('tag3','tag1' not in annot.tags)
+ self.assertTrue('mytag','tag2new' in annot.tags)
+ self.assertEqual(annot.color,self.color)
+ self.assertEqual(annot.description,self.description)
+ self.assertEqual(annot.text,self.text)
+ self.assertEqual(annot.contributor,self.contributor)
+
+ def test_error_update(self):
+
+ response = self.client.put('/api/'+ VERSION_STR +'/text/update/1', {'content':self.content})
+ self.assertEqual(response.status_code,404)
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/tests/oauth_tests.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/oauth_tests.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,266 @@
+#encoding:UTF-8
+
+""" Run these tests with 'python manage.py test text' """
+
+from django.conf import settings, settings
+from django.contrib.auth.models import *
+from django.db import transaction
+from django.test import TestCase
+from ldt.test.client import Client
+from ldt.test.testcases import *
+from ldt.text import VERSION_STR
+from ldt.text.models import Annotation
+from ldt.text.views import *
+from oauth2 import Request, SignatureMethod_HMAC_SHA1, SignatureMethod_PLAINTEXT, \
+ generate_nonce
+from oauth_provider.consts import OUT_OF_BAND
+from oauth_provider.models import Resource, Consumer, Token, Nonce
+import logging
+import time
+import urlparse
+
+CONSUMER_KEY = 'dpf43f3p2l4k3l03'
+CONSUMER_SECRET = 'kd94hf93k423kf44'
+
+
+class OAuthTestDelete(TestCase):
+
+ fixtures = ['oauth_data', 'base_data']
+
+ def setUp(self):
+
+ self.nonce = generate_nonce(8)
+
+ self.parameters_request = {
+ 'oauth_consumer_key': CONSUMER_KEY,
+ 'oauth_signature_method': 'PLAINTEXT',
+ 'oauth_signature': '%s&' % CONSUMER_SECRET,
+ 'oauth_timestamp': str(int(time.time())),
+ 'oauth_nonce': self.nonce,
+ 'oauth_version': '1.0',
+ 'oauth_callback': OUT_OF_BAND,
+ }
+
+ self.parameters_access = {
+ 'oauth_consumer_key': CONSUMER_KEY,
+ 'oauth_signature_method': 'PLAINTEXT',
+ 'oauth_nonce': self.nonce,
+ 'oauth_version': '1.0',
+ }
+
+ self.parameters_protected = {
+ 'oauth_consumer_key': CONSUMER_KEY,
+ 'oauth_signature_method': 'HMAC-SHA1',
+ 'oauth_nonce': self.nonce,
+ 'oauth_version': '1.0',
+ }
+
+
+ def test_auth_access_delete(self):
+
+ ## REQUEST TOKEN
+ self.parameters_request['scope'] = 'delete'
+
+ response = self.client.get("http://127.0.0.1:8000/oauth/request_token/", self.parameters_request)
+ self.assertEqual(response.status_code,200)
+
+ token = list(Token.objects.all())[-1]
+ data = urlparse.parse_qs(response.content)
+
+ self.assertEqual(token.key, data["oauth_token"][0])
+ self.assertEqual(token.secret, data['oauth_token_secret'][0])
+ self.assertTrue(data['oauth_callback_confirmed'][0])
+ self.assertEqual(token.callback, None)
+
+ ## USER AUTHORIZATION
+ parameters = {
+ 'oauth_token': token.key,
+ }
+
+ response = self.client.get("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,302)
+ self.assertTrue(token.key in response['Location'])
+
+ self.client.login(username='jane', password='toto')
+
+ response = self.client.get("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,200)
+ self.assertEqual(response.content,'Fake authorize view for example.com.')
+
+ # fake authorization by the user
+ parameters['authorize_access'] = 1
+ response = self.client.post("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,200)
+
+ token = list(Token.objects.all())[-1]
+ self.assertTrue(token.is_approved)
+
+ ## ACCESS TOKEN
+ self.parameters_access['oauth_token'] = token.key
+ self.parameters_access['oauth_signature'] = '%s&%s' % (CONSUMER_SECRET, token.secret)
+ self.parameters_access['oauth_timestamp'] = str(int(time.time()))
+ self.parameters_access['oauth_verifier'] = token.verifier
+ self.parameters_access['scope'] = 'delete'
+
+ response = self.client.get("/oauth/access_token/", self.parameters_access)
+
+ access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
+ self.assertTrue(access_token.key in response.content)
+ self.assertTrue(access_token.secret in response.content)
+ self.assertEqual(access_token.user.username, u'jane')
+
+ ## ACCESSING PROTECTED VIEW
+ self.parameters_protected['oauth_token'] = access_token.key
+ self.parameters_protected['oauth_timestamp'] = str(int(time.time()))
+
+ oauth_request = Request.from_token_and_callback(access_token, http_url='http://testserver/api/'+VERSION_STR+'/text/delete/z2c1d1fa-629d-4520-a3d2-955b4f2582c0', parameters=self.parameters_protected, http_method="DELETE")
+ signature_method = SignatureMethod_HMAC_SHA1()
+ signature = signature_method.sign(oauth_request, Consumer.objects.get(name="example.com"), access_token)
+
+ self.parameters_protected['oauth_signature'] = signature
+ response = self.client.delete("/api/"+VERSION_STR+"/text/delete/z2c1d1fa-629d-4520-a3d2-955b4f2582c0", self.parameters_protected)
+ self.assertEqual(response.content, "")
+ self.assertEqual(response.status_code,200)
+
+ self.client.logout()
+ access_token.delete()
+
+
+ def test_auth_access_create(self):
+
+ ## REQUEST TOKEN
+ self.parameters_request['scope'] = 'create'
+ response = self.client.get("/oauth/request_token/", self.parameters_request)
+ self.assertEqual(response.status_code,200)
+ token = list(Token.objects.all())[-1]
+ data = urlparse.parse_qs(response.content)
+ self.assertEqual(token.key, data["oauth_token"][0])
+ self.assertEqual(token.secret, data['oauth_token_secret'][0])
+ self.assertTrue(data['oauth_callback_confirmed'][0])
+ self.assertEqual(token.callback, None)
+
+ ## USER AUTHORIZATION
+ parameters = {
+ 'oauth_token': token.key,
+ }
+
+ response = self.client.get("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,302)
+ self.assertTrue(token.key in response['Location'])
+
+ self.client.login(username='jane', password='toto')
+
+ response = self.client.get("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,200)
+ self.assertEqual(response.content,'Fake authorize view for example.com.')
+
+ # fake authorization by the user
+ parameters['authorize_access'] = 1
+ response = self.client.post("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,200)
+ token = list(Token.objects.all())[-1]
+ self.assertTrue(token.is_approved)
+
+ ## ACCESS TOKEN
+ self.parameters_access['oauth_token'] = token.key
+ self.parameters_access['oauth_signature'] = '%s&%s' % (CONSUMER_SECRET, token.secret)
+ self.parameters_access['oauth_timestamp'] = str(int(time.time()))
+ self.parameters_access['oauth_verifier'] = token.verifier
+ self.parameters_access['scope'] = 'create'
+
+ response = self.client.get("/oauth/access_token/", self.parameters_access)
+
+ access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
+ self.assertTrue(access_token.key in response.content)
+ self.assertTrue(access_token.secret in response.content)
+ self.assertEqual(access_token.user.username, u'jane')
+
+ ## ACCESSING PROTECTED VIEW
+ self.parameters_protected['oauth_token'] = access_token.key
+ self.parameters_protected['oauth_timestamp'] = str(int(time.time()))
+ self.parameters_protected['content'] = 'f2c1d1fa-629d-4520-a3d2-955b4f2582c0http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168tag1tag2#AAAAAAoaubert79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.417550oaubert79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.420459'
+
+ oauth_request = Request.from_token_and_callback(access_token, http_url='http://testserver/api/'+VERSION_STR+'/text/create/', parameters=self.parameters_protected, http_method="POST")
+ signature_method = SignatureMethod_HMAC_SHA1()
+ signature = signature_method.sign(oauth_request, Consumer.objects.get(name="example.com"), access_token)
+
+ self.parameters_protected['oauth_signature'] = signature
+ response = self.client.post("/api/"+VERSION_STR+"/text/create/", self.parameters_protected)
+ annot1 = lxml.etree.fromstring(response.content)
+ self.assertEqual(annot1.xpath("/iri/text-annotation/meta/created/text()")[0][:11], str(datetime.datetime.now())[:11])
+ self.assertEqual(response.status_code,200)
+
+ self.client.logout()
+ access_token.delete()
+
+ def test_auth_access_update(self):
+
+ ## REQUEST TOKEN
+ self.parameters_request['scope'] = 'update'
+
+ response = self.client.get("/oauth/request_token/", self.parameters_request)
+ self.assertEqual(response.status_code,200)
+
+ token = list(Token.objects.all())[-1]
+ data = urlparse.parse_qs(response.content)
+ self.assertEqual(token.key, data["oauth_token"][0])
+ self.assertEqual(token.secret, data['oauth_token_secret'][0])
+ self.assertTrue(data['oauth_callback_confirmed'][0])
+ self.assertEqual(token.callback, None)
+
+ ## USER AUTHORIZATION
+ parameters = {
+ 'oauth_token': token.key,
+ }
+
+ response = self.client.get("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,302)
+ self.assertTrue(token.key in response['Location'])
+
+ self.client.login(username='jane', password='toto')
+
+ response = self.client.get("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,200)
+ self.assertEqual(response.content,'Fake authorize view for example.com.')
+
+ # fake authorization by the user
+ parameters['authorize_access'] = 1
+ response = self.client.post("/oauth/authorize/", parameters)
+ self.assertEqual(response.status_code,200)
+ token = list(Token.objects.all())[-1]
+ self.assertTrue(token.is_approved)
+
+ ## ACCESS TOKEN
+ self.parameters_access['oauth_token'] = token.key
+ self.parameters_access['oauth_signature'] = '%s&%s' % (CONSUMER_SECRET, token.secret)
+ self.parameters_access['oauth_timestamp'] = str(int(time.time()))
+ self.parameters_access['oauth_verifier'] = token.verifier
+ self.parameters_access['scope'] = 'update'
+
+ response = self.client.get("/oauth/access_token/", self.parameters_access)
+
+ access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
+ self.assertTrue(access_token.key in response.content)
+ self.assertTrue(access_token.secret in response.content)
+ self.assertEqual(access_token.user.username, u'jane')
+
+ ## ACCESSING PROTECTED VIEW
+ self.parameters_protected['oauth_token'] = access_token.key
+ self.parameters_protected['oauth_timestamp'] = str(int(time.time()))
+ self.parameters_protected['content'] = 'tag2newmytag#DDDDDDoaubert80cd0532-1dda-4130-b351-6a181130a7c92010-11-06 12:33:53.420459'
+
+ oauth_request = Request.from_token_and_callback(access_token, http_url='http://testserver/api/'+VERSION_STR+'/text/update/z2c1d1fa-629d-4520-a3d2-955b4f2582c0', parameters=self.parameters_protected, http_method="PUT")
+ signature_method = SignatureMethod_HMAC_SHA1()
+ signature = signature_method.sign(oauth_request, Consumer.objects.get(name="example.com"), access_token)
+
+ self.parameters_protected['oauth_signature'] = signature
+ response = self.client.put("/api/"+VERSION_STR+"/text/update/z2c1d1fa-629d-4520-a3d2-955b4f2582c0", self.parameters_protected)
+ doc = lxml.etree.fromstring(response.content)
+ self.assertEqual(doc.xpath("/iri/text-annotation/id/text()")[0],"z2c1d1fa-629d-4520-a3d2-955b4f2582c0")
+ self.assertTrue('tag3','tag1' not in doc.xpath("/iri/text-annotation/tags/tag/text()"))
+ self.assertTrue('mytag','tag2new' in doc.xpath("/iri/text-annotation/tags/tag/text()"))
+ self.assertEqual(doc.xpath("/iri/text-annotation/meta/modified/text()")[0][:11],str(datetime.datetime.now())[:11])
+ self.assertEqual(response.status_code,200)
+
+ self.client.logout()
+ access_token.delete()
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/tests/server_tests.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/server_tests.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,160 @@
+#encoding:UTF-8
+
+""" Run these tests with 'python manage.py test text' """
+
+from django.conf import settings
+from django.contrib.auth.models import *
+from django.test import TestCase
+from ldt.core.models import Owner
+from ldt.test.testcases import OAuthWebTestCase, OAuthTestCase, WebTestCase
+from ldt.text import VERSION_STR
+from ldt.text.models import *
+from ldt.text.views import *
+from oauth_provider.models import Resource, Consumer, Token, Nonce
+import datetime
+import logging
+import lxml.etree
+import time
+import unittest
+import urllib
+import uuid
+
+CONSUMER_KEY = 'dpf43f3p2l4k3l03'
+CONSUMER_SECRET = 'kd94hf93k423kf44'
+
+
+class OnServerGlobalTest(OAuthWebTestCase):
+
+ fixtures = ['oauth_data','base_data']
+
+ def setUp(self):
+
+ self.id = "mypersonnalid"
+ self.title = "titre de l\'annotation"
+ self.text = "texte selectionne lors de la creation de l\'annotation"
+ self.textupdate = "texte selectionne a nouveau lors de la creation de l\'annotation"
+ self.descupdate = "texte de description update"
+ self.contributor = "oaubert"
+ self.creator = "wakimd"
+ self.uri = "http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168"
+ self.content1 = ''+self.id+''+self.uri+'tag1tag2#AAAAAA'+self.title+''+self.text+'oaubert79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.417550'+self.creator+'79cd0532-1dda-4130-b351-6a181130a7c92010-09-06 12:33:53.420459'
+ self.contentupdate = 'tag1tag2newtag3#DDDDDD'+self.descupdate+''+self.textupdate+''+self.contributor+'80cd0532-1dda-4130-b351-6a181130a7c92010-11-06 12:33:53.420459'
+
+ self.set_login_url("/accounts/login/")
+ self.set_consumer(CONSUMER_KEY, CONSUMER_SECRET)
+
+
+ def test_everything(self):
+
+ self.assertTrue(self.client.login(username='jane', password='toto'))
+
+ #test POST
+ creation = self.client.post("/api/"+VERSION_STR+"/text/create/", data={'content': self.content1})
+ self.assertEqual(creation.status_code,200)
+ annot = Annotation.objects.get(external_id=self.id)
+ self.assertEqual(str(annot.creation_date)[:11], str(datetime.datetime.now())[:11])
+ self.assertEqual(annot.text,self.text)
+ self.assertEqual(len(annot.tags.split(",")),2)
+
+ #test GET
+ get = self.client.get("/api/"+VERSION_STR+"/text/get/"+self.id+"")
+ self.assertEqual(get.status_code,200)
+ annot = lxml.etree.fromstring(get.content)
+ self.assertTrue('tag1','tag2' in annot.xpath("/iri/text-annotation/tags/tag/text()"))
+ self.assertEqual(annot.xpath("/iri/text-annotation/content/title/text()")[0],self.title)
+
+ #test OPTIONS
+ options = self.client.options("/api/"+VERSION_STR+"/text/get/"+self.id+"")
+ self.assertEqual(options.status_code,200)
+ self.assertEqual(options.content, get.content)
+
+ #test HEAD
+ head = self.client.head("/api/"+VERSION_STR+"/text/get/"+self.id+"")
+ self.assertEqual(head.status_code,200)
+ self.assertEqual(head['content-type'],'text/xml;charset=utf-8')
+ self.assertEqual(head['content-language'],'fr-fr')
+ self.assertEqual(head.content,'')
+
+ #test LOGOUT
+ self.client.logout()
+
+ creation = self.client.post("/api/"+VERSION_STR+"/text/create/", data={'content': self.content1})
+ self.assertEqual(creation.status_code,401)
+
+ self.assertTrue(self.client.login(username='jane', password='toto'))
+
+ # filter
+ filt1 = self.client.get("/api/"+VERSION_STR+"/text/filter/", data={"uri":self.uri})
+ self.assertEqual(filt1.status_code,200)
+ doc = lxml.etree.fromstring(filt1.content)
+ self.assertEqual(len(doc.xpath("/iri/text-annotation")),2)
+ for elem in doc.xpath("/iri/text-annotation/uri/text()"):
+ self.assertEqual(elem,self.uri)
+
+ filt2 = self.client.get("/api/"+VERSION_STR+"/text/filter/", data={"creator":self.contributor})
+ self.assertEqual(filt2.status_code,200)
+ self.assertEqual(filt2.content, '\n')
+
+ #test PUT
+ update = self.client.put("/api/"+VERSION_STR+"/text/update/"+self.id+"", data={'content':self.contentupdate})
+ self.assertEqual(update.status_code,200)
+ annot = Annotation.objects.get(external_id=self.id)
+ self.assertEqual(str(annot.creation_date)[:11],str(datetime.datetime.now())[:11])
+ self.assertEqual(annot.external_id,self.id)
+ self.assertTrue('tag1','tag2' not in annot.tags)
+ self.assertTrue('mytag','tag2new' in annot.tags)
+ self.assertEqual(annot.description,self.descupdate)
+ self.assertEqual(annot.text,self.textupdate)
+ self.assertEqual(annot.contributor,self.contributor)
+
+ #test DELETE
+ delete = self.client.delete("/api/"+VERSION_STR+"/text/delete/"+self.id+"")
+ self.assertEqual(delete.content, "")
+ self.assertEqual(delete.status_code,200)
+ self.assertRaises(Annotation.DoesNotExist, Annotation.objects.get, external_id=self.id)
+
+ self.client.logout()
+
+
+ def test_errors(self):
+
+ self.assertTrue(self.client.login(username='jane', password='toto'))
+
+ creation = self.client.post("/api/"+VERSION_STR+"/text/create/", data={'content': self.content1})
+ self.assertEqual(creation.status_code,200)
+
+ creation = self.client.post("/api/"+VERSION_STR+"/text/create/", data={'content': self.content1})
+ self.assertEqual(creation.status_code,409)
+
+ get = self.client.get("/api/"+VERSION_STR+"/text/get/1")
+ self.assertEqual(get.status_code,404)
+
+ update = self.client.put('/api/'+ VERSION_STR +'/text/update/1', {'content':self.contentupdate})
+ self.assertEqual(update.status_code,404)
+
+ delete = self.client.delete("/api/"+VERSION_STR+"/text/delete/1")
+ self.assertEqual(delete.status_code,404)
+
+ delete = self.client.delete("/api/"+VERSION_STR+"/text/delete/"+self.id+"")
+ self.assertEqual(delete.status_code,200)
+ self.assertEqual(delete.content, "")
+
+ self.client.logout()
+
+
+class WebClientTest(WebTestCase):
+
+ fixtures = ['oauth_data', 'base_data']
+
+ def setUp(self):
+ self.set_login_url("/accounts/login/")
+
+ def test_get(self):
+ self.assertTrue(self.client.login(username='jane', password='toto'))
+
+ get = self.client.get("/api/"+VERSION_STR+"/text/get/z2c1d1fa-629d-4520-a3d2-955b4f2582c0")
+ self.assertEqual(get.status_code,200)
+ annot = lxml.etree.fromstring(get.content)
+ self.assertTrue('tag1','tag2' in annot.xpath("/iri/text-annotation/tags/tag/text()"))
+
+
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/tests/utils.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/utils.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,1 @@
+
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/urls.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/urls.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,14 @@
+from django.conf.urls.defaults import *
+from ldt.management import test_ldt
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('ldt.text',
+ url(r'^create/$', 'views.create_annotation'),
+ url(r'^filter/$', 'views.filter_annotation'),
+ url(r'^get/(?P.*)$', 'views.get_annotation'),
+ url(r'^delete/(?P.*)$', 'views.delete_annotation'),
+ url(r'^update/(?P.*)$', 'views.update_annotation'),
+)
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/utils.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/utils.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,61 @@
+from django.conf import settings
+from ldt.ldt_utils import ANALYZER, STORE
+import base64
+import django.core.urlresolvers
+import lucene
+import lxml.etree
+import urllib
+import uuid
+
+__BOOLEAN_DICT = {
+ 'false':False,
+ 'true':True,
+ '0':False,
+ '1':True,
+ 't': True,
+ 'f':False
+}
+
+def boolean_convert(bool):
+ if bool is None:
+ return False
+ if bool is True or bool is False:
+ return bool
+ key = str(bool).lower()
+ return __BOOLEAN_DICT.get(key, False)
+
+
+def generate_uuid():
+ return unicode(uuid.uuid1())
+
+
+#def normalize_tags(list):
+# nlist=[]
+# for tag in list:
+# tag = tag.lower()
+# nlist.append(tag)
+# taglist = dict().fromkeys(nlist).keys()
+#
+# return taglist
+
+
+class TextSearch(object):
+
+ def query(self, field, query):
+ indexSearcher = lucene.IndexSearcher(STORE)
+ queryParser = lucene.QueryParser(lucene.Version.LUCENE_30, field, lucene.FrenchAnalyzer(lucene.Version.LUCENE_30))
+ queryParser.setDefaultOperator(lucene.QueryParser.Operator.AND)
+ queryObj = queryParser.parse(query)
+ hits = indexSearcher.search(queryObj, settings.LDT_MAX_SEARCH_NUMBER)
+
+ res = []
+ for hit in hits.scoreDocs:
+ doc = indexSearcher.doc(hit.doc)
+ res.append({"external_id":doc.get("external_id"),"title":doc.get("title")})
+ indexSearcher.close()
+ return res
+
+ def queryAll(self, query):
+ return self.query("all", query)
+
+
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/text/views.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/views.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,226 @@
+from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.db import IntegrityError
+from django.db.models import Q
+from django.forms.util import ErrorList
+from django.http import HttpResponse, Http404, HttpResponseRedirect, \
+ HttpResponseForbidden, HttpResponseServerError, HttpResponseBadRequest
+from django.shortcuts import render_to_response, get_object_or_404, \
+ get_list_or_404
+from django.template import RequestContext
+from django.template.loader import render_to_string
+from django.utils.html import escape
+from django.utils.translation import ugettext as _, ungettext
+from django.views.decorators.csrf import csrf_exempt
+from httplib import CONFLICT
+from ldt.core.models import Owner
+from ldt.text.models import *
+from ldt.text.utils import boolean_convert
+from lxml import etree
+from lxml.html import fromstring, fragment_fromstring
+from string import Template
+from urllib2 import urlparse
+from utils import *
+import StringIO
+import cgi
+import django.core.urlresolvers
+import ldt.auth as ldt_auth
+import ldt.utils.path as ldt_utils_path
+import logging
+import lucene
+import tempfile
+import uuid
+from tagging.models import Tag
+from oauth_provider.decorators import *
+
+
+## Filters the annotation depending on the request parameters
+## Returns an xml containing the resulting annotations
+def filter_annotation(request, uri=None, filter=None, limit=None, creator=None):
+ annotlist = None
+ query = Q()
+
+ if request.GET.get('uri'):
+ query &= Q(uri=request.GET.get('uri'))
+ if request.GET.get('creator'):
+ query &= Q(creator=request.GET.get('creator'))
+
+ annotlist = Annotation.objects.filter(query)
+
+ if request.GET.get('filter') and len(request.GET.get('filter')) > 0:
+ search = TextSearch()
+ res = search.query("text",request.GET.get('filter'))
+ for r in res:
+ annotlist.append(r)
+
+ if request.GET.get('limit'):
+ nb = request.GET.get('limit')[0]
+ offset = request.GET.get('limit')[2]
+ annotlist = annotlist[offset:]
+ annotlist = annotlist[:nb]
+
+ #create xml
+ iri = lxml.etree.Element('iri')
+ doc = lxml.etree.ElementTree(iri)
+
+ for annot in annotlist:
+ annot.serialize(iri)
+
+ return HttpResponse(lxml.etree.tostring(doc, pretty_print=True), mimetype="text/xml;charset=utf-8")
+
+
+## Creates an annotation from a urlencoded xml content
+## Returns an xml-structured annotation
+@oauth_required
+@csrf_exempt
+def create_annotation(request):
+ cont = request.POST["content"]
+ doc = lxml.etree.fromstring(cont)
+
+ id_nodes = doc.xpath("/iri/text-annotation/id/text()")
+ if id_nodes:
+ id = unicode(id_nodes[0])
+ else:
+ id = generate_uuid()
+
+ uri = unicode(doc.xpath("/iri/text-annotation/uri/text()")[0])
+
+ ltags = list(set([unicode(tag.text).lower().strip() for tag in doc.xpath("/iri/text-annotation/tags/tag")]))
+ tags = ",".join(ltags)
+ if len(ltags) == 1:
+ tags += ","
+
+
+ title_nodes = doc.xpath("/iri/text-annotation/content/title/text()")
+ if title_nodes:
+ title = unicode(title_nodes[0])
+ else:
+ title = None
+ desc_nodes = doc.xpath("/iri/text-annotation/content/description/text()")
+ if desc_nodes:
+ desc = unicode(desc_nodes[0])
+ else:
+ desc = None
+ text_nodes = doc.xpath("/iri/text-annotation/content/text/text()")
+ if text_nodes:
+ text = unicode(text_nodes[0])
+ else:
+ text = None
+ color_nodes = doc.xpath("/iri/text-annotation/content/color/text()")
+ if color_nodes:
+ color = unicode(color_nodes[0])
+ else:
+ color = None
+
+ creator_nodes = doc.xpath("/iri/text-annotation/meta/creator/text()")
+ if creator_nodes:
+ creator = unicode(creator_nodes[0])
+ else:
+ creator = None
+ contributor_nodes = doc.xpath("/iri/text-annotation/meta/contributor/text()")
+ if contributor_nodes:
+ contributor = unicode(contributor_nodes[0])
+ else:
+ contributor = None
+
+ try:
+ annotation = Annotation.create_annotation(external_id=id, uri=uri, tags=tags, title=title, description=desc, text=text, color=color, creator=creator, contributor=contributor)
+ annotation.save()
+ return HttpResponse(lxml.etree.tostring(annotation.serialize(), pretty_print=True), mimetype="text/xml;charset=utf-8")
+ except IntegrityError:
+ return HttpResponse(status=409)
+
+
+
+## Gets an annotation from its id
+## Returns the xml-structured annotation
+def get_annotation(request, id):
+ try:
+ annot = Annotation.objects.get(external_id=id)
+ except Annotation.DoesNotExist:
+ raise Http404
+
+ doc = annot.serialize()
+
+ return HttpResponse(lxml.etree.tostring(doc, pretty_print=True), mimetype="text/xml;charset=utf-8")
+
+
+## Deletes an annotation (from its id)
+## Returns an empty xml-structured annotation
+@oauth_required
+@csrf_exempt
+def delete_annotation(request, id):
+ try:
+ annot = Annotation.objects.get(external_id=id)
+ annot.delete()
+ except Annotation.DoesNotExist:
+ raise Http404
+
+ return HttpResponse("")
+
+
+## Updates the content of an annotation
+## Returns the xml-structured updated annotation
+@oauth_required
+@csrf_exempt
+def update_annotation(request, id):
+
+ put_data={}
+ if request.GET != {}:
+ put_data = request.GET.copy()
+ elif request.raw_post_data is not None:
+ for k,v in urlparse.parse_qs(request.raw_post_data).iteritems():
+ if len(v)>1:
+ for item in v:
+ put_data[k]= item
+ else:
+ put_data[k]= v[0]
+
+ try:
+ annot = Annotation.objects.get(external_id=id)
+ except Annotation.DoesNotExist:
+ raise Http404
+
+ cont = put_data['content']
+ doc = lxml.etree.fromstring(cont)
+
+ uri = doc.xpath("/iri/text-annotation/uri/text()")
+ if uri != [] and annot.uri != uri[0]:
+ annot.uri = unicode(uri[0])
+
+ tags_nodes = doc.xpath("/iri/text-annotation/tags")
+
+ if len(tags_nodes) > 0:
+ tags = list(set([unicode(tag.text).lower().strip() for tag in doc.xpath("/iri/text-annotation/tags/tag")]))
+ tags_str = ",".join(tags)
+ if len(tags) == 1:
+ tags_str += ","
+ annot.tags = tags_str
+
+ title = doc.xpath("/iri/text-annotation/content/title/text()")
+ if title and annot.title != title[0]:
+ annot.title = unicode(title[0])
+ desc = doc.xpath("/iri/text-annotation/content/description/text()")
+ if desc and annot.description != desc[0]:
+ annot.description = unicode(desc[0])
+ text = doc.xpath("/iri/text-annotation/content/text/text()")
+ if text and annot.text != text[0]:
+ annot.text = unicode(text[0])
+ color = doc.xpath("/iri/text-annotation/content/color/text()")
+ if color and annot.color != color[0]:
+ annot.color = unicode(color[0])
+
+ contributor = doc.xpath("/iri/text-annotation/meta/contributor/text()")
+ if contributor and annot.contributor != contributor[0]:
+ annot.contributor = unicode(contributor[0])
+ update_date = doc.xpath("/iri/text-annotation/meta/modified/text()")
+ if update_date and annot.update_date != update_date[0]:
+ annot.update_date = unicode(update_date[0])
+
+ annot.save()
+ annot.update_index()
+
+ return HttpResponse(lxml.etree.tostring(annot.serialize(), pretty_print=True), mimetype="text/xml;charset=utf-8")
+
+
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/utils/__init__.py
--- a/src/ldt/ldt/utils/__init__.py Fri Feb 04 18:41:55 2011 +0100
+++ b/src/ldt/ldt/utils/__init__.py Fri Feb 11 11:56:11 2011 +0100
@@ -1,2 +1,4 @@
-
+def Property(func):
+ return property(**func())
+
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 src/ldt/ldt/utils/threading.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/utils/threading.py Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,44 @@
+import threading
+import inspect
+import ctypes
+
+def _async_raise(tid, exctype):
+ """raises the exception, performs cleanup if needed"""
+ if not inspect.isclass(exctype):
+ raise TypeError("Only types can be raised (not instances)")
+ res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
+ if res == 0:
+ raise ValueError("invalid thread id")
+ elif res != 1:
+ # """if it returns a number greater than one, you're in trouble,
+ # and you should call it again with exc=NULL to revert the effect"""
+ ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
+ raise SystemError("PyThreadState_SetAsyncExc failed")
+
+
+class Thread(threading.Thread):
+ def _get_my_tid(self):
+ """determines this (self's) thread id"""
+ if not self.isAlive():
+ raise threading.ThreadError("the thread is not active")
+
+ # do we have it cached?
+ if hasattr(self, "_thread_id"):
+ return self._thread_id
+
+ # no, look for it in the _active dict
+ for tid, tobj in threading._active.items():
+ if tobj is self:
+ self._thread_id = tid
+ return tid
+
+ raise AssertionError("could not determine the thread's id")
+
+ def raise_exc(self, exctype):
+ """raises the given exception type in the context of this thread"""
+ _async_raise(self._get_my_tid(), exctype)
+
+ def terminate(self):
+ """raises SystemExit in the context of the given thread, which should
+ cause the thread to exit silently (unless caught)"""
+ self.raise_exc(SystemExit)
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/create_python_env.py
--- a/virtualenv/web/create_python_env.py Fri Feb 04 18:41:55 2011 +0100
+++ b/virtualenv/web/create_python_env.py Fri Feb 11 11:56:11 2011 +0100
@@ -59,11 +59,15 @@
EXTRA_TEXT += " 'DJANGO-EXTENSIONS' : { 'setup': 'django-extensions', 'url':'https://github.com/django-extensions/django-extensions/tarball/0.6', 'local':'"+ os.path.abspath(os.path.join(src_base,"django-extensions-0.6.tar.gz")).replace("\\","/")+"' },\n"
EXTRA_TEXT += " 'DJANGO-REGISTRATION' : { 'setup': 'django-registration', 'url':'http://bitbucket.org/ubernostrum/django-registration/get/tip.tar.gz', 'local':'"+ os.path.abspath(os.path.join(src_base,"django-registration.tar.gz")).replace("\\","/")+"' },\n"
EXTRA_TEXT += " 'DJANGO-TAGGING' : { 'setup': 'django-tagging', 'url':'http://django-tagging.googlecode.com/files/django-tagging-0.3.1.tar.gz', 'local':'"+ os.path.abspath(os.path.join(src_base,"django-tagging-0.3.1.tar.gz")).replace("\\","/")+"' },\n"
-EXTRA_TEXT += " 'DJANGO-PISTON' : { 'setup': 'django-piston', 'url':'http://bitbucket.org/jespern/django-piston/downloads/django-piston-0.2.2.tar.gz', 'local':'"+ os.path.abspath(os.path.join(src_base,"django-piston-0.2.2.tar.gz")).replace("\\","/")+"' },\n"
+EXTRA_TEXT += " 'DJANGO-PISTON' : { 'setup': 'django-piston', 'url':'"+ os.path.abspath(os.path.join(src_base,"django-piston-0.2.2-modified.tar.gz")).replace("\\","/")+"', 'local':'"+ os.path.abspath(os.path.join(src_base,"django-piston-0.2.2-modified.tar.gz")).replace("\\","/")+"' },\n"
if sys.platform == 'win32':
EXTRA_TEXT += " 'LXML' : { 'setup': 'lxml', 'url': 'http://pypi.python.org/packages/2.6/l/lxml/lxml-2.2.8-py2.6-win32.egg', 'local': '"+ os.path.abspath(os.path.join(src_base,"lxml-2.2.8-py2.6-win32.egg")).replace("\\","/")+"'},\n"
else:
EXTRA_TEXT += " 'LXML' : { 'setup': 'lxml', 'url': '"+ os.path.abspath(os.path.join(src_base,"lxml_2.2.8.tar.gz"))+"', 'local': '"+ os.path.abspath(os.path.join(src_base,"lxml-2.2.8.tar.gz")).replace("\\","/")+"'},\n"
+EXTRA_TEXT += " 'SETUPTOOLS-HG' : { 'setup': 'setuptools-hg', 'url':'http://bitbucket.org/jezdez/setuptools_hg/downloads/setuptools_hg-0.2.tar.gz', 'local':'"+ os.path.abspath(os.path.join(src_base,"setuptools_hg-0.2.tar.gz")).replace("\\","/")+"' },\n"
+EXTRA_TEXT += " 'OAUTH2' : { 'setup': 'python-oauth2', 'url':'"+ os.path.abspath(os.path.join(src_base,"python-oauth2-1.2.1-modified.tar.gz")).replace("\\","/")+"', 'local':'"+ os.path.abspath(os.path.join(src_base,"python-oauth2-1.2.1-modified.tar.gz")).replace("\\","/")+"' },\n"
+EXTRA_TEXT += " 'HTTPLIB2' : { 'setup': 'python-oauth2', 'url':'http://httplib2.googlecode.com/files/httplib2-0.6.0.tar.gz', 'local':'"+ os.path.abspath(os.path.join(src_base,"httplib2-0.6.0.tar.gz")).replace("\\","/")+"' },\n"
+EXTRA_TEXT += " 'DJANGO-OAUTH-PLUS' : { 'setup': 'django-oauth-plus', 'url':'http://bitbucket.org/david/django-oauth-plus/get/f314f018e473.gz', 'local':'"+ os.path.abspath(os.path.join(src_base,"django-oauth-plus.tar.gz")).replace("\\","/")+"' },\n"
EXTRA_TEXT += "}\n"
EXTRA_TEXT += "import sys\n"
@@ -227,6 +231,10 @@
('DJANGO-REGISTRATION', 'easy_install', '-Z', None),
('DJANGO-TAGGING', 'pip', None, None),
('DJANGO-PISTON', 'pip', None, None),
+ ('SETUPTOOLS-HG', 'pip', None, None),
+ ('HTTPLIB2', 'pip', None, None),
+ ('OAUTH2', 'pip', None, None),
+ ('DJANGO-OAUTH-PLUS', 'pip', None, None),
]
if 'PYLUCENE' not in ignore_packages and system_str == "Windows":
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/README
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/virtualenv/web/res/README Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,22 @@
+README - Platform virtualenv lib modifications
+-------------------------------------------------
+
+1. DJANGO-PISTON
+https://bitbucket.org/jespern/django-piston/overview
+------------------------------------------------------
+Model name collision with python-oauth.
+Described here: https://bitbucket.org/david/django-oauth/issue/3/collision-with-django-piston-on-syncdb
+
+Piston and Django-oauth use the same model for Tokens, and same related name to their ForeignKey User.
+One of the related_name has to be modified.
+See platform/virtualenv/web/res/patch/piston.diff
+
+
+2. PYTHON-OAUTH2
+https://github.com/simplegeo/python-oauth2
+---------------------------------------------
+Request paramters are not handled correctly, and sometimes appear twice in the request query_string.
+Described here: https://github.com/simplegeo/python-oauth2/issues#issue/21
+
+Modification made in get_normalized_parameters in order to avoid doubling the parameters, and making the request non-valid.
+See platform/virtualenv/web/res/patch/oauth2.diff
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/patch/oauth2.diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/virtualenv/web/res/patch/oauth2.diff Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,11 @@
+diff -r 7ea87e3229fd5c4eebd0 oauth2/__init__.py
+@@ -385,386 +385,391 @@
+ url_items = self._split_url_string(query).items()
+ non_oauth_url_items = list([(k, v) for k, v in url_items if not k.startswith('oauth_')])
+
+- items.extend(non_oauth_url_items)
++ for (key,value) in non_oauth_url_items:
++ if (key,value) not in items:
++ items.append((key,value))
+
+ encoded_str = urllib.urlencode(sorted(items))
\ No newline at end of file
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/patch/piston.diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/virtualenv/web/res/patch/piston.diff Fri Feb 11 11:56:11 2011 +0100
@@ -0,0 +1,8 @@
+diff -r 278:c4b2d21db51a piston/models.py
+@@ -118 +118 @@
+ timestamp = models.IntegerField()
+ is_approved = models.BooleanField(default=False)
+
+- user = models.ForeignKey(User, null=True, blank=True, related_name='piston_tokens')
++ user = models.ForeignKey(User, null=True, blank=True, related_name='piston_tokens')
+ consumer = models.ForeignKey(Consumer)
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/src/django-oauth-plus.tar.gz
Binary file virtualenv/web/res/src/django-oauth-plus.tar.gz has changed
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/src/django-piston-0.2.2-modified.tar.gz
Binary file virtualenv/web/res/src/django-piston-0.2.2-modified.tar.gz has changed
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/src/django-piston-0.2.2.tar.gz
Binary file virtualenv/web/res/src/django-piston-0.2.2.tar.gz has changed
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/src/httplib2-0.6.0.tar.gz
Binary file virtualenv/web/res/src/httplib2-0.6.0.tar.gz has changed
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/src/python-oauth2-1.2.1-modified.tar.gz
Binary file virtualenv/web/res/src/python-oauth2-1.2.1-modified.tar.gz has changed
diff -r a5f517ac1c03 -r b3703b98ce32 virtualenv/web/res/src/setuptools_hg-0.2.tar.gz
Binary file virtualenv/web/res/src/setuptools_hg-0.2.tar.gz has changed
diff -r a5f517ac1c03 -r b3703b98ce32 web/ldtplatform/settings.py
--- a/web/ldtplatform/settings.py Fri Feb 04 18:41:55 2011 +0100
+++ b/web/ldtplatform/settings.py Fri Feb 11 11:56:11 2011 +0100
@@ -104,6 +104,10 @@
os.path.join(os.path.basename(__file__), 'templates'),
)
+FIXTURES_DIRS = (
+ os.path.join(os.path.basename(__file__), 'fixtures'),
+)
+
INSTALLED_APPS = (
'jogging',
'django_extensions',
@@ -119,8 +123,10 @@
'ldt',
'ldt.core',
'ldt.ldt_utils',
+ 'ldt.text',
'ldt.user',
'ldt.management',
+ 'oauth_provider',
)
DECOUPAGE_BLACKLIST = (
@@ -136,11 +142,18 @@
LDT_MAX_SEARCH_NUMBER = 50
LDT_JSON_DEFAULT_INDENT = 2
+OAUTH_PROVIDER_KEY_SIZE = 32
+OAUTH_PROVIDER_SECRET_SIZE = 32
+OAUTH_PROVIDER_VERIFIER_SIZE = 10
+OAUTH_PROVIDER_CONSUMER_KEY_SIZE = 256
+OAUTH_AUTHORIZE_VIEW = 'oauth_provider.views.fake_authorize_view'
+OAUTH_CALLBACK_VIEW = 'oauth_provider.views.fake_callback_view'
+TEST_WEBSERVER_ADDRPORT = "127.0.0.1:8000"
from config import *
-LOGIN_URL = BASE_URL + 'ldtplatform/accounts/login/'
-LOGOUT_URL = BASE_URL + 'ldtplatform/accounts/logout/'
+LOGIN_URL = BASE_URL + 'platform/accounts/login/'
+LOGOUT_URL = BASE_URL + 'platform/accounts/logout/'
LOGIN_REDIRECT_URL = BASE_URL + 'ldtplatform'
GLOBAL_LOG_LEVEL = LOG_LEVEL
diff -r a5f517ac1c03 -r b3703b98ce32 web/ldtplatform/urls.py
--- a/web/ldtplatform/urls.py Fri Feb 04 18:41:55 2011 +0100
+++ b/web/ldtplatform/urls.py Fri Feb 11 11:56:11 2011 +0100
@@ -1,5 +1,6 @@
-from django.conf.urls.defaults import patterns ,include
+from django.conf.urls.defaults import patterns, include, handler500, handler404
from django.contrib import admin
+from ldt.text import VERSION_STR
# Uncomment the next two lines to enable the admin:
@@ -19,8 +20,10 @@
(r'^ldt/', include('ldt.ldt_utils.urls')),
(r'^user/', include('ldt.user.urls')),
(r'^api/', include('ldt.api.urls')),
+ (r'^api/' + VERSION_STR + '/text/', include('ldt.text.urls')),
(r'^accounts/', include('registration.backends.simple.urls')),
+ (r'^oauth/', include('oauth_provider.urls')),
(r'^/?$', 'django.views.generic.simple.redirect_to', {'url': 'ldt'}),
)