# HG changeset patch # User durandn # Date 1440170178 -7200 # Node ID c212b4f4e059947fddc8ee4ec8c1fb6c73074493 # Parent 9c76c7eea3fdd9a2d90bb228defb64fd21309f72 Added support for editing annotation through API diff -r 9c76c7eea3fd -r c212b4f4e059 src/ldt/ldt/api/ldt/resources/annotation.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'): diff -r 9c76c7eea3fd -r c212b4f4e059 src/ldt/ldt/ldt_utils/contentindexer.py --- 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): diff -r 9c76c7eea3fd -r c212b4f4e059 src/ldt/ldt/ldt_utils/utils.py --- 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: