Browse Source

Update AnnotationAPI

pull/10/head
Hironsan 7 years ago
parent
commit
c217d203ca
7 changed files with 55 additions and 179 deletions
  1. 2
      doccano/app/app/settings.py
  2. BIN
      doccano/app/db.sqlite3
  3. 135
      doccano/app/server/static/annotation.js
  4. 2
      doccano/app/server/static/main.js
  5. 18
      doccano/app/server/templates/annotation.html
  6. 6
      doccano/app/server/urls.py
  7. 71
      doccano/app/server/views.py

2
doccano/app/app/settings.py

@ -114,7 +114,7 @@ REST_FRAMEWORK = {
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10,
'PAGE_SIZE': 5,
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
'SEARCH_PARAM': 'q',
}

BIN
doccano/app/db.sqlite3

135
doccano/app/server/static/annotation.js

@ -5,99 +5,57 @@ axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
var base_url = window.location.href.split('/').slice(3, 5).join('/');
const HTTP = axios.create({
baseURL: '/api/' + base_url + '/',
//headers: {
// Authorization: 'Bearer {token}'
//}
baseURL: `/api/${base_url}/`
})
function swap(values) {
var ret = {};
for (var item of values) {
ret[item['text']] = item['id'];
}
return ret;
};
var vm = new Vue({
el: '#mail-app',
delimiters: ['[[', ']]'],
data: {
cur: 0,
items: [{
"id": null,
"labels": [],
"text": ''
}],
items: [{id: null, text: '', labels: []}],
labels: [],
guideline: 'Here is the Annotation Guideline Text',
total: 0,
remaining: 0,
searchQuery: '',
hasNext: false,
hasPrevious: false,
nextPageNum: 1,
prevPageNum: 1,
page: 1,
url: '',
},
methods: {
addLabel: async function (label) {
addLabel: async function (label_id) {
for (var i = 0; i < this.items[this.cur]['labels'].length; i++) {
var item = this.items[this.cur]['labels'][i];
if (label == item.text) {
if (label_id == item.label.id) {
this.deleteLabel(i);
return;
}
}
var label = {
'text': label,
'prob': null
};
this.items[this.cur]['labels'].push(label);
var label2id = swap(this.labels);
var data = {
'id': this.items[this.cur]['id'],
'label_id': label2id[label['text']]
var payload = {
'label_id': label_id
};
await axios.post('/' + base_url + '/apis/data', data)
.then(function (response) {
console.log('post data');
})
.catch(function (error) {
console.log('ERROR!! happend by Backend.')
});
var doc_id = this.items[this.cur].id;
await HTTP.post(`docs/${doc_id}/annotations/`, payload).then(response => {
this.items[this.cur]['labels'].push(response.data);
});
this.updateProgress();
},
deleteLabel: async function (index) {
var label2id = swap(this.labels);
var label = this.items[this.cur]['labels'][index];
var payload = {
'id': this.items[this.cur]['id'],
'label_id': label2id[label['text']]
};
await axios.delete('/' + base_url + '/apis/data', {
data: payload
})
.then(function (response) {
console.log('delete data');
})
.catch(function (error) {
console.log('ERROR!! happend by Backend.')
});
this.items[this.cur]['labels'].splice(index, 1)
var doc_id = this.items[this.cur].id;
var annotation_id = this.items[this.cur]['labels'][index].id;
HTTP.delete(`docs/${doc_id}/annotations/${annotation_id}`).then(response => {
this.items[this.cur]['labels'].splice(index, 1)
});
this.updateProgress();
},
nextPage: function () {
nextPage: async function () {
this.cur += 1;
if (this.cur == this.items.length) {
if (this.hasNext) {
this.page = this.nextPageNum;
this.submit();
if (this.next) {
this.url = this.next;
await this.search();
this.cur = 0;
} else {
this.cur = this.items.length - 1;
@ -105,12 +63,12 @@ var vm = new Vue({
}
this.showMessage(this.cur);
},
prevPage: function () {
prevPage: async function () {
this.cur -= 1;
if (this.cur == -1) {
if (this.hasPrevious) {
this.page = this.prevPageNum;
this.submit();
if (this.prev) {
this.url = this.prev;
await this.search();
this.cur = this.items.length - 1;
} else {
this.cur = 0;
@ -118,36 +76,25 @@ var vm = new Vue({
}
this.showMessage(this.cur);
},
activeLearn: function () {
alert('Active Learning!');
},
submit: function () {
console.log('submit' + this.searchQuery);
var self = this;
axios.get('/' + base_url + '/apis/search?keyword=' + this.searchQuery + '&page=' + this.page)
.then(function (response) {
console.log('search response');
console.log(response.data);
self.items = response.data['data'];
self.hasNext = response.data['has_next']
self.nextPageNum = response.data['next_page_number']
self.hasPrevious = response.data['has_previous']
self.prevPageNum = response.data['previous_page_number']
//self.searchQuery = '';
})
.catch(function (error) {
console.log('ERROR!! happend by Backend.')
});
this.url = `docs/?q=${this.searchQuery}`;
this.search();
},
search: async function () {
await HTTP.get(this.url).then(response => {
this.items = response.data['results'];
this.next = response.data['next'];
this.prev = response.data['previous'];
})
},
showMessage: function (index) {
this.cur = index;
},
updateProgress: function () {
HTTP.get('progress')
.then(response => {
this.total = response.data['total'];
this.remaining = response.data['remaining'];
})
HTTP.get('progress').then(response => {
this.total = response.data['total'];
this.remaining = response.data['remaining'];
})
}
},
created: function () {
@ -159,11 +106,9 @@ var vm = new Vue({
},
computed: {
achievement: function () {
if (this.total == 0) {
return 0
} else {
return Math.round((this.total - this.remaining) / this.total * 100)
}
var done = this.total - this.remaining;
var percentage = Math.round(done / this.total * 100);
return this.total > 0 ? percentage : 0;
},
progressColor: function () {
if (this.achievement < 30) {

2
doccano/app/server/static/main.js
File diff suppressed because it is too large
View File

18
doccano/app/server/templates/annotation.html

@ -26,7 +26,7 @@
<div class="control" v-for="(label, label_id) in labels">
<li class="tags has-addons">
<span class="tag is-primary">[[label.shortcut]]</span>
<a class="tag is-grey" v-on:click="addLabel(label.text)" v-shortkey.once="['ctrl', [[ label.shortcut ]]]" @shortkey="addLabel(label.text)">[[label.text]]</a>
<a class="tag is-grey" v-on:click="addLabel(label.id)" v-shortkey.once="['ctrl', [[ label.shortcut ]]]" @shortkey="addLabel(label.id)">[[label.text]]</a>
</li>
</div>
</ul>
@ -69,18 +69,18 @@
</div>
</div>
<div class="inbox-messages" id="inbox-messages">
<div class="inbox-messages" id="inbox-messages" v-if="items.length">
<div v-for="(msg, index) in items" class="card" v-bind:class="{ active: index == cur }" v-on:click="showMessage(index)" v-bind:data-preview-id="index">
<div class="card-content" style="padding:1.2rem;">
<div class="msg-header">
<span class="msg-from">
<div class="field is-grouped is-grouped-multiline">
<div class="control" v-for="(label, index) in msg.labels">
<div class="control" v-for="(annotation, index) in msg.labels">
<div class="tags has-addons">
<span class="tag is-dark">
[[label.text]]
[[annotation.label.text]]
</span>
<span class="tag is-primary" v-if="label.prob">[[label.prob]]</span>
<span class="tag is-primary" v-if="annotation.label.prob">[[annotation.label.prob]]</span>
</div>
</div>
</div>
@ -105,15 +105,15 @@
</div>
</div>
<div class="box message-preview">
<div class="top">
<div class="top" v-if="items.length">
<div class="field is-grouped is-grouped-multiline">
<div class="control" v-for="(label, index) in items[cur].labels">
<div class="control" v-for="(annotation, index) in items[cur].labels">
<div class="tags has-addons">
<span class="tag is-dark">
<button class="delete is-small" v-on:click="deleteLabel(index)"></button>
[[ label.text ]]
[[ annotation.label.text ]]
</span>
<span class="tag is-primary" v-if="label.prob">[[ label.prob ]]</span>
<span class="tag is-primary" v-if="annotation.label.prob">[[ annotation.label.prob ]]</span>
</div>
</div>
</div>

6
doccano/app/server/urls.py

@ -1,7 +1,7 @@
from django.urls import path
from .views import IndexView
from .views import AnnotationAPIView, SearchAPI, InboxView
from .views import InboxView
from .views import ProjectsView, ProjectAdminView, RawDataAPI, DataDownloadAPI
from rest_framework import routers
from .views import ProjectViewSet
@ -17,13 +17,11 @@ urlpatterns = [
path('api/projects/<int:project_id>/labels/', ProjectLabelsAPI.as_view(), name='labels'),
path('api/projects/<int:project_id>/labels/<int:label_id>', ProjectLabelAPI.as_view(), name='label'),
path('api/projects/<int:project_id>/docs/', ProjectDocsAPI.as_view(), name='docs'),
path('api/projects/<int:project_id>/docs/<int:doc_id>/annotations', AnnotationsAPI.as_view()),
path('api/projects/<int:project_id>/docs/<int:doc_id>/annotations/', AnnotationsAPI.as_view()),
path('api/projects/<int:project_id>/docs/<int:doc_id>/annotations/<int:annotation_id>', AnnotationAPI.as_view()),
path('projects/', ProjectsView.as_view(), name='project-list'),
path('projects/<int:pk>/admin', ProjectAdminView.as_view(), name='project-admin'),
path('projects/<int:project_id>/download', DataDownloadAPI.as_view(), name='download'),
path('projects/<int:project_id>/', InboxView.as_view(), name='annotation'),
path('projects/<int:project_id>/apis/data', AnnotationAPIView.as_view()),
path('projects/<int:pk>/apis/raw_data', RawDataAPI.as_view(), name='data_api'),
path('projects/<int:project_id>/apis/search', SearchAPI.as_view()),
]

71
doccano/app/server/views.py

@ -37,76 +37,8 @@ class ProjectAdminView(LoginRequiredMixin, DetailView):
template_name = 'project_admin.html'
class AnnotationAPIView(View):
def get(self, request, *args, **kwargs):
project_id = kwargs.get('project_id')
project = Project.objects.get(id=project_id)
once_active_learned = len(Annotation.objects.all().exclude(prob=None)) > 0
if once_active_learned:
# Use Annotation model & RawData model.
# Left outer join data and annotation.
# Filter manual=False
# Sort prob
docs = Annotation.objects.all()
else:
# Left outer join data and annotation.
docs = Document.objects.filter(annotation__isnull=True, project=project)
docs = [{**d.as_dict(), **{'labels': []}} for d in docs]
if not docs:
docs = [{'id': None, 'labels': [], 'text': ''}]
return JsonResponse({'data': docs})
def post(self, request, *args, **kwargs):
body = json.loads(request.body)
data_id, label_id = body.get('id'), body.get('label_id') # {id:0, label_id:1}
data = Document.objects.get(id=data_id)
label = Label.objects.get(id=label_id)
Annotation(data=data, label=label, manual=True).save()
return JsonResponse({})
def delete(self, request, *args, **kwargs):
body = json.loads(request.body)
data_id, label_id = body.get('id'), body.get('label_id') # {id:0, label_id:1}
Annotation.objects.get(data=data_id, label=label_id).delete()
return JsonResponse({})
class SearchAPI(View):
def get(self, request, *args, **kwargs):
project_id = kwargs.get('project_id')
keyword = request.GET.get('keyword')
docs = Document.objects.filter(project=project_id, text__contains=keyword)
labels = [[a.as_dict() for a in Annotation.objects.filter(data=d.id)] for d in docs]
docs = [{**d.as_dict(), **{'labels': []}} for d in docs]
if not docs:
docs = [{'id': None, 'labels': [], 'text': ''}]
# Annotation.objects.select_related('data').all().filter(data__text__contains=keyword)
paginator = Paginator(docs, 5)
page = request.GET.get('page', 1)
page = paginator.get_page(page)
docs = page.object_list
return JsonResponse({'data': docs,
'has_next': page.has_next(),
'has_previous': page.has_previous(),
'previous_page_number': page.previous_page_number() if page.has_previous() else None,
'next_page_number': page.next_page_number() if page.has_next() else None})
class RawDataAPI(View):
def get(self, request, *args, **kwargs):
"""Get raw data."""
data = []
return JsonResponse({'data': data})
def post(self, request, *args, **kwargs):
"""Upload data."""
f = request.FILES['file']
@ -207,8 +139,9 @@ class AnnotationsAPI(generics.ListCreateAPIView):
label = Label.objects.get(id=label_id)
annotation = Annotation(data=doc, label=label, manual=True)
annotation.save()
serializer = self.serializer_class(annotation)
return Response(annotation)
return Response(serializer.data)
class AnnotationAPI(generics.RetrieveUpdateDestroyAPIView):

Loading…
Cancel
Save