# HG changeset patch # User ymh # Date 1297427755 -3600 # Node ID d5decd9e80cfbb91ff0f27d0c46a0ea930731246 # Parent 57a2650a7f8701ab9f492487fd113c47312afafc# Parent b3703b98ce32f39f3f3537fbb20c6d8d3b247788 Merge with b3703b98ce32f39f3f3537fbb20c6d8d3b247788 diff -r 57a2650a7f87 -r d5decd9e80cf src/ldt/ldt/management/commands/__init__.py diff -r 57a2650a7f87 -r d5decd9e80cf src/ldt/ldt/management/commands/testrunserver.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldt/ldt/management/commands/testrunserver.py Fri Feb 11 13:35:55 2011 +0100 @@ -0,0 +1,79 @@ +from django.core.management.commands.runserver import Command as RunserverCommand +from django.core.servers.basehttp import WSGIServer, WSGIRequestHandler +import sys +import threading +from threading import Event +from django.conf import settings + +def run(addr, port, wsgi_handler, keep_running=None, ready_event=None): + server_address = (addr, port) + httpd = WSGIServer(server_address, WSGIRequestHandler) + httpd.set_app(wsgi_handler) + if keep_running is None: + if ready_event is not None: + ready_event.set() + httpd.serve_forever() + else: + if ready_event is not None: + ready_event.set() + while keep_running(): + httpd.handle_request() + + + +class Command(RunserverCommand): + def handle(self, addrport='', keep_running=None, ready_event=None, *args, **options): + import django + from django.core.servers.basehttp import AdminMediaHandler, WSGIServerException + from django.core.handlers.wsgi import WSGIHandler + if args: + raise CommandError('Usage is testrunserver %s' % self.args) + if not addrport: + addr = '' + port = '8000' + else: + try: + addr, port = addrport.split(':') + except ValueError: + addr, port = '', addrport + if not addr: + addr = '127.0.0.1' + + if not port.isdigit(): + raise CommandError("%r is not a valid port number." % port) + + admin_media_path = options.get('admin_media_path', '') + shutdown_message = options.get('shutdown_message', '') + quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C' + + from django.conf import settings + from django.utils import translation + print "Validating models..." + self.validate(display_num_errors=True) + print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE) + print "Development server is running at http://%s:%s/" % (addr, port) + print "Quit the server with %s." % quit_command + + translation.activate(settings.LANGUAGE_CODE) + + try: + handler = AdminMediaHandler(WSGIHandler(), admin_media_path) + run(addr, int(port), handler, keep_running, ready_event) + except WSGIServerException, e: + # Use helpful error messages instead of ugly tracebacks. + ERRORS = { + 13: "You don't have permission to access that port.", + 98: "That port is already in use.", + 99: "That IP address can't be assigned-to.", + } + try: + error_text = ERRORS[e.args[0].args[0]] + except (AttributeError, KeyError): + error_text = str(e) + sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n') + # Need to use an OS exit because sys.exit doesn't work in a thread + except KeyboardInterrupt: + if shutdown_message: + print shutdown_message + + \ No newline at end of file diff -r 57a2650a7f87 -r d5decd9e80cf src/ldt/ldt/settings.py --- a/src/ldt/ldt/settings.py Fri Feb 11 13:31:26 2011 +0100 +++ b/src/ldt/ldt/settings.py Fri Feb 11 13:35:55 2011 +0100 @@ -14,6 +14,7 @@ INSTALLED_APPS = ( 'jogging', + 'django_extensions', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -25,8 +26,10 @@ 'ldt', 'ldt.core', 'ldt.ldt_utils', + 'ldt.text', 'ldt.user', 'ldt.management', + 'oauth_provider', ) MIDDLEWARE_CLASSES = ( @@ -58,7 +61,7 @@ MEDIA_URL = getattr(settings, 'MEDIA_URL', '') MEDIA_ROOT = getattr(settings, 'MEDIA_ROOT', '') SITE_ID = getattr(settings, 'SITE_ID', 1) -DEBUG = getattr(settings, 'DEBUG', False) +DEBUG = getattr(settings, 'DEBUG', True) MANAGERS = settings.MANAGERS INSTALLED_APPS = settings.INSTALLED_APPS LANGUAGES = settings.LANGUAGES @@ -73,6 +76,7 @@ GLOBAL_LOG_LEVEL = LOG_LEVEL GLOBAL_LOG_HANDLERS = [logging.FileHandler(LOG_FILE)] +TEST_WEBSERVER_ADDRPORT = getattr(settings, 'TEST_WEBSERVER_ADDRPORT', '127.0.0.1:8000') ACCOUNT_ACTIVATION_DAYS = getattr(settings, 'ACCOUNT_ACTIVATION_DAYS', 7) LDT_MEDIA_PREFIX = getattr(settings, 'LDT_MEDIA_PREFIX', MEDIA_URL + 'ldt/') diff -r 57a2650a7f87 -r d5decd9e80cf src/ldt/ldt/templates/404.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldt/ldt/templates/404.html Fri Feb 11 13:35:55 2011 +0100 @@ -0,0 +1,1 @@ +

Not Found!

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