first commit with new rest api urls and resources.
--- 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://<plateform_location>/api/ldt/projects/<project_id>.<format>.
- <project_id> is the ldt_id field of the project. If <projet_id> does not match any project on the platform, a 410 ("Gone")
- error will be returned.
- <format> 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()
-
--- /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"]
--- /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
--- /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<resource_name>%s)/(?P<iri_id>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
+ ]
+
\ No newline at end of file
--- /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<resource_name>%s)/(?P<ldt_id>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
+ ]
+
\ No newline at end of file
--- /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<resource_name>%s)/search%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_search'), name="api_get_search"),
+ url(r"^(?P<resource_name>%s)/bytimecode%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_segments_by_timecode'), name='segment_api_empty'),
+ url(r"^(?P<resource_name>%s)/bytimecode/(?P<iri_id>.*)/(?P<begin>.*)/(?P<end>.*)$" % 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
--- 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<project_id>[^/.]+)\.?(?P<emitter_format>.*)$', project_handler, name='project_api'),
- url(r'annotations/$', page_not_found, name='annotation_api_empty'),
- url(r'annotations/(?P<project_id>[^/.]+)\.?(?P<emitter_format>.*)$', annotation_handler, name='annotation_api'),
- url(r'contents/(?P<iri_id>[^/.]+)\.?(?P<emitter_format>.*)$', content_handler, name='content_api'),
- url(r'segments/$', page_not_found, name='segment_api_empty'),
- url(r'segments/(?P<iri_id>.*)/(?P<begin>.*)/(?P<end>.*)$', segment_handler, name='segment_api'),
+ (r'', include(v1_api.urls)),
)
-
-
--- 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,
Binary file virtualenv/res/src/PyYAML-3.10.tar.gz has changed
Binary file virtualenv/res/src/django-tastypie-0.9.11.tar.gz has changed
Binary file virtualenv/res/src/mimeparse-0.1.3.tar.gz has changed
Binary file virtualenv/res/src/python-dateutil-2.1.tar.gz has changed
Binary file virtualenv/res/src/python-digest-1.7.tar.gz has changed