From 29008d0eaecac2d1f423d478375c94902c8d15ee Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Mon, 4 Mar 2013 15:41:25 +0000 Subject: [PATCH] add addAnnotations api to batch import many annotations, update importAnnotations dialog --- pandora/annotation/tasks.py | 28 ++++++ pandora/annotation/views.py | 41 ++++++++- pandora/archive/views.py | 5 +- pandora/config.indiancinema.jsonc | 2 +- static/js/pandora/importAnnotations.js | 118 ++++++++++++++----------- static/js/pandora/utils.js | 20 ++++- 6 files changed, 158 insertions(+), 56 deletions(-) diff --git a/pandora/annotation/tasks.py b/pandora/annotation/tasks.py index 4684ad86..3d3088eb 100644 --- a/pandora/annotation/tasks.py +++ b/pandora/annotation/tasks.py @@ -4,6 +4,7 @@ import json import ox from django.conf import settings +from django.db import transaction from celery.task import task import models @@ -63,6 +64,33 @@ def update_matches(id, type): for e in a_matches.all(): e.update_matches(models.Annotation.objects.filter(pk=a.id)) +@task(ignore_results=False, queue='default') +def add_annotations(data): + from item.models import Item + from user.models import User + item = Item.objects.get(itemId=data['item']) + layer_id = data['layer'] + layer = filter(lambda l: l['id'] == layer_id, settings.CONFIG['layers'])[0] + user = User.objects.get(username=data['user']) + with transaction.commit_on_success(): + for a in data['annotations']: + annotation = models.Annotation( + item=item, + layer=layer_id, + user=user, + start=float(a['in']), end=float(a['out']), + value=a['value']) + annotation.save() + #update facets if needed + if filter(lambda f: f['id'] == layer_id, settings.CONFIG['filters']): + item.update_layer_facet(layer_id) + Item.objects.filter(id=item.id).update(modified=annotation.modified) + annotation.item.modified = annotation.modified + annotation.item.update_find() + annotation.item.update_sort() + annotation.item.update_facets() + return True + @task(ignore_results=True, queue='default') def update_item(id): from item.models import Item diff --git a/pandora/annotation/views.py b/pandora/annotation/views.py index 6cd04af1..3a632266 100644 --- a/pandora/annotation/views.py +++ b/pandora/annotation/views.py @@ -15,7 +15,7 @@ from item import utils from item.models import Item import models -from tasks import update_item +from tasks import update_item, add_annotations def parse_query(data, user): query = {} @@ -157,6 +157,45 @@ def addAnnotation(request): return render_to_json_response(response) actions.register(addAnnotation, cache=False) +@login_required_json +def addAnnotations(request): + ''' + param data { + item: itemId, + layer: layerId, + annotations: [{ + in: float, + out: float, + value: string + }, ...] + } + return {'status': {'code': int, 'text': string}, + 'data': { + id: 123, //id of new annotation + ... + } + } + ''' + data = json.loads(request.POST['data']) + for key in ('item', 'layer', 'annotations'): + if key not in data: + return render_to_json_response(json_response(status=400, + text='invalid data')) + + item = get_object_or_404_json(Item, itemId=data['item']) + + layer_id = data['layer'] + layer = filter(lambda l: l['id'] == layer_id, settings.CONFIG['layers'])[0] + if item.editable(request.user) \ + and layer['canAddAnnotations'].get(request.user.get_profile().get_level()): + response = json_response() + data['user'] = request.user.username + t = add_annotations.delay(data) + response['data']['taskId'] = t.task_id + else: + response = json_response(status=403, text='permission denied') + return render_to_json_response(response) +actions.register(addAnnotations, cache=False) @login_required_json def removeAnnotation(request): diff --git a/pandora/archive/views.py b/pandora/archive/views.py index f23ee960..d0ba76cc 100644 --- a/pandora/archive/views.py +++ b/pandora/archive/views.py @@ -285,7 +285,10 @@ def firefogg_upload(request): def taskStatus(request): #FIXME: should check if user has permissions to get status data = json.loads(request.POST['data']) - task_id = data['task_id'] + if 'taskId' in data: + task_id = data['taskId'] + else: + task_id = data['task_id'] response = task_status(request, task_id) return render_to_json_response(response) actions.register(taskStatus, cache=False) diff --git a/pandora/config.indiancinema.jsonc b/pandora/config.indiancinema.jsonc index 2727a23b..ef327ade 100644 --- a/pandora/config.indiancinema.jsonc +++ b/pandora/config.indiancinema.jsonc @@ -38,7 +38,7 @@ "canEditPlaces": {"student": true, "staff": true, "admin": true}, "canEditSitePages": {"staff": true, "admin": true}, "canEditUsers": {"staff": true, "admin": true}, - "canImportAnnotations": {}, + "canImportAnnotations": {"admin": true}, "canManagePlacesAndEvents": {"student": true, "staff": true, "admin": true}, "canManageTitlesAndNames": {"student": true, "staff": true, "admin": true}, "canManageUsers": {"staff": true, "admin": true}, diff --git a/static/js/pandora/importAnnotations.js b/static/js/pandora/importAnnotations.js index 17b1b573..93f78fe2 100644 --- a/static/js/pandora/importAnnotations.js +++ b/static/js/pandora/importAnnotations.js @@ -4,18 +4,17 @@ pandora.ui.importAnnotations = function(data) { var content = Ox.Element().css({margin: '16px'}), file, - height = 192, + height = 128, layers = pandora.site.layers.filter(function(layer) { return layer.canAddAnnotations[pandora.user.level]; }), layer, - width = 384, srt = [], total = 0, importButton, selectLayer, selectFile, - that = Ox.Dialog({ + that = pandora.ui.iconDialog({ buttons: [ Ox.Button({ id: 'close', @@ -32,20 +31,16 @@ pandora.ui.importAnnotations = function(data) { }).bindEvent({ click: function() { importButton.hide(); - selectLayer.hide(); - selectFile.hide(); - addAnnotation(); + addAnnotations(); } }) ], closeButton: true, - content: content, + text: content, keys: { 'escape': 'close' }, - height: height, removeOnClose: true, - width: width, title: 'Import Annotations' }) .bindEvent({ @@ -54,39 +49,50 @@ pandora.ui.importAnnotations = function(data) { } }), $status = $('
').css({ - padding: '4px' - }); + padding: '4px', + paddingBottom: '8px' + }).appendTo(content); - function addAnnotation() { + setStatus('Please select layer and .srt file') + function addAnnotations() { + var annotations, task; if (srt.length > 0) { - var data = srt.shift(); - data.text = Ox.sanitizeHTML(data.text) - .replace(/\n/g, '\n') - .replace(/\n\n/g, '
\n') - .replace(/\n/g, '
\n'); - $status.html( - Ox.formatDuration(data['in']) + ' to ' - + Ox.formatDuration(data.out) + '
\n' + data.text - ); - pandora.api.addAnnotation({ - 'in': data['in'], - out: data.out, - value: data.text, + setStatus('Loading...'); + var annotations = srt.filter(function(data) { + return !Ox.isUndefined(data['in']) && !Ox.isUndefined(data.out) && data.text; + + }).map(function(data) { + return { + 'in': data['in'], + out: data.out, + value: Ox.sanitizeHTML(data.text) + .replace(/\n/g, '\n') + .replace(/\n\n/g, '
\n') + .replace(/\n/g, '
\n') + }; + }); + pandora.api.addAnnotations({ + annotations: annotations, item: pandora.user.ui.item, layer: layer }, function(result) { - if (result.status.code == 200) { - addAnnotation(); + if (result.data.taskId) { + setStatus('Waiting or server to import annotations...'); + pandora.wait(result.data.taskId, function(result) { + if(result.data.status == 'SUCCESS') { + setStatus(annotations.length + ' annotations imported.'); + Ox.Request.clearCache(pandora.user.ui.item); + pandora.$ui.contentPanel.replaceElement( + 1, pandora.$ui.item = pandora.ui.item() + ); + } else { + setStatus('Importing annotations failed.'); + } + }); } else { - content.html('Failed'); + setStatus('Importing annotations failed.'); } }); - } else { - $status.html(total + ' annotations imported.'); - Ox.Request.clearCache(pandora.user.ui.item); - pandora.$ui.contentPanel.replaceElement( - 1, pandora.$ui.item = pandora.ui.item() - ); } } function parseSRT(data) { @@ -103,14 +109,17 @@ pandora.ui.importAnnotations = function(data) { } return srt; } - content.append($('
').css({ - padding: '4px', - paddingBottom: '16px' - }).html('Import annotations from .srt file:')); + + function setStatus(status) { + $status.html(status); + } + selectLayer = Ox.Select({ items: layers, - title: 'select...', - label: 'Layer' + title: 'Select Layer', + }) + .css({ + margin: '4px 2px 4px 4px' }) .bindEvent({ change: function(data) { @@ -123,28 +132,33 @@ pandora.ui.importAnnotations = function(data) { }) .appendTo(content); - selectFile = $('') - .attr({ - type: 'file' + selectFile = Ox.FileButton({ + image: 'upload', + lbael: 'File', + title: 'Select SRT...', + width: 156 }) .css({ - padding: '8px' + margin: '4px 2px 4px 4px' }) - .on({ - change: function(event) { - if (this.files.length) { - file = this.files[0]; + .bindEvent({ + click: function(data) { + if(data.files.length) { var reader = new FileReader(); reader.onloadend = function(event) { srt = parseSRT(this.result); total = srt.length; - total && layer && importButton.options({disabled: false}); - $status.html( + if(total && layer) { + importButton.options({disabled: false}); + selectLayer.hide(); + selectFile.hide(); + } + setStatus( 'File contains ' + total + ' annotation' + (total == 1 ? '' : 's') + '.' ); }; - reader.readAsText(file); + reader.readAsText(data.files[0]); } else { srt = []; total = 0; @@ -155,6 +169,6 @@ pandora.ui.importAnnotations = function(data) { } }) .appendTo(content); - content.append($status); + return that; }; diff --git a/static/js/pandora/utils.js b/static/js/pandora/utils.js index 6b3a1835..8be49880 100644 --- a/static/js/pandora/utils.js +++ b/static/js/pandora/utils.js @@ -1401,7 +1401,7 @@ pandora.beforeunloadWindow = function() { return "Encoding is currently running\nDo you want to leave this page?"; //prevent error dialogs on unload pandora.isUnloading = true; -} +}; pandora.unloadWindow = function() { /* @@ -1439,6 +1439,24 @@ pandora.updateItemContext = function() { } }; +pandora.wait = function(taskId, callback, timeout) { + var task = {}; + timeout = timeout || 5000; + task.timeout = setTimeout(function() { + pandora.api.taskStatus({taskId: taskId}, function(result) { + var t; + if(result.data.status == 'PENDING') { + t = pandora.wait(taskId, callback); + task.timeout = t.timeout; + } else { + callback(result); + } + }); + }, 5000); + return task; +}; + + (function() { // Note: getFindState has to run after getListState and getFilterState