Added text API, test Clients and Testcases
authorwakimd
Fri, 11 Feb 2011 11:51:35 +0100
changeset 22 03d02cf0bea7
parent 18 2984757e6d98
child 23 b3703b98ce32
Added text API, test Clients and Testcases
src/ldt/ldt/management/commands/__init__.py
src/ldt/ldt/management/commands/testrunserver.py
src/ldt/ldt/settings.py
src/ldt/ldt/templates/404.html
src/ldt/ldt/test/__init__.py
src/ldt/ldt/test/client.py
src/ldt/ldt/test/testcases.py
src/ldt/ldt/text/__init__.py
src/ldt/ldt/text/admin.py
src/ldt/ldt/text/annotindexer.py
src/ldt/ldt/text/fixtures/base_data.json
src/ldt/ldt/text/fixtures/filter_data.json
src/ldt/ldt/text/fixtures/oauth_data.json
src/ldt/ldt/text/fixtures/test_data.json
src/ldt/ldt/text/models.py
src/ldt/ldt/text/tests/__init__.py
src/ldt/ldt/text/tests/base_tests.py
src/ldt/ldt/text/tests/oauth_tests.py
src/ldt/ldt/text/tests/server_tests.py
src/ldt/ldt/text/tests/utils.py
src/ldt/ldt/text/urls.py
src/ldt/ldt/text/utils.py
src/ldt/ldt/text/views.py
src/ldt/ldt/utils/__init__.py
src/ldt/ldt/utils/threading.py
virtualenv/web/create_python_env.py
virtualenv/web/res/README
virtualenv/web/res/patch/oauth2.diff
virtualenv/web/res/patch/piston.diff
virtualenv/web/res/src/django-oauth-plus.tar.gz
virtualenv/web/res/src/django-piston-0.2.2-modified.tar.gz
virtualenv/web/res/src/django-piston-0.2.2.tar.gz
virtualenv/web/res/src/httplib2-0.6.0.tar.gz
virtualenv/web/res/src/python-oauth2-1.2.1-modified.tar.gz
virtualenv/web/res/src/setuptools_hg-0.2.tar.gz
web/ldtplatform/settings.py
web/ldtplatform/urls.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/management/commands/testrunserver.py	Fri Feb 11 11:51:35 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
--- a/src/ldt/ldt/settings.py	Fri Feb 04 17:18:16 2011 +0100
+++ b/src/ldt/ldt/settings.py	Fri Feb 11 11:51:35 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/')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/templates/404.html	Fri Feb 11 11:51:35 2011 +0100
@@ -0,0 +1,1 @@
+<h1>Not Found!</h1>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/test/client.py	Fri Feb 11 11:51:35 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'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/test/testcases.py	Fri Feb 11 11:51:35 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/__init__.py	Fri Feb 11 11:51:35 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)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/admin.py	Fri Feb 11 11:51:35 2011 +0100
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from django.conf import settings
+from models import *
+
+admin.site.register(Annotation)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/annotindexer.py	Fri Feb 11 11:51:35 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/base_data.json	Fri Feb 11 11:51:35 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"
+    }
+  }
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/filter_data.json	Fri Feb 11 11:51:35 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"
+    }
+  }
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/oauth_data.json	Fri Feb 11 11:51:35 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/fixtures/test_data.json	Fri Feb 11 11:51:35 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": ""
+    }
+  }
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/models.py	Fri Feb 11 11:51:35 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/__init__.py	Fri Feb 11 11:51:35 2011 +0100
@@ -0,0 +1,3 @@
+from base_tests import *
+from oauth_tests import *
+from server_tests import *
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/base_tests.py	Fri Feb 11 11:51:35 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('<iri><text-annotation><id>'+self.id+'</id><uri>http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168</uri><tags><tag>tag1</tag><tag>tag2</tag></tags><content><color>#AAAAAA</color><description><![CDATA[texte de description]]></description><title><![CDATA[titre de l\'annotation]]></title><text>'+self.text+'</text></content><meta><contributor>oaubert</contributor><contributor-id>79cd0532-1dda-4130-b351-6a181130a7c9</contributor-id><created>2010-09-06 12:33:53.417550</created><creator>oaubert</creator><creator-id>79cd0532-1dda-4130-b351-6a181130a7c9</creator-id><modified>2010-09-06 12:33:53.420459</modified></meta></text-annotation></iri>')
+        self.error_content = '<iri><text-annotation><id>z2c1d1fa-629d-4520-a3d2-955b4f2582c0</id><uri>http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168</uri><tags><tag>tag1</tag><tag>tag2</tag></tags><content><color>#AAAAAA</color><description><![CDATA[texte de description]]></description><title><![CDATA[titre de l\'annotation]]></title><text><![CDATA[texte selectionne lors de la creation de l\'annotation]]></text></content><meta><contributor>oaubert</contributor><contributor-id>79cd0532-1dda-4130-b351-6a181130a7c9</contributor-id><created>2010-09-06 12:33:53.417550</created><creator>oaubert</creator><creator-id>79cd0532-1dda-4130-b351-6a181130a7c9</creator-id><modified>2010-09-06 12:33:53.420459</modified></meta></text-annotation></iri>' 
+        
+        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, '<iri/>\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 = '<iri><text-annotation><id></id><uri></uri><tags><tag>tag2new</tag><tag>mytag</tag></tags><content><color>'+self.color+'</color><description>'+self.description+'</description><title></title><text>'+self.text+'</text></content><meta><contributor>'+self.contributor+'</contributor><contributor-id>80cd0532-1dda-4130-b351-6a181130a7c9</contributor-id><created></created><creator></creator><creator-id></creator-id><modified>2010-11-06 12:33:53.420459</modified></meta></text-annotation></iri>'
+        
+        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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/oauth_tests.py	Fri Feb 11 11:51:35 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'] = '<iri><text-annotation><id>f2c1d1fa-629d-4520-a3d2-955b4f2582c0</id><uri>http://www.leezam.com/pub/epub/123456!/OPS/chapter2.xhtml#pos=56,168</uri><tags><tag>tag1</tag><tag>tag2</tag></tags><content><color>#AAAAAA</color><description><![CDATA[texte de description]]></description><title><![CDATA[titre de l\'annotation]]></title><text><![CDATA[texte selectionne lors de la creation de l\'annotation]]></text></content><meta><contributor>oaubert</contributor><contributor-id>79cd0532-1dda-4130-b351-6a181130a7c9</contributor-id><created>2010-09-06 12:33:53.417550</created><creator>oaubert</creator><creator-id>79cd0532-1dda-4130-b351-6a181130a7c9</creator-id><modified>2010-09-06 12:33:53.420459</modified></meta></text-annotation></iri>'
+        
+        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'] = '<iri><text-annotation><id></id><uri></uri><tags><tag>tag2new</tag><tag>mytag</tag></tags><content><color>#DDDDDD</color><description><![CDATA[texte de description update]]></description><title></title><text><![CDATA[texte selectionne a nouveau lors de la creation de l\'annotation]]></text></content><meta><contributor>oaubert</contributor><contributor-id>80cd0532-1dda-4130-b351-6a181130a7c9</contributor-id><created></created><creator></creator><creator-id></creator-id><modified>2010-11-06 12:33:53.420459</modified></meta></text-annotation></iri>'
+        
+        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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/server_tests.py	Fri Feb 11 11:51:35 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 = '<iri><text-annotation><id>'+self.id+'</id><uri>'+self.uri+'</uri><tags><tag>tag1</tag><tag>tag2</tag></tags><content><color>#AAAAAA</color><description><![CDATA[texte de description]]></description><title>'+self.title+'</title><text>'+self.text+'</text></content><meta><contributor>oaubert</contributor><contributor-id>79cd0532-1dda-4130-b351-6a181130a7c9</contributor-id><created>2010-09-06 12:33:53.417550</created><creator>'+self.creator+'</creator><creator-id>79cd0532-1dda-4130-b351-6a181130a7c9</creator-id><modified>2010-09-06 12:33:53.420459</modified></meta></text-annotation></iri>'        
+        self.contentupdate = '<iri><text-annotation><id></id><uri></uri><tags><tag>tag1</tag><tag>tag2new</tag><tag>tag3</tag></tags><content><color>#DDDDDD</color><description>'+self.descupdate+'</description><title></title><text>'+self.textupdate+'</text></content><meta><contributor>'+self.contributor+'</contributor><contributor-id>80cd0532-1dda-4130-b351-6a181130a7c9</contributor-id><created></created><creator></creator><creator-id></creator-id><modified>2010-11-06 12:33:53.420459</modified></meta></text-annotation></iri>'
+        
+        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, '<iri/>\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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/tests/utils.py	Fri Feb 11 11:51:35 2011 +0100
@@ -0,0 +1,1 @@
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/urls.py	Fri Feb 11 11:51:35 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<id>.*)$', 'views.get_annotation'),
+    url(r'^delete/(?P<id>.*)$', 'views.delete_annotation'),
+    url(r'^update/(?P<id>.*)$', 'views.update_annotation'),
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/utils.py	Fri Feb 11 11:51:35 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)
+    
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/text/views.py	Fri Feb 11 11:51:35 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")
+
+    
--- a/src/ldt/ldt/utils/__init__.py	Fri Feb 04 17:18:16 2011 +0100
+++ b/src/ldt/ldt/utils/__init__.py	Fri Feb 11 11:51:35 2011 +0100
@@ -1,2 +1,4 @@
 
-    
+def Property(func):
+    return property(**func())
+    
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/utils/threading.py	Fri Feb 11 11:51:35 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)
--- a/virtualenv/web/create_python_env.py	Fri Feb 04 17:18:16 2011 +0100
+++ b/virtualenv/web/create_python_env.py	Fri Feb 11 11:51:35 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":
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/virtualenv/web/res/README	Fri Feb 11 11:51:35 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/virtualenv/web/res/patch/oauth2.diff	Fri Feb 11 11:51:35 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/virtualenv/web/res/patch/piston.diff	Fri Feb 11 11:51:35 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)
Binary file virtualenv/web/res/src/django-oauth-plus.tar.gz has changed
Binary file virtualenv/web/res/src/django-piston-0.2.2-modified.tar.gz has changed
Binary file virtualenv/web/res/src/django-piston-0.2.2.tar.gz has changed
Binary file virtualenv/web/res/src/httplib2-0.6.0.tar.gz has changed
Binary file virtualenv/web/res/src/python-oauth2-1.2.1-modified.tar.gz has changed
Binary file virtualenv/web/res/src/setuptools_hg-0.2.tar.gz has changed
--- a/web/ldtplatform/settings.py	Fri Feb 04 17:18:16 2011 +0100
+++ b/web/ldtplatform/settings.py	Fri Feb 11 11:51:35 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
--- a/web/ldtplatform/urls.py	Fri Feb 04 17:18:16 2011 +0100
+++ b/web/ldtplatform/urls.py	Fri Feb 11 11:51:35 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'}),
 )