forked from 0x2620/pandora
add addAnnotations api to batch import many annotations, update importAnnotations dialog
This commit is contained in:
parent
b9a01b2db9
commit
29008d0eae
6 changed files with 158 additions and 56 deletions
|
@ -4,6 +4,7 @@ import json
|
||||||
import ox
|
import ox
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import transaction
|
||||||
from celery.task import task
|
from celery.task import task
|
||||||
|
|
||||||
import models
|
import models
|
||||||
|
@ -63,6 +64,33 @@ def update_matches(id, type):
|
||||||
for e in a_matches.all():
|
for e in a_matches.all():
|
||||||
e.update_matches(models.Annotation.objects.filter(pk=a.id))
|
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')
|
@task(ignore_results=True, queue='default')
|
||||||
def update_item(id):
|
def update_item(id):
|
||||||
from item.models import Item
|
from item.models import Item
|
||||||
|
|
|
@ -15,7 +15,7 @@ from item import utils
|
||||||
from item.models import Item
|
from item.models import Item
|
||||||
|
|
||||||
import models
|
import models
|
||||||
from tasks import update_item
|
from tasks import update_item, add_annotations
|
||||||
|
|
||||||
def parse_query(data, user):
|
def parse_query(data, user):
|
||||||
query = {}
|
query = {}
|
||||||
|
@ -157,6 +157,45 @@ def addAnnotation(request):
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
actions.register(addAnnotation, cache=False)
|
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
|
@login_required_json
|
||||||
def removeAnnotation(request):
|
def removeAnnotation(request):
|
||||||
|
|
|
@ -285,7 +285,10 @@ def firefogg_upload(request):
|
||||||
def taskStatus(request):
|
def taskStatus(request):
|
||||||
#FIXME: should check if user has permissions to get status
|
#FIXME: should check if user has permissions to get status
|
||||||
data = json.loads(request.POST['data'])
|
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)
|
response = task_status(request, task_id)
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
actions.register(taskStatus, cache=False)
|
actions.register(taskStatus, cache=False)
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
"canEditPlaces": {"student": true, "staff": true, "admin": true},
|
"canEditPlaces": {"student": true, "staff": true, "admin": true},
|
||||||
"canEditSitePages": {"staff": true, "admin": true},
|
"canEditSitePages": {"staff": true, "admin": true},
|
||||||
"canEditUsers": {"staff": true, "admin": true},
|
"canEditUsers": {"staff": true, "admin": true},
|
||||||
"canImportAnnotations": {},
|
"canImportAnnotations": {"admin": true},
|
||||||
"canManagePlacesAndEvents": {"student": true, "staff": true, "admin": true},
|
"canManagePlacesAndEvents": {"student": true, "staff": true, "admin": true},
|
||||||
"canManageTitlesAndNames": {"student": true, "staff": true, "admin": true},
|
"canManageTitlesAndNames": {"student": true, "staff": true, "admin": true},
|
||||||
"canManageUsers": {"staff": true, "admin": true},
|
"canManageUsers": {"staff": true, "admin": true},
|
||||||
|
|
|
@ -4,18 +4,17 @@
|
||||||
pandora.ui.importAnnotations = function(data) {
|
pandora.ui.importAnnotations = function(data) {
|
||||||
var content = Ox.Element().css({margin: '16px'}),
|
var content = Ox.Element().css({margin: '16px'}),
|
||||||
file,
|
file,
|
||||||
height = 192,
|
height = 128,
|
||||||
layers = pandora.site.layers.filter(function(layer) {
|
layers = pandora.site.layers.filter(function(layer) {
|
||||||
return layer.canAddAnnotations[pandora.user.level];
|
return layer.canAddAnnotations[pandora.user.level];
|
||||||
}),
|
}),
|
||||||
layer,
|
layer,
|
||||||
width = 384,
|
|
||||||
srt = [],
|
srt = [],
|
||||||
total = 0,
|
total = 0,
|
||||||
importButton,
|
importButton,
|
||||||
selectLayer,
|
selectLayer,
|
||||||
selectFile,
|
selectFile,
|
||||||
that = Ox.Dialog({
|
that = pandora.ui.iconDialog({
|
||||||
buttons: [
|
buttons: [
|
||||||
Ox.Button({
|
Ox.Button({
|
||||||
id: 'close',
|
id: 'close',
|
||||||
|
@ -32,20 +31,16 @@ pandora.ui.importAnnotations = function(data) {
|
||||||
}).bindEvent({
|
}).bindEvent({
|
||||||
click: function() {
|
click: function() {
|
||||||
importButton.hide();
|
importButton.hide();
|
||||||
selectLayer.hide();
|
addAnnotations();
|
||||||
selectFile.hide();
|
|
||||||
addAnnotation();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
content: content,
|
text: content,
|
||||||
keys: {
|
keys: {
|
||||||
'escape': 'close'
|
'escape': 'close'
|
||||||
},
|
},
|
||||||
height: height,
|
|
||||||
removeOnClose: true,
|
removeOnClose: true,
|
||||||
width: width,
|
|
||||||
title: 'Import Annotations'
|
title: 'Import Annotations'
|
||||||
})
|
})
|
||||||
.bindEvent({
|
.bindEvent({
|
||||||
|
@ -54,39 +49,50 @@ pandora.ui.importAnnotations = function(data) {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
$status = $('<div>').css({
|
$status = $('<div>').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) {
|
if (srt.length > 0) {
|
||||||
var data = srt.shift();
|
setStatus('Loading...');
|
||||||
data.text = Ox.sanitizeHTML(data.text)
|
var annotations = srt.filter(function(data) {
|
||||||
.replace(/<br[ /]*?>\n/g, '\n')
|
return !Ox.isUndefined(data['in']) && !Ox.isUndefined(data.out) && data.text;
|
||||||
.replace(/\n\n/g, '<br>\n')
|
|
||||||
.replace(/\n/g, '<br>\n');
|
}).map(function(data) {
|
||||||
$status.html(
|
return {
|
||||||
Ox.formatDuration(data['in']) + ' to '
|
'in': data['in'],
|
||||||
+ Ox.formatDuration(data.out) + '<br>\n' + data.text
|
out: data.out,
|
||||||
);
|
value: Ox.sanitizeHTML(data.text)
|
||||||
pandora.api.addAnnotation({
|
.replace(/<br[ /]*?>\n/g, '\n')
|
||||||
'in': data['in'],
|
.replace(/\n\n/g, '<br>\n')
|
||||||
out: data.out,
|
.replace(/\n/g, '<br>\n')
|
||||||
value: data.text,
|
};
|
||||||
|
});
|
||||||
|
pandora.api.addAnnotations({
|
||||||
|
annotations: annotations,
|
||||||
item: pandora.user.ui.item,
|
item: pandora.user.ui.item,
|
||||||
layer: layer
|
layer: layer
|
||||||
}, function(result) {
|
}, function(result) {
|
||||||
if (result.status.code == 200) {
|
if (result.data.taskId) {
|
||||||
addAnnotation();
|
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 {
|
} 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) {
|
function parseSRT(data) {
|
||||||
|
@ -103,14 +109,17 @@ pandora.ui.importAnnotations = function(data) {
|
||||||
}
|
}
|
||||||
return srt;
|
return srt;
|
||||||
}
|
}
|
||||||
content.append($('<div>').css({
|
|
||||||
padding: '4px',
|
function setStatus(status) {
|
||||||
paddingBottom: '16px'
|
$status.html(status);
|
||||||
}).html('Import annotations from .srt file:'));
|
}
|
||||||
|
|
||||||
selectLayer = Ox.Select({
|
selectLayer = Ox.Select({
|
||||||
items: layers,
|
items: layers,
|
||||||
title: 'select...',
|
title: 'Select Layer',
|
||||||
label: 'Layer'
|
})
|
||||||
|
.css({
|
||||||
|
margin: '4px 2px 4px 4px'
|
||||||
})
|
})
|
||||||
.bindEvent({
|
.bindEvent({
|
||||||
change: function(data) {
|
change: function(data) {
|
||||||
|
@ -123,28 +132,33 @@ pandora.ui.importAnnotations = function(data) {
|
||||||
})
|
})
|
||||||
.appendTo(content);
|
.appendTo(content);
|
||||||
|
|
||||||
selectFile = $('<input>')
|
selectFile = Ox.FileButton({
|
||||||
.attr({
|
image: 'upload',
|
||||||
type: 'file'
|
lbael: 'File',
|
||||||
|
title: 'Select SRT...',
|
||||||
|
width: 156
|
||||||
})
|
})
|
||||||
.css({
|
.css({
|
||||||
padding: '8px'
|
margin: '4px 2px 4px 4px'
|
||||||
})
|
})
|
||||||
.on({
|
.bindEvent({
|
||||||
change: function(event) {
|
click: function(data) {
|
||||||
if (this.files.length) {
|
if(data.files.length) {
|
||||||
file = this.files[0];
|
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onloadend = function(event) {
|
reader.onloadend = function(event) {
|
||||||
srt = parseSRT(this.result);
|
srt = parseSRT(this.result);
|
||||||
total = srt.length;
|
total = srt.length;
|
||||||
total && layer && importButton.options({disabled: false});
|
if(total && layer) {
|
||||||
$status.html(
|
importButton.options({disabled: false});
|
||||||
|
selectLayer.hide();
|
||||||
|
selectFile.hide();
|
||||||
|
}
|
||||||
|
setStatus(
|
||||||
'File contains ' + total + ' annotation'
|
'File contains ' + total + ' annotation'
|
||||||
+ (total == 1 ? '' : 's') + '.'
|
+ (total == 1 ? '' : 's') + '.'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(data.files[0]);
|
||||||
} else {
|
} else {
|
||||||
srt = [];
|
srt = [];
|
||||||
total = 0;
|
total = 0;
|
||||||
|
@ -155,6 +169,6 @@ pandora.ui.importAnnotations = function(data) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.appendTo(content);
|
.appendTo(content);
|
||||||
content.append($status);
|
|
||||||
return that;
|
return that;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1401,7 +1401,7 @@ pandora.beforeunloadWindow = function() {
|
||||||
return "Encoding is currently running\nDo you want to leave this page?";
|
return "Encoding is currently running\nDo you want to leave this page?";
|
||||||
//prevent error dialogs on unload
|
//prevent error dialogs on unload
|
||||||
pandora.isUnloading = true;
|
pandora.isUnloading = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
pandora.unloadWindow = function() {
|
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() {
|
(function() {
|
||||||
|
|
||||||
// Note: getFindState has to run after getListState and getFilterState
|
// Note: getFindState has to run after getListState and getFilterState
|
||||||
|
|
Loading…
Reference in a new issue