diff --git a/app/db.sqlite3 b/app/db.sqlite3 index 12b9af51..25ee1a35 100644 Binary files a/app/db.sqlite3 and b/app/db.sqlite3 differ diff --git a/app/server/api.py b/app/server/api.py index 99a6b025..3c2557f9 100644 --- a/app/server/api.py +++ b/app/server/api.py @@ -137,61 +137,35 @@ class AnnotationsAPI(generics.ListCreateAPIView): permission_classes = (IsAuthenticated, IsProjectUser) def get_serializer_class(self): - project_id = self.kwargs['project_id'] - project = get_object_or_404(Project, pk=project_id) + project = get_object_or_404(Project, pk=self.kwargs['project_id']) self.serializer_class = project.get_annotation_serializer() return self.serializer_class def get_queryset(self): - project_id = self.kwargs['project_id'] - project = get_object_or_404(Project, pk=project_id) - doc_id = self.kwargs['doc_id'] - document = get_object_or_404(Document, pk=doc_id, project=project) + project = get_object_or_404(Project, pk=self.kwargs['project_id']) + document = project.documents.get(id=self.kwargs['doc_id']) self.queryset = document.get_annotations() return self.queryset - def post(self, request, *args, **kwargs): + def perform_create(self, serializer): doc = get_object_or_404(Document, pk=self.kwargs['doc_id']) - project = get_object_or_404(Project, pk=self.kwargs['project_id']) - self.serializer_class = project.get_annotation_serializer() - if project.is_type_of(Project.DOCUMENT_CLASSIFICATION): - label = get_object_or_404(Label, pk=request.data['label_id']) - annotation = DocumentAnnotation(document=doc, label=label, manual=True, - user=self.request.user) - elif project.is_type_of(Project.SEQUENCE_LABELING): - label = get_object_or_404(Label, pk=request.data['label_id']) - annotation = SequenceAnnotation(document=doc, label=label, manual=True, - user=self.request.user, - start_offset=request.data['start_offset'], - end_offset=request.data['end_offset']) - elif project.is_type_of(Project.Seq2seq): - text = request.data['text'] - annotation = Seq2seqAnnotation(document=doc, - text=text, - manual=True, - user=self.request.user) - annotation.save() - serializer = self.serializer_class(annotation) - - return Response(serializer.data) + serializer.save(document=doc, user=self.request.user) class AnnotationAPI(generics.RetrieveUpdateDestroyAPIView): permission_classes = (IsAuthenticated, IsProjectUser, IsOwnAnnotation) def get_queryset(self): - doc_id = self.kwargs['doc_id'] - document = get_object_or_404(Document, pk=doc_id) + document = get_object_or_404(Document, pk=self.kwargs['doc_id']) self.queryset = document.get_annotations() return self.queryset def get_object(self): - annotation_id = self.kwargs['annotation_id'] queryset = self.filter_queryset(self.get_queryset()) - obj = get_object_or_404(queryset, pk=annotation_id) + obj = get_object_or_404(queryset, pk=self.kwargs['annotation_id']) self.check_object_permissions(self.request, obj) return obj diff --git a/app/server/serializers.py b/app/server/serializers.py index 55db8b17..f261da3a 100644 --- a/app/server/serializers.py +++ b/app/server/serializers.py @@ -19,12 +19,16 @@ class TextSerializer(serializers.ModelSerializer): class DocumentAnnotationSerializer(serializers.ModelSerializer): - label = LabelSerializer() + label = serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()) class Meta: model = DocumentAnnotation fields = ('id', 'prob', 'label') + def create(self, validated_data): + annotation = DocumentAnnotation.objects.create(**validated_data) + return annotation + class DocumentSerializer(serializers.ModelSerializer): labels = DocumentAnnotationSerializer(source='doc_annotations', many=True) @@ -35,12 +39,16 @@ class DocumentSerializer(serializers.ModelSerializer): class SequenceAnnotationSerializer(serializers.ModelSerializer): - label = LabelSerializer() + label = serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()) class Meta: model = SequenceAnnotation fields = ('id', 'prob', 'label', 'start_offset', 'end_offset') + def create(self, validated_data): + annotation = SequenceAnnotation.objects.create(**validated_data) + return annotation + class SequenceSerializer(serializers.ModelSerializer): labels = SequenceAnnotationSerializer(source='seq_annotations', many=True) diff --git a/app/server/static/bundle/document_classification.js b/app/server/static/bundle/document_classification.js index 494118c2..39de770b 100644 --- a/app/server/static/bundle/document_classification.js +++ b/app/server/static/bundle/document_classification.js @@ -161,7 +161,7 @@ eval("var g;\n\n// This works in non-strict mode\ng = (function() {\n\treturn th /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _mixin_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mixin.js */ \"./static/js/mixin.js\");\n/* harmony import */ var _http_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./http.js */ \"./static/js/http.js\");\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(__webpack_require__(/*! vue-shortkey */ \"./node_modules/vue-shortkey/dist/index.js\"), { prevent: ['input', 'textarea'] });\n\n\n\nvar vm = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]({\n el: '#mail-app',\n delimiters: ['[[', ']]'],\n mixins: [_mixin_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]],\n\n methods: {\n addLabel: async function (label_id) {\n for (var i = 0; i < this.items[this.cur]['labels'].length; i++) {\n var item = this.items[this.cur]['labels'][i];\n if (label_id == item.label.id) {\n this.deleteLabel(i);\n return;\n }\n }\n\n var payload = {\n 'label_id': label_id\n };\n\n var doc_id = this.items[this.cur].id;\n await _http_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"].post(`docs/${doc_id}/annotations/`, payload).then(response => {\n this.items[this.cur]['labels'].push(response.data);\n })\n }\n }\n});\n\n//# sourceURL=webpack:///./static/js/document_classification.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _mixin_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mixin.js */ \"./static/js/mixin.js\");\n/* harmony import */ var _http_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./http.js */ \"./static/js/http.js\");\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(__webpack_require__(/*! vue-shortkey */ \"./node_modules/vue-shortkey/dist/index.js\"), { prevent: ['input', 'textarea'] });\n\n\n\nvar vm = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]({\n el: '#mail-app',\n delimiters: ['[[', ']]'],\n mixins: [_mixin_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]],\n\n methods: {\n addLabel: async function (label_id) {\n for (var i = 0; i < this.items[this.cur]['labels'].length; i++) {\n var item = this.items[this.cur]['labels'][i];\n if (label_id == item.label.id) {\n this.deleteLabel(i);\n return;\n }\n }\n\n var payload = {\n 'label': label_id\n };\n\n var doc_id = this.items[this.cur].id;\n await _http_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"].post(`docs/${doc_id}/annotations/`, payload).then(response => {\n this.items[this.cur]['labels'].push(response.data);\n })\n }\n }\n});\n\n//# sourceURL=webpack:///./static/js/document_classification.js?"); /***/ }), diff --git a/app/server/static/bundle/sequence_labeling.js b/app/server/static/bundle/sequence_labeling.js index 91f59d4e..dd5995aa 100644 --- a/app/server/static/bundle/sequence_labeling.js +++ b/app/server/static/bundle/sequence_labeling.js @@ -185,7 +185,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _htt /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _mixin_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mixin.js */ \"./static/js/mixin.js\");\n/* harmony import */ var _http_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./http.js */ \"./static/js/http.js\");\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(__webpack_require__(/*! vue-shortkey */ \"./node_modules/vue-shortkey/dist/index.js\"), { prevent: ['input', 'textarea'] });\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('annotator', {\n template: '
\\\n {{ text.slice(r.start_offset, r.end_offset) }}\\\n
',\n props: {\n 'labels': Array, // [{id: Integer, color: String, text: String}]\n 'text': String,\n 'entityPositions': Array, //[{'startOffset': 10, 'endOffset': 15, 'label_id': 1}]\n },\n data() {\n return {\n startOffset: 0,\n endOffset: 0,\n }\n },\n methods: {\n setSelectedRange: function (e) {\n if (window.getSelection) {\n var range = window.getSelection().getRangeAt(0);\n var preSelectionRange = range.cloneRange();\n preSelectionRange.selectNodeContents(this.$el);\n preSelectionRange.setEnd(range.startContainer, range.startOffset);\n var start = preSelectionRange.toString().length;\n var end = start + range.toString().length;\n } else if (document.selection && document.selection.type != 'Control') {\n var selectedTextRange = document.selection.createRange();\n var preSelectionTextRange = document.body.createTextRange();\n preSelectionTextRange.moveToElementText(this.$el);\n preSelectionTextRange.setEndPoint('EndToStart', selectedTextRange);\n var start = preSelectionTextRange.text.length;\n var end = start + selectedTextRange.text.length;\n }\n this.startOffset = start;\n this.endOffset = end;\n console.log(start, end);\n },\n validRange: function () {\n if (this.startOffset == this.endOffset) {\n return false\n } else if (this.startOffset > this.text.length || this.endOffset > this.text.length) {\n return false\n } else if (this.startOffset < 0 || this.endOffset < 0) {\n return false\n } else {\n return true\n }\n },\n resetRange: function () {\n this.startOffset = 0;\n this.endOffset = 0\n },\n addLabel: function (label_id) {\n if (this.validRange()) {\n var label = {\n start_offset: this.startOffset,\n end_offset: this.endOffset,\n label_id: label_id\n };\n this.$emit('add-label', label);\n }\n },\n deleteLabel: function (index) {\n this.$emit('delete-label', index);\n },\n makeLabel: function (start_offset, end_offset) {\n var label = {\n id: 0,\n label: {\n id: -1,\n text: '',\n shortcut: '',\n background_color: '',\n text_color: ''\n },\n start_offset: start_offset,\n end_offset: end_offset\n }\n return label\n }\n },\n watch: {\n entityPositions: function () {\n this.resetRange()\n }\n },\n computed: {\n sortedEntityPositions: function () {\n this.entityPositions = this.entityPositions.sort((a, b) => a.start_offset - b.start_offset);\n return this.entityPositions\n },\n chunks: function () {\n var res = [];\n var left = 0;\n for (let i in this.sortedEntityPositions) {\n var e = this.sortedEntityPositions[i];\n var l = this.makeLabel(left, e['start_offset'])\n res.push(l);\n res.push(e);\n left = e['end_offset'];\n }\n var l = this.makeLabel(left, this.text.length)\n res.push(l)\n\n return res\n }\n }\n})\n\nvar vm = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]({\n el: '#mail-app',\n delimiters: ['[[', ']]'],\n mixins: [_mixin_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]],\n methods: {\n annotate: function (label_id) {\n var payload = this.$refs.annotator.addLabel(label_id);\n },\n addLabel: function (label) {\n var payload = label;\n var doc_id = this.items[this.cur].id;\n _http_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"].post(`docs/${doc_id}/annotations/`, payload).then(response => {\n this.items[this.cur]['labels'].push(response.data);\n })\n },\n deleteLabel: function (label) {\n var doc_id = this.items[this.cur].id;\n _http_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"].delete(`docs/${doc_id}/annotations/${label.id}`).then(response => {\n this.items[this.cur]['labels'].splice(this.items[this.cur]['labels'].indexOf(label), 1)\n });\n },\n }\n});\n\n//# sourceURL=webpack:///./static/js/sequence_labeling.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _mixin_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mixin.js */ \"./static/js/mixin.js\");\n/* harmony import */ var _http_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./http.js */ \"./static/js/http.js\");\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(__webpack_require__(/*! vue-shortkey */ \"./node_modules/vue-shortkey/dist/index.js\"), { prevent: ['input', 'textarea'] });\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('annotator', {\n template: '
\\\n {{ text.slice(r.start_offset, r.end_offset) }}\\\n
',\n props: {\n 'labels': Array, // [{id: Integer, color: String, text: String}]\n 'text': String,\n 'entityPositions': Array, //[{'startOffset': 10, 'endOffset': 15, 'label_id': 1}]\n },\n data() {\n return {\n startOffset: 0,\n endOffset: 0,\n }\n },\n methods: {\n setSelectedRange: function (e) {\n if (window.getSelection) {\n var range = window.getSelection().getRangeAt(0);\n var preSelectionRange = range.cloneRange();\n preSelectionRange.selectNodeContents(this.$el);\n preSelectionRange.setEnd(range.startContainer, range.startOffset);\n var start = preSelectionRange.toString().length;\n var end = start + range.toString().length;\n } else if (document.selection && document.selection.type != 'Control') {\n var selectedTextRange = document.selection.createRange();\n var preSelectionTextRange = document.body.createTextRange();\n preSelectionTextRange.moveToElementText(this.$el);\n preSelectionTextRange.setEndPoint('EndToStart', selectedTextRange);\n var start = preSelectionTextRange.text.length;\n var end = start + selectedTextRange.text.length;\n }\n this.startOffset = start;\n this.endOffset = end;\n console.log(start, end);\n },\n validRange: function () {\n if (this.startOffset == this.endOffset) {\n return false\n } else if (this.startOffset > this.text.length || this.endOffset > this.text.length) {\n return false\n } else if (this.startOffset < 0 || this.endOffset < 0) {\n return false\n } else {\n return true\n }\n },\n resetRange: function () {\n this.startOffset = 0;\n this.endOffset = 0\n },\n addLabel: function (label_id) {\n if (this.validRange()) {\n var label = {\n start_offset: this.startOffset,\n end_offset: this.endOffset,\n label_id: label_id\n };\n this.$emit('add-label', label);\n }\n },\n deleteLabel: function (index) {\n this.$emit('delete-label', index);\n },\n makeLabel: function (start_offset, end_offset) {\n var label = {\n id: 0,\n label: {\n id: -1,\n text: '',\n shortcut: '',\n background_color: '',\n text_color: ''\n },\n start_offset: start_offset,\n end_offset: end_offset\n }\n return label\n }\n },\n watch: {\n entityPositions: function () {\n this.resetRange()\n }\n },\n computed: {\n sortedEntityPositions: function () {\n this.entityPositions = this.entityPositions.sort((a, b) => a.start_offset - b.start_offset);\n return this.entityPositions\n },\n chunks: function () {\n var res = [];\n var left = 0;\n for (let i in this.sortedEntityPositions) {\n var e = this.sortedEntityPositions[i];\n var l = this.makeLabel(left, e['start_offset'])\n res.push(l);\n res.push(e);\n left = e['end_offset'];\n }\n var l = this.makeLabel(left, this.text.length)\n res.push(l)\n\n return res\n }\n }\n})\n\nvar vm = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]({\n el: '#mail-app',\n delimiters: ['[[', ']]'],\n mixins: [_mixin_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]],\n methods: {\n annotate: function (label_id) {\n var payload = this.$refs.annotator.addLabel(label_id);\n },\n addLabel: function (label) {\n var payload = label;\n var doc_id = this.items[this.cur].id;\n payload['label'] = label.label_id;\n _http_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"].post(`docs/${doc_id}/annotations/`, payload).then(response => {\n this.items[this.cur]['labels'].push(response.data);\n })\n },\n deleteLabel: function (label) {\n var doc_id = this.items[this.cur].id;\n _http_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"].delete(`docs/${doc_id}/annotations/${label.id}`).then(response => {\n this.items[this.cur]['labels'].splice(this.items[this.cur]['labels'].indexOf(label), 1)\n });\n },\n }\n});\n\n//# sourceURL=webpack:///./static/js/sequence_labeling.js?"); /***/ }) diff --git a/app/server/static/js/document_classification.js b/app/server/static/js/document_classification.js index 22615329..3e7c4900 100644 --- a/app/server/static/js/document_classification.js +++ b/app/server/static/js/document_classification.js @@ -19,7 +19,7 @@ var vm = new Vue({ } var payload = { - 'label_id': label_id + 'label': label_id }; var doc_id = this.items[this.cur].id; diff --git a/app/server/static/js/sequence_labeling.js b/app/server/static/js/sequence_labeling.js index 634ba8ad..d8a7b8c1 100644 --- a/app/server/static/js/sequence_labeling.js +++ b/app/server/static/js/sequence_labeling.js @@ -127,6 +127,7 @@ var vm = new Vue({ addLabel: function (label) { var payload = label; var doc_id = this.items[this.cur].id; + payload['label'] = label.label_id; HTTP.post(`docs/${doc_id}/annotations/`, payload).then(response => { this.items[this.cur]['labels'].push(response.data); })