Added support for editing annotation through API
authordurandn
Fri, 21 Aug 2015 17:16:18 +0200
changeset 1415 c212b4f4e059
parent 1414 9c76c7eea3fd
child 1416 9d6e4a8c9dc7
Added support for editing annotation through API
src/ldt/ldt/api/ldt/resources/annotation.py
src/ldt/ldt/ldt_utils/contentindexer.py
src/ldt/ldt/ldt_utils/utils.py
--- a/src/ldt/ldt/api/ldt/resources/annotation.py	Sun Aug 23 22:37:27 2015 +0200
+++ b/src/ldt/ldt/api/ldt/resources/annotation.py	Fri Aug 21 17:16:18 2015 +0200
@@ -1,4 +1,4 @@
-from ldt.ldt_utils.contentindexer import add_segment
+from ldt.ldt_utils.contentindexer import add_segment, edit_segment
 from ldt.ldt_utils.models import Project, Content
 from ldt.ldt_utils.utils import LdtAnnotation
 from ldt.security import protect_models, unprotect_models
@@ -36,7 +36,7 @@
     meta = fields.DictField(attribute = 'meta')
     
     class Meta:
-        allowed_methods = ['post']
+        allowed_methods = ['post', 'put']
         resource_name = 'annotations'
         object_class = AnnotationObject
         authorization = Authorization()
@@ -44,15 +44,18 @@
         always_return_data = True
         include_resource_uri = False
     
-    def obj_delete_list(self, bundle, **kwargs):
-        return True
-    
-    def obj_create(self, bundle, **kwargs):
-        # Here the a has the datas for only one annotation. Tastypie's post allows only one resource addition
-        a = bundle.data
+    def extract_annotation_data(self, data):
+        # This method extracts and validates the data we receive from the user for adding or editing an annotation
+        
         project_id = ""
-        if a.has_key('project') :
-            project_id = a["project"]
+        if data.has_key('project') :
+            project_id = data["project"]
+            
+        if data.has_key('media') :
+            iri_id = data["media"]
+        else :
+            raise BadRequest
+        
         if project_id and project_id != "" :
             try:
                 project = Project.objects.get(ldt_id=project_id)
