# HG changeset patch # User cavaliet # Date 1350922959 -7200 # Node ID f52567ae138c62ef2a7ca7f33bd644cf1e1ffccd # Parent 834ceff0696638add5941348ddeb7ee7f76f03a0 first commit with new rest api urls and resources. diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/api/ldt/handlers.py --- a/src/ldt/ldt/api/ldt/handlers.py Mon Oct 22 18:19:12 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,291 +0,0 @@ -from ldt.ldt_utils.models import Project, Content, Segment -from django.db.models import F, Q -from piston.handler import BaseHandler -from piston.utils import rc, require_extended -from ldt.ldt_utils.utils import LdtAnnotation -from ldt.ldt_utils.stat import add_annotation_to_stat -from ldt.security import protect_models, unprotect_models -from ldt.ldt_utils.segmentserializer import SegmentSerializer -import logging #@UnresolvedImport - - -class ProjectHandler(BaseHandler): - allowed_methods = ('GET', 'PUT',) - model = Project - - def read(self, request, project_id): - """ - returns a single project - """ - return Project.objects.get(ldt_id=project_id) - - @require_extended - def update(self, request, project_id): - #return rc.ALL_OK - logging.debug("request " + repr(request)) - data = request.data - ldt_str = data["ldt"] - - logging.debug("request data" + repr(ldt_str)) - - if not ldt_str: - return rc.BAD_REQUEST - - project = Project.objects.get(ldt_id=project_id) - - project.ldt = ldt_str - - unprotect_models() - project.save() - protect_models() - - return rc.ALL_OK - - -class AnnotationHandler(BaseHandler): - allowed_methods = ('PUT',) - - @require_extended - def update(self, request, project_id): - """ - This method is called when a PUT request is sent to http:///api/ldt/projects/.. - is the ldt_id field of the project. If does not match any project on the platform, a 410 ("Gone") - error will be returned. - is the format of the data sent back by the server. It can be 'json', 'yaml', 'xml' or 'pickle'. - - If the request contains a content-type header whose value is set to "application/json" and a valid utf-8 encoded JSON file, - the following conditions will be checked before the annotations are added : - If the submitted file is not valid or refers to a media that is not contained in the project, a 500 ("Bad Request") - error will be returned. If the "type" field of an annotation matches an already existing cutting, it will be added to that - cutting. Otherwise, a new cutting will be added (as well as a new ensemble if needed). New cuttings are added to the view - "View at the last recording" if it exists, or to the view "Init view" else. If none of those views exist, the server will - not add the cutting to a view. Several annotations can be added at the same time if the submitted file contains multiple - annotations. The server returns the file submitted if all annotations have been added successfully, and adds to this file - IDs of created annotations to the file with a 200("OK") error code. - - If no content-type header is set, the file submitted must be a valid XML file and will replace entirely the ldt field - of the project without any verifications. - - Example : - - Remark : The file below contain the minimum necessary fields and attributes for the handler to work. If one field or attribute is - missing (e.g. author, or date) during submission, an error will occur. - - A platform is reachable at http://localhost/. It contains a project with ID a0593b58-f258-11df-80e1-00145ea4a2be. This project has - a content milosforman_amadeus, which has a cutting Salieri inside the view "View at the last recording". The following JSON file exists in the current directory : - - Example of ajax call with 2 differents annotations : - $('#mon_ajax').click(function(e) { - var url = "{% url project_api project_id='c8448f21-272d-11e1-876b-c8bcc896c290' emitter_format='.json' %}"; // Don't forget the "." before "json" ! - - var monjson = '{\ - "annotations": [\ - {\ - "type": "c_07BA1284-5F24-71A8-1EE2-423EED999B8A",\ - "type_title": "New cutting name if necessary",\ - "media": "briandepalma_scarfacedepalma",\ - "begin": 1600000,\ - "end": 2100000,\ - "content": {\ - "data": "new scar annot"\ - "audio": {\ - "mimetype": "audio/mp3",\ - "src": "mic",\ - "href": "rtmp://media.iri.centrepompidou.fr/ddc_micro_record/r_20120606190143793"\ - }\ - },\ - "tags": [ "json","dude" ]\ - }\ - ],\ - "meta": {\ - "creator": "John Doe",\ - "created": "2011-09-10T09:12:58"\ - }\ - }'; - var monjson2 = '{\ - "annotations": [\ - {\ - "type": "c_07BA1284-5F24-71A8-1EE2-423EED999B8A",\ - "type_title": "New cutting name if necessary",\ - "media": "briandepalma_scarfacedepalma",\ - "begin": 2400000,\ - "end": 3000000,\ - "content": {\ - "data": "ntm iam 2"\ - },\ - "tags": [ "jak" ]\ - }\ - ],\ - "meta": {\ - "creator": "John Doe",\ - "created": "2011-09-10T09:12:58"\ - }\ - }'; - - $.ajax({ - url: url, - type: 'PUT', - contentType: 'application/json', - data: monjson, - // bug with jquery >= 1.5, "json" adds a callback so we don't specify dataType - //dataType: 'json', - success: function(json, textStatus, XMLHttpRequest) { - alert("success = " + json); - }, - error: function(jqXHR, textStatus, errorThrown) { - alert("ERROR = " + jqXHR.responseText + ", " + errorThrown); - } - }); - }); - - If we send a PUT request with curl : - $curl -X PUT http://localhost/api/ldt/projects/a0593b58-f258-11df-80e1-00145ea4a2be.json -d @example.JSON -H "content-type:application/json" - A new cutting titled "New cutting name" will be created with the first annotation inside, and the annotation "Annotation about Salieri" - will be added to the Salieri cutting. The returned file is : - - { - "annotations": [ - { - "id": "6d8baf01-ffb1-11e0-810c-001485352c9a", - "type": "id_annot_type", - "type_title": "New cutting name", - "media": "milosforman_amadeus", - "begin": 50000, - "end": 900000, - "content": { - "data": "new annotation" - }, - "tags": [ "json" ] - }, - { - "id": "6d8baf00-ffb1-11e0-8097-001485352c9b", - "type": "another_id_annot_type", - "type_title": "Salieri", - "media": "milosforman_amadeus", - "begin": 700000, - "end": 1200000, - "content": { - "data": "Annotation about Salieri" - }, - "tags": [ "xml", "test", "blop" ] - } - ], - - "meta": { - "creator": "John Doe", - "created": "2011-09-10T09:12:58" - } - } - - """ - #return rc.ALL_OK - try: - project = Project.objects.get(ldt_id=project_id) - except Project.DoesNotExist: - return rc.NOT_HERE - - adder = LdtAnnotation(project) - logging.debug("request json " + repr(request.data)) - - unprotect_models() # Allows anonymous user to modify models in this request only - - meta = request.data['meta'] - author = meta['creator'] - date = meta['created'] - new_annotations = request.data['annotations'] - - for a in new_annotations: - dur = str(a['end'] - a['begin']) - begin = str(a['begin']) - # We test if the annotation has audio node - audio_src = "" - audio_href = "" - if a['content'].has_key('audio') : - if a['content']['audio'].has_key('src') : - audio_src = a['content']['audio']['src'] - if a['content']['audio'].has_key('href') : - audio_href = a['content']['audio']['href'] - type_id, new_id = adder.add(a['media'], a['type'], a['type_title'], a['content']['data'], '', a['tags'], begin, dur, author, date, None, "2194379", audio_src, audio_href) - if not new_id: - protect_models() - return rc.BAD_REQUEST - - - content = project.contents.get(iri_id=a['media']) - add_annotation_to_stat(content, a['begin'], a['end']) - - # We update the ids - a['type'] = type_id - a['id'] = new_id - if not a['content'].has_key('audio') : - a['content']['audio'] = {'src':audio_src, 'href':audio_href} - - # We save if there were added annotation - if len(new_annotations)>0 : - adder.save() - - protect_models() - - return request.data - - - -class ContentHandler(BaseHandler): - allowed_methods = ('GET', 'PUT') - model = Content - exclude = ( - ("media_obj"), - ) - - def read(self, request, iri_id): - """ - returns a single content - """ - return Content.objects.get(iri_id=iri_id) - - @require_extended - def update(self, request, iri_id): - """ - Receives a json exactly like AnnotationHandler, but without any project indicated. - We get or set the current content front project, and add the annotation - """ - try: - content = Content.objects.get(iri_id=iri_id) - except Content.DoesNotExist: - return rc.NOT_HERE - proj = content.get_or_create_front_project() - ah = AnnotationHandler() - updated_data = ah.update(request, proj.ldt_id) - - return updated_data - - -class SegmentHandler(BaseHandler): - allowed_methods = ('GET', ) - model = Segment - exclude = ( - ("project_obj"), - ("content"), - ) - - def read(self, request, iri_id, begin, end): - """ - returns segments about content iri_id between timecodes begin and end - """ - begin = int(begin) - end = int(end) - - content = Content.objects.filter(iri_id=iri_id) - if not content: - return rc.NOT_FOUND - content = content[0] - - segments = Segment.objects.filter(content=content).filter( - Q(start_ts__gte=begin, start_ts__lte=end) | # segment starts between begin and end - Q(start_ts__gte=begin-F('duration'), start_ts__lte=end-F('duration')) |# segment ends between begin and end - Q(start_ts__lte=begin, start_ts__gte=end-F('duration')) # period [begin:end] is included in the segment - ) - - a = SegmentSerializer(content, segments) - return a.serialize_to_cinelab() - diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/api/ldt/resources/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldt/ldt/api/ldt/resources/__init__.py Mon Oct 22 18:22:39 2012 +0200 @@ -0,0 +1,6 @@ +from annotation import AnnotationResource +from content import ContentResource +from project import ProjectResource +from segment import SegmentResource + +__all__ = ["AnnotationResource", "ContentResource", "ProjectResource", "SegmentResource"] diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/api/ldt/resources/annotation.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldt/ldt/api/ldt/resources/annotation.py Mon Oct 22 18:22:39 2012 +0200 @@ -0,0 +1,109 @@ +from ldt.ldt_utils.models import Project, Content +from ldt.ldt_utils.stat import add_annotation_to_stat +from ldt.ldt_utils.utils import LdtAnnotation +from ldt.security import protect_models, unprotect_models +from tastypie import fields +from tastypie.authorization import Authorization +from tastypie.exceptions import NotFound, BadRequest +from tastypie.resources import Resource +import logging #@UnresolvedImport + + +class AnnotationObject(object): + # For the moment, these attributes are useless. We just need to define AnnotationObject + id = "" + project = "" + type = "" + type_title = "" + media = "" + begin = "" + end = "" + content = {"data":""} + tags = [] + meta = {"creator":"","created":""} + +class AnnotationResource(Resource): + # For the moment, these attributes are useless. We just prepare relations to AnnotationObject + id = fields.CharField(attribute = 'id') + project = fields.CharField(attribute = 'project') + type = fields.CharField(attribute = 'type') + type_title = fields.CharField(attribute = 'type_title') + media = fields.CharField(attribute = 'media') + begin = fields.IntegerField(attribute = 'begin') + end = fields.IntegerField(attribute = 'end') + content = fields.DictField(attribute = 'content') + tags = fields.ListField(attribute = 'tags') + meta = fields.DictField(attribute = 'meta') + + class Meta: + allowed_methods = ['put'] + resource_name = 'annotations' + object_class = AnnotationObject + authorization = Authorization() + # always_return_data = True because we want the api returns the data with the updated ids + always_return_data = True + + def obj_delete_list(self, request=None, **kwargs): + return True + + def obj_create(self, bundle, request=None, **kwargs): + """ + """ + logging.debug("ICI 0-1 bundle.data = " + repr(bundle.data)) + project_id = "" + if bundle.data.has_key('project') : + project_id = bundle.data["project"] + if project_id and project_id != "" : + try: + project = Project.objects.get(ldt_id=project_id) + except Project.DoesNotExist: + raise NotFound("Project not found. project_id = " + project_id) + else : + # If the project's is not defined, we get or create the content's front project. + iri_id = bundle.data["media"] + try: + content = Content.objects.get(iri_id=iri_id) + except Content.DoesNotExist: + raise NotFound("Content not found. iri_id = " + iri_id) + project = content.get_or_create_front_project() + bundle.data["project"] = project.ldt_id + + adder = LdtAnnotation(project) + unprotect_models() # Allows anonymous user to modify models in this request only + # Here the bundle.data has the datas for only one annotation. The others api's functions parse the "objects" from the request's json. + a = bundle.data + dur = str(a['end'] - a['begin']) + begin = str(a['begin']) + # We test if the annotation has audio node + audio_src = "" + audio_href = "" + if a['content'].has_key('audio') : + if a['content']['audio'].has_key('src') : + audio_src = a['content']['audio']['src'] + if a['content']['audio'].has_key('href') : + audio_href = a['content']['audio']['href'] + meta = a['meta'] + author = meta['creator'] + date = meta['created'] + type_id, new_id = adder.add(a['media'], a['type'], a['type_title'], a['content']['data'], '', a['tags'], begin, dur, author, date, None, "2194379", audio_src, audio_href) + if not new_id: + protect_models() + raise BadRequest + + content = project.contents.get(iri_id=a['media']) + add_annotation_to_stat(content, a['begin'], a['end']) + + # We update the ids + bundle.data['type'] = type_id + bundle.data['id'] = new_id + if not bundle.data['content'].has_key('audio') : + bundle.data['content']['audio'] = {'src':audio_src, 'href':audio_href} + # We reinject the datas in the bundle's response + bundle = self.full_hydrate(bundle) + + # We save the added annotation and reprotect the contents and projects + adder.save() + protect_models() + return bundle + + \ No newline at end of file diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/api/ldt/resources/content.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldt/ldt/api/ldt/resources/content.py Mon Oct 22 18:22:39 2012 +0200 @@ -0,0 +1,18 @@ +from django.conf.urls.defaults import url +from ldt.ldt_utils.models import Content +from tastypie.resources import ModelResource + + +class ContentResource(ModelResource): + class Meta: + allowed_methods = ['get'] + resource_name = 'contents' + queryset = Content.objects.all() + excludes = ['media_obj'] + + def override_urls(self): + # WARNING : in tastypie <= 1.0, override_urls is used instead of prepend_urls. From 1.0.0, prepend_urls will be prefered and override_urls deprecated + return [ + url(r"^(?P%s)/(?P[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), + ] + \ No newline at end of file diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/api/ldt/resources/project.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldt/ldt/api/ldt/resources/project.py Mon Oct 22 18:22:39 2012 +0200 @@ -0,0 +1,23 @@ +from django.conf.urls.defaults import url +from ldt.ldt_utils.models import Project +from tastypie.authorization import Authorization +from tastypie.resources import ModelResource + + +class ProjectResource(ModelResource): + class Meta: + allowed_methods = ['get', 'put'] + authorization= Authorization() # BE CAREFUL WITH THAT, it's unsecure + resource_name = 'projects' + queryset = Project.objects.all() + +# # WARNING : this project API will only accepts and returns json format, no matter format get parameter. +# def determine_format(self, request): +# return "application/json" + + def override_urls(self): + # WARNING : in tastypie <= 1.0, override_urls is used instead of prepend_urls. From 1.0.0, prepend_urls will be prefered and override_urls deprecated + return [ + url(r"^(?P%s)/(?P[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), + ] + \ No newline at end of file diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/api/ldt/resources/segment.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ldt/ldt/api/ldt/resources/segment.py Mon Oct 22 18:22:39 2012 +0200 @@ -0,0 +1,99 @@ +from django.conf import settings +from django.conf.urls.defaults import url +from django.core.paginator import Paginator, InvalidPage +from django.db.models import F, Q +from ldt.indexation import get_results_with_context +from ldt.ldt_utils.models import Content, Segment +from ldt.ldt_utils.segmentserializer import SegmentSerializer +from tastypie.http import HttpNotFound +from tastypie.resources import ModelResource +from tastypie.utils import trailing_slash + + +class SegmentResource(ModelResource): + class Meta: + allowed_methods = ['get'] + resource_name = 'segments' + excludes = ['project_obj', 'content'] + queryset = Segment.objects.all() + filtering = { + 'iri_id': ['exact'], + 'start_ts': ['lte', 'gte'], + } + +# # WARNING : this segment API will only return json format, no matter format get parameter. +# def determine_format(self, request): +# return "application/json" + + def override_urls(self): + # WARNING : in tastypie <= 0.9.11, override_urls is used instead of prepend_urls. From 0.9.12, prepend_urls will be prefered and override_urls deprecated + return [ + url(r"^(?P%s)/search%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_search'), name="api_get_search"), + url(r"^(?P%s)/bytimecode%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_segments_by_timecode'), name='segment_api_empty'), + url(r"^(?P%s)/bytimecode/(?P.*)/(?P.*)/(?P.*)$" % self._meta.resource_name, self.wrap_view('get_segments_by_timecode'), name='segment_api'), + ] + + def get_search(self, request, **kwargs): + self.method_check(request, allowed=['get']) + # Do the query. + search = request.GET.get('q', '') + field = "all" + content_list = None + if u'author:' in search.lower() : + sub = search[7:] + sub = sub.upper() + if sub[0] != u'"': + sub = u'"' + sub + if sub[-1] != u'"': + sub = sub + u'"' + search = u'author:' + sub + results = get_results_with_context(field, search, content_list) + all_segments = Segment.objects.filter(element_id__in=[e['element_id'] for e in results]) + paginator = Paginator(all_segments, getattr(settings, 'API_LIMIT_PER_PAGE', 20)) + + try: + page = paginator.page(int(request.GET.get('page', 1))) + except InvalidPage: + raise HttpNotFound("Sorry, no results on that page.") + + objects = [] + + for segment in page.object_list: + bundle = self.build_bundle(obj=segment, request=request) + bundle = self.full_dehydrate(bundle) + objects.append(bundle) + + object_list = { + 'objects': objects, + } + + self.log_throttled_access(request) + return self.create_response(request, object_list) + + + + def get_segments_by_timecode(self, request, api_name, resource_name, iri_id=None, begin=None, end=None): + """ + returns segments about content iri_id between timecodes begin and end + """ + if not begin: + return HttpNotFound("begin timecode argument is missing.") + if not end: + return HttpNotFound("end timecode argument is missing.") + begin = int(begin) + end = int(end) + + content = Content.objects.filter(iri_id=iri_id) + if not content: + return HttpNotFound("Content does not exist or id is not correct.") + content = content[0] + + segments = Segment.objects.filter(content=content).filter( + Q(start_ts__gte=begin, start_ts__lte=end) | # segment starts between begin and end + Q(start_ts__gte=begin-F('duration'), start_ts__lte=end-F('duration')) |# segment ends between begin and end + Q(start_ts__lte=begin, start_ts__gte=end-F('duration')) # period [begin:end] is included in the segment + ) + + a = SegmentSerializer(content, segments) + return self.create_response(request, a.serialize_to_cinelab()) + \ No newline at end of file diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/api/ldt/urls.py --- a/src/ldt/ldt/api/ldt/urls.py Mon Oct 22 18:19:12 2012 +0200 +++ b/src/ldt/ldt/api/ldt/urls.py Mon Oct 22 18:22:39 2012 +0200 @@ -1,20 +1,13 @@ -from django.conf.urls.defaults import patterns, url -from django.views.defaults import page_not_found -from piston.resource import Resource -from ldt.api.ldt.handlers import ProjectHandler, AnnotationHandler, ContentHandler, SegmentHandler +from django.conf.urls.defaults import patterns, include +from ldt.api.ldt.resources import ProjectResource, ContentResource, SegmentResource, AnnotationResource +from tastypie.api import Api -project_handler = Resource(ProjectHandler, None) -annotation_handler = Resource(AnnotationHandler, None) -content_handler = Resource(ContentHandler, None) -segment_handler = Resource(SegmentHandler, None) +v1_api = Api(api_name='1.0') +v1_api.register(ProjectResource()) +v1_api.register(ContentResource()) +v1_api.register(SegmentResource()) +v1_api.register(AnnotationResource()) urlpatterns = patterns('', - url(r'projects/(?P[^/.]+)\.?(?P.*)$', project_handler, name='project_api'), - url(r'annotations/$', page_not_found, name='annotation_api_empty'), - url(r'annotations/(?P[^/.]+)\.?(?P.*)$', annotation_handler, name='annotation_api'), - url(r'contents/(?P[^/.]+)\.?(?P.*)$', content_handler, name='content_api'), - url(r'segments/$', page_not_found, name='segment_api_empty'), - url(r'segments/(?P.*)/(?P.*)/(?P.*)$', segment_handler, name='segment_api'), + (r'', include(v1_api.urls)), ) - - diff -r 834ceff06966 -r f52567ae138c src/ldt/ldt/ldt_utils/templates/ldt/ldt_utils/partial/embed_player.html --- a/src/ldt/ldt/ldt_utils/templates/ldt/ldt_utils/partial/embed_player.html Mon Oct 22 18:19:12 2012 +0200 +++ b/src/ldt/ldt/ldt_utils/templates/ldt/ldt_utils/partial/embed_player.html Mon Oct 22 18:22:39 2012 +0200 @@ -69,7 +69,7 @@ },{ type: "AnnotationsList", container: "AnnotationsList_ext", - ajax_url: "{{WEB_URL}}{% url segment_api_empty %}{% templatetag openvariable %}media{% templatetag closevariable %}/{% templatetag openvariable %}begin{% templatetag closevariable %}/{% templatetag openvariable %}end{% templatetag closevariable %}", + ajax_url: "{{WEB_URL}}{% url segment_api_empty resource_name='segments' api_name='1.0' %}{% templatetag openvariable %}media{% templatetag closevariable %}/{% templatetag openvariable %}begin{% templatetag closevariable %}/{% templatetag openvariable %}end{% templatetag closevariable %}", ajax_granularity : 300000, default_thumbnail : "{{WEB_URL}}{{LDT_MEDIA_PREFIX}}css/imgs/video_sequence.png", show_audio: true, diff -r 834ceff06966 -r f52567ae138c virtualenv/res/src/PyYAML-3.10.tar.gz Binary file virtualenv/res/src/PyYAML-3.10.tar.gz has changed diff -r 834ceff06966 -r f52567ae138c virtualenv/res/src/django-tastypie-0.9.11.tar.gz Binary file virtualenv/res/src/django-tastypie-0.9.11.tar.gz has changed diff -r 834ceff06966 -r f52567ae138c virtualenv/res/src/mimeparse-0.1.3.tar.gz Binary file virtualenv/res/src/mimeparse-0.1.3.tar.gz has changed diff -r 834ceff06966 -r f52567ae138c virtualenv/res/src/python-dateutil-2.1.tar.gz Binary file virtualenv/res/src/python-dateutil-2.1.tar.gz has changed diff -r 834ceff06966 -r f52567ae138c virtualenv/res/src/python-digest-1.7.tar.gz Binary file virtualenv/res/src/python-digest-1.7.tar.gz has changed