@@ -60,69 +63,205 @@
                 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 = a["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()
-            a[u"project"] = project.ldt_id
+            project_id = project.ldt_id
+        
+        ann_duration = str(data['end'] - data['begin'])
+        ann_begin = str(data['begin'])
+        ann_end = str(data['end'])
+        # We test if the annotation has audio node
+        ann_audio_src = ""
+        ann_audio_href = ""
+        if data['content'].has_key('audio') :
+            if data['content']['audio'].has_key('src') :
+                ann_audio_src = data['content']['audio']['src']
+            if data['content']['audio'].has_key('href') :
+                ann_audio_href = data['content']['audio']['href']
+        
+        ann_author = data['meta']['creator']
+        ann_date = data['meta']['created']
+        
+        ann_type_id = data.get('type', '')
+        ann_type_title = data.get('type_title', '')
+        ann_id = data.get("id", "")
+        ann_title = data.get('content', {}).get('title', "") 
+        ann_description = data.get('content', {}).get('description')
+        ann_tags = data.get('tags', [])
+        
+        content = project.contents.get(iri_id=iri_id)
         
-        adder = LdtAnnotation(project)
+        return {
+            "project_obj" : project,
+            "project_id" : project_id,
+            "content_obj" : content,  
+            "iri_id" : iri_id,
+            "ann_type_id" : ann_type_id, 
+            "ann_type_title" : ann_type_title,
+            "ann_id" : ann_id,
+            "ann_content" : { 
+                "title" : ann_title, 
+                "description" : ann_description,
+                "audio" : {
+                    "src" : ann_audio_src,
+                    "href" : ann_audio_href,
+                },
+            },
+            "ann_meta" : {
+                "creator" : ann_author,
+                "created" : ann_date,
+            },
+            "ann_tags" : ann_tags,
+            "ann_begin" : ann_begin,
+            "ann_end" : ann_end,
+            "ann_duration" : ann_duration,
+        }
+    
+    def obj_delete_list(self, bundle, **kwargs):
+        return True
+    
+    def obj_create(self, bundle, **kwargs):
+        # Here the a has the datas for only one annotation. Tastypie's post allows only one resource addition 
+        data_dict = self.extract_annotation_data(bundle.data)
+        adder = LdtAnnotation(data_dict["project_obj"])
         unprotect_models() # Allows anonymous user to modify models in this request only
         
-        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']
-        #                                    add(media,      cutting_id, cutting_title,  title,                 text,                        tags_list, begin, dur, author, date
-        type_id, new_id, ensemble_id = adder.add(a['media'], a['type'], a['type_title'], a['content']['title'], a['content']['description'], a['tags'], begin, dur, author, date, None, "2194379", audio_src, audio_href)
+        type_id, new_id, ensemble_id = adder.add(
+            data_dict['iri_id'], # media
+            data_dict['ann_type_id'], # cutting_id
+            data_dict['ann_type_title'], # cutting_title
+            data_dict['ann_content']['title'], # title
+            data_dict['ann_content']['description'], # text
+            data_dict['ann_tags'], # tags_list
+            data_dict['ann_begin'], # begin
+            data_dict['ann_duration'], # dur
+            data_dict['ann_meta']['creator'], # author
+            data_dict['ann_meta']['created'], # date
+            None, 
+            "2194379", 
+            data_dict['ann_content']['audio']['src'], 
+            data_dict['ann_content']['audio']['href']
+        )
         if not new_id:
             protect_models()
             raise BadRequest
                         
-        content = project.contents.get(iri_id=a['media'])
-        
         # We update the ids
-        a['type'] = type_id
-        a['ensemble'] = ensemble_id
-        a['id'] = new_id
-        if not a['content'].has_key('audio') :
-            a['content']['audio'] = {'src':audio_src, 'href':audio_href}
-        
+        data_dict['ann_type_id'] = type_id
+        data_dict['ensemble_id'] = ensemble_id
+        data_dict['ann_id'] = new_id
+
         #add segment
         add_segment({
-            "project" : project,
-            "content" : content,
-            "ensemble_id" : ensemble_id,
-            "cutting_id" :  a['type'],            
-            "element_id" : new_id,
-            "title" : a['content']['title'],
-            "abstract" : a['content']['description'],
-            "tags" : ",".join(a['tags']),
-            "start_ts" : begin,
-            "duration" :  dur,
-            "author" : author,
-            "date" : date, 
-            "audio_src" : audio_src,            
-            "audio_href" : audio_href,
-            "polemics": adder.get_polemic_syntax(a['content']['title'])
+            "project" : data_dict["project_obj"],
+            "content" : data_dict["content_obj"],
+            "ensemble_id" : data_dict["ensemble_id"],
+            "cutting_id" :  data_dict['ann_type_id'],            
+            "element_id" : data_dict['ann_id'],
+            "title" : data_dict['ann_content']['title'],
+            "abstract" : data_dict['ann_content']['description'],
+            "tags" : ",".join(data_dict['ann_tags']),
+            "start_ts" : data_dict['ann_begin'],
+            "duration" :  data_dict['ann_duration'],
+            "author" : data_dict['ann_meta']['creator'],
+            "date" : data_dict['ann_meta']['created'], 
+            "audio_src" : data_dict['ann_content']['audio']['src'],            
+            "audio_href" : data_dict['ann_content']['audio']['href'],
+            "polemics": adder.get_polemic_syntax(data_dict['ann_content']['title'])
         })
             
         # We save the added annotation and reprotect the contents and projects
         adder.save(must_reindex=False)
         protect_models()
         # We update the AnnotationObject for the returned datas to be correct.
-        bundle.obj = AnnotationObject(id = a["id"], project = a["project"], type = a["type"], type_title = a["type_title"], ensemble=a['ensemble'], media = a["media"], begin = a["begin"], end = a["end"], content = a['content'], tags = a['tags'], meta = a['meta'])
+        bundle.obj = AnnotationObject(
+            id = data_dict["ann_id"], 
+            project = data_dict["project_id"], 
+            type = data_dict["ann_type_id"], 
+            type_title = data_dict["ann_type_title"], 
+            ensemble = data_dict['ensemble_id'], 
+            media = data_dict["iri_id"], 
+            begin = data_dict["ann_begin"], 
+            end = data_dict["ann_end"], 
+            content = data_dict['ann_content'], 
+            tags = data_dict['ann_tags'], 
+            meta = data_dict['ann_meta']
+        )
+        return bundle
+    
+    def obj_update(self, bundle, **kwargs):
+        data_dict = self.extract_annotation_data(bundle.data)
+        adder = LdtAnnotation(data_dict["project_obj"])
+        unprotect_models() # Allows anonymous user to modify models in this request only
+        
+        ensemble_id = adder.edit(
+            data_dict['ann_id'], # annotation_id
+            data_dict['iri_id'], # media
+            data_dict['ann_type_id'], # cutting_id
+            data_dict['ann_type_title'], # cutting_title
+            data_dict['ann_content']['title'], # title
+            data_dict['ann_content']['description'], # text
+            data_dict['ann_tags'], # tags_list
+            data_dict['ann_begin'], # begin
+            data_dict['ann_duration'], # dur
+            data_dict['ann_meta']['creator'], # author
+            data_dict['ann_meta']['created'], # date
+            None, 
+            "2194379", 
+            data_dict['ann_content']['audio']['src'], 
+            data_dict['ann_content']['audio']['href']
+        )
+        if not ensemble_id:
+            protect_models()
+            raise BadRequest
+        
+        # We update the id
+        data_dict['ensemble_id'] = ensemble_id
+        
+        #add segment
+        edit_segment(
+            data_dict["project_id"],
+            data_dict["iri_id"],
+            data_dict["ensemble_id"],
+            data_dict['ann_type_id'],
+            data_dict["ann_id"],
+            params = {
+                "project" : data_dict["project_obj"],
+                "content" : data_dict["content_obj"],
+                "ensemble_id" : data_dict["ensemble_id"],
+                "cutting_id" :  data_dict['ann_type_id'],            
+                "element_id" : data_dict['ann_id'],
+                "title" : data_dict['ann_content']['title'],
+                "abstract" : data_dict['ann_content']['description'],
+                "tags" : ",".join(data_dict['ann_tags']),
+                "start_ts" : data_dict['ann_begin'],
+                "duration" :  data_dict['ann_duration'],
+                "author" : data_dict['ann_meta']['creator'],
+                "date" : data_dict['ann_meta']['created'], 
+                "audio_src" : data_dict['ann_content']['audio']['src'],            
+                "audio_href" : data_dict['ann_content']['audio']['href'],
+                "polemics": adder.get_polemic_syntax(data_dict['ann_content']['title'])
+            })
+        # We save the added annotation and reprotect the contents and projects
+        adder.save(must_reindex=False)
+        protect_models()
+        # We update the AnnotationObject for the returned datas to be correct.
+        bundle.obj = AnnotationObject(
+            id = data_dict["ann_id"], 
+            project = data_dict["project_id"], 
+            type = data_dict["ann_type_id"], 
+            type_title = data_dict["ann_type_title"], 
+            ensemble = data_dict['ensemble_id'], 
+            media = data_dict["iri_id"], 
+            begin = data_dict["ann_begin"], 
+            end = data_dict["ann_end"], 
+            content = data_dict['ann_content'], 
+            tags = data_dict['ann_tags'], 
+            meta = data_dict['ann_meta']
+        )
         return bundle
     
     def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'):
--- a/src/ldt/ldt/ldt_utils/contentindexer.py	Sun Aug 23 22:37:27 2015 +0200
+++ b/src/ldt/ldt/ldt_utils/contentindexer.py	Fri Aug 21 17:16:18 2015 +0200
@@ -258,48 +258,82 @@
             projectIndexer.index_all()
             update_stat_project(instance)
 
-
-
-def add_segment(params):
-                                
-    project = params.get("project",None)
-    content = params.get("content",None)
-    ensemble_id = params.get("ensemble_id", "")
-    cutting_id = params.get("cutting_id", "")
-    element_id = params.get("element_id", "")
-    title = params.get("title", "")
-    abstract = params.get("abstract", "")
-    tags_str = params.get("tags", "")
-    start_ts = params.get("start_ts", 0)
-    duration = params.get("duration", 0)
-    author = params.get("author", "")
-    date_str = params.get("date", "")
-    audio_src = params.get("audio_src", "")
-    audio_href = params.get("audio_href", "")
+def update_or_create_segment(params):
+    project = params.get("project", None)
+    content = params.get("content", None)
+    
+    seg_data = {}
+    
+    if params.has_key("content"):
+        seg_data["content"] = params["content"]
+        seg_data["iri_id"] = params["content"].iri_id
+    if params.has_key("project"):
+        seg_data["project_obj"] = params["project"]
+        seg_data["project_id"] = params["project"].ldt_id
+        
+    if params.has_key("ensemble_id"):
+        seg_data["ensemble_id"] = params["ensemble_id"]
+        
+    if params.has_key("cutting_id"):
+        seg_data["cutting_id"] = params["cutting_id"]
+        
+    if params.has_key("element_id"):
+        seg_data["element_id"] = params["element_id"]
+        
+    if params.has_key("title"):
+        seg_data["title"] = params["title"]
+        
+    if params.has_key("abstract"):
+        seg_data["abstract"] = params["abstract"]
+        
+    if params.has_key("start_ts"):
+        seg_data["start_ts"] = params["start_ts"]
+    
+    if params.has_key("duration"):
+        seg_data["duration"] = params["duration"]
+        
+    if params.has_key("date"):
+        seg_data["date"] = params["date"]
+        
+    if params.has_key("author"):
+        seg_data["author"] = params["author"]
+        
+    if params.has_key("audio_src"):
+        seg_data["audio_src"] = params["audio_src"]
+        
+    if params.has_key("audio_href"):
+        seg_data["audio_href"] = params["audio_href"]
+        
+    seg, created = Segment.objects.update_or_create(
+        project_id=project.ldt_id if project is not None else "", 
+        iri_id=content.iri_id if content is not None else "", 
+        ensemble_id=params.get("ensemble_id", ""), 
+        cutting_id=params.get("cutting_id", ""), 
+        element_id=params.get("element_id", ""),
+        defaults = seg_data
+    )
+    
     polemics = params.get("polemics", "")
-    
-    seg = Segment.create(content=content,
-              iri_id=content.iri_id if content is not None else "",
-              ensemble_id=ensemble_id,
-              cutting_id=cutting_id,
-              element_id=element_id,
-              title=title,
-              abstract=abstract,
-              duration=duration,
-              author=author,
-              start_ts=start_ts,
-              date=date_str,
-              project_obj=project,
-              project_id=project.ldt_id if project is not None else "",
-              audio_src=audio_src,
-              audio_href=audio_href)
     seg.polemics = seg.get_polemic(polemics)
     seg.save()
+    
+    tags_str = params.get("tags", "")
     for t in parse_tags(tags_str):
         seg.tags.add(t)
     seg.save()
     add_annotation_to_stat(seg.content, seg.start_ts, seg.start_ts+seg.duration)
+    
+    return created
 
+def add_segment(params):
+    return update_or_create_segment(params)
+
+def edit_segment(project_id, iri_id, ensemble_id, cutting_id, element_id, params):
+    seg = Segment.objects.filter(project_id=project_id, iri_id=iri_id, ensemble_id=ensemble_id, cutting_id=cutting_id, element_id=element_id)
+    if seg.count() <= 0:
+        return False
+    created = update_or_create_segment(params)
+    return not(created)
 
 def delete_segment(project, project_id, iri_id, ensemble_id, cutting_id, element_id):
 
--- a/src/ldt/ldt/ldt_utils/utils.py	Sun Aug 23 22:37:27 2015 +0200
+++ b/src/ldt/ldt/ldt_utils/utils.py	Fri Aug 21 17:16:18 2015 +0200
@@ -169,13 +169,80 @@
         self.parser = lxml.etree.XMLParser(remove_blank_text=True)
         self.ldtdoc = lxml.etree.parse(StringIO(project.ldt_encoded), self.parser)
         self.to_add = True
+    
+    def update_annotation_element(self, element, title, text, begin, dur, author, date, color, audio_src, audio_href, tags_list):       
+        element.set('begin', begin)
+        element.set('dur', dur)
+        element.set('author', author)
+        element.set('date', date)
+        element.set('color', color)
+        element.set('src', "")
         
-    #   add( a['media'], a['type'],  a['type_title, a[data], '', a['tags'], begin, dur, author, date)
-    def add(self, media, cutting_id, cutting_title, title, text, tags_list, begin, dur, author, date, view_id="0", color="2194379", audio_scr="", audio_href=""):
+        abstract_nodes = element.xpath('abstract')
+        if len(abstract_nodes) <= 0:
+            abstract_node = lxml.etree.SubElement(element, 'abstract')
+        else:
+            for node in abstract_nodes:
+                if node != abstract_nodes[0]:
+                    element.remove(node)
+            abstract_node = abstract_nodes[0]
+        abstract_node.text = text
+        
+        title_nodes = element.xpath('title')
+        if len(title_nodes) <= 0:
+            title_node = lxml.etree.SubElement(element, 'title')
+        else:
+            for node in title_nodes:
+                if node != title_nodes[0]:
+                    element.remove(node)
+            title_node = title_nodes[0]
+        title_node.text = title
+        
+        audio_nodes = element.xpath('audio')
+        if len(audio_nodes) <= 0:
+            audio_node = lxml.etree.SubElement(element, 'audio')
+        else:
+            for node in audio_nodes:
+                if node != audio_nodes[0]:
+                    element.remove(node)
+            audio_node = audio_nodes[0]
+        audio_node.set('source', audio_src)
+        audio_node.text = audio_href
+                                
+        polemics = self.get_polemic_syntax(title)
+        
+        if polemics:
+            meta_nodes = element.xpath('meta')
+            if len(meta_nodes) <= 0:
+                meta_node = lxml.etree.SubElement(element, 'meta')
+            else:
+                for node in meta_nodes:
+                    if node != meta_nodes[0]:
+                        element.remove(node)
+                meta_node = meta_nodes[0]
+                
+            for polemics_node in meta_node.xpath('polemics'):
+                meta_node.remove(polemics_node)
+                
+            polemics_node = lxml.etree.SubElement(meta_node, 'polemics')
+            
+            for polemic in polemics:
+                polemic_node = lxml.etree.SubElement(polemics_node, 'polemic')
+                polemic_node.text = polemic
+        
+        for tag_node in element.xpath('tags'):
+            element.remove(tag_node)
+        
+        tags = lxml.etree.SubElement(element, 'tags')
+        for tag in tags_list:
+            tag_node = lxml.etree.SubElement(tags, 'tag')
+            tag_node.text = tag
+
+    #   add(a['media'], a['type'],  a['type_title, a[data], '', a['tags'], begin, dur, author, date)
+    def add(self, media, cutting_id, cutting_title, title, text, tags_list, begin, dur, author, date, view_id="0", color="2194379", audio_src="", audio_href=""):
         """
         Add an annotation to a project. begin and dur must be strings. Default color is yellow.
         """
-        
         if dur < 0:
             self.to_add = False
             return False
@@ -248,39 +315,70 @@
                     dec.set('tagsSelect', '')
         
         # We add the annotation/element node
-        element = lxml.etree.SubElement(decoupage_elements[0], 'element')
+        
         id_annotation = 's_' + generate_uuid()
+        element = lxml.etree.SubElement(decoupage_elements[0], 'element')      
         element.set('id', id_annotation)
-        element.set('begin', begin)
-        element.set('dur', dur)
-        element.set('author', author)
-        element.set('date', date)
-        element.set('color', color)
-        element.set('src', "")
-        abstract = lxml.etree.SubElement(element, 'abstract')
-        abstract.text = text
-        title_node = lxml.etree.SubElement(element, 'title')
-        title_node.text = title
-        audio = lxml.etree.SubElement(element, 'audio')
-        audio.set('source', audio_scr)
-        audio.text = audio_href
-        tags = lxml.etree.SubElement(element, 'tags')
-                                
-        polemics = self.get_polemic_syntax(title)
+        
+        self.update_annotation_element(element, title, text, begin, dur, author, date, color, audio_src, audio_href, tags_list)
+                    
+        return cutting_id, id_annotation, ensemble_id
+    
+        
+    #   edit(      a["id"], a['media'], a['type'], a['type_title'], a[data], '', a['tags'], begin, dur, author, date)
+    def edit(self, ann_id, media, cutting_id, cutting_title, title, text, tags_list, begin, dur, author, date, view_id="0", color="2194379", audio_src="", audio_href=""):   
+        """
+        Edit an annotation in a project. begin and dur must be strings. Default color is yellow.
+        """
+        
+        if dur < 0:
+            self.to_add = False
+            return False
+        
+        # We check if the project references the media.
+        path_media = self.ldtdoc.xpath('/iri/medias/media[@id="%s"]' % media)
+        if len(path_media) == 0:
+            self.to_add = False
+            return False
+        
+        # We get the content node
+        path_annotations = self.ldtdoc.xpath('/iri/annotations')[0]
+        path_content = path_annotations.xpath('content[@id="%s"]' % media)
+        if len(path_content) == 0:
+            # If the content node does not exist, we abort as this should be an edition operation
+            return False
         
-        if polemics:
-            meta = lxml.etree.SubElement(element, 'meta')
-            polemics_node = lxml.etree.SubElement(meta, 'polemics')
+        # We check if cutting_id is provided, if it isn't we abort as this is an edition operation
+        if cutting_id is None or cutting_id=="" :
+            return False
+        # We get the ensemble node
+        path_ensemble = path_content[0].xpath('ensemble[decoupage[@id="%s"]]' % cutting_id)
+        
+        if len(path_ensemble) == 0:
+            # If the ensemble node does not exist, we abort as this should be an edition operation
+            return False
+        #else:
+        #    path_ensemble = path_content[0].xpath('ensemble')
             
-            for polemic in polemics:
-                polemic_node = lxml.etree.SubElement(polemics_node, 'polemic')
-                polemic_node.text = polemic
+        # We get the elements node in the good decoupage node
+        ensemble_id = path_ensemble[0].get('id')
+        decoupage_elements = path_ensemble[0].xpath('decoupage[@id="%s"]/elements' % cutting_id)
+        if len(decoupage_elements) == 0:
+            # If the decoupage node does not exist, we abort as this should be an edition operation 
+            return False
+        #else:
+        #    cutting_id = path_ensemble[0].xpath('decoupage[title="%s"]' % cutting_title)[0].get('id')     
         
-        for tag in tags_list:
-            tag_node = lxml.etree.SubElement(tags, 'tag')
-            tag_node.text = tag
+        # We get the annotation/element node
+        elements = decoupage_elements[0].xpath('element[@id="%s"]' % ann_id)
+        if len(elements) == 0:
+            # If the element doesn't exist there was a mistake calling edit method so we abort
+            return False
         
-        return cutting_id, id_annotation, ensemble_id
+        element = elements[0]
+        self.update_annotation_element(element, title, text, begin, dur, author, date, color, audio_src, audio_href, tags_list)
+           
+        return ensemble_id
     
     def save(self, must_reindex=True):
         if self.to_add: