Browse Source

Merge pull request #651 from doccano/bugfix/enable-to-show-error-messages-on-import-labels

[Bugfix] enable to show error messages on importing labels
pull/653/head
Hiroki Nakayama 4 years ago
committed by GitHub
parent
commit
8ba2394180
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 170 additions and 5 deletions
  1. 18
      app/api/tests/data/invalid_labels.json
  2. 18
      app/api/tests/data/valid_labels.json
  3. 47
      app/api/tests/test_api.py
  4. 4
      app/api/urls.py
  5. 24
      app/api/views.py
  6. 4
      frontend/components/containers/labels/LabelActionMenu.vue
  7. 39
      frontend/components/organisms/labels/LabelImportForm.vue
  8. 4
      frontend/services/label.service.js
  9. 17
      frontend/store/labels.js

18
app/api/tests/data/invalid_labels.json

@ -0,0 +1,18 @@
[
{
"id": 44,
"text": "Dog",
"prefix_key": null,
"suffix_key": "a",
"background_color": "#FF0000",
"text_color": "#ffffff"
},
{
"id": 45,
"text": "Dog",
"prefix_key": null,
"suffix_key": "c",
"background_color": "#FF0000",
"text_color": "#ffffff"
}
]

18
app/api/tests/data/valid_labels.json

@ -0,0 +1,18 @@
[
{
"id": 44,
"text": "Dog",
"prefix_key": null,
"suffix_key": "a",
"background_color": "#FF0000",
"text_color": "#ffffff"
},
{
"id": 45,
"text": "Cat",
"prefix_key": null,
"suffix_key": "c",
"background_color": "#FF0000",
"text_color": "#ffffff"
}
]

47
app/api/tests/test_api.py

@ -389,6 +389,53 @@ class TestLabelDetailAPI(APITestCase):
remove_all_role_mappings()
class TestLabelUploadAPI(APITestCase):
@classmethod
def setUpTestData(cls):
cls.project_member_name = 'project_member_name'
cls.project_member_pass = 'project_member_pass'
cls.non_project_member_name = 'non_project_member_name'
cls.non_project_member_pass = 'non_project_member_pass'
cls.super_user_name = 'super_user_name'
cls.super_user_pass = 'super_user_pass'
create_default_roles()
project_member = User.objects.create_user(username=cls.project_member_name,
password=cls.project_member_pass)
User.objects.create_user(username=cls.non_project_member_name, password=cls.non_project_member_pass)
project_admin = User.objects.create_user(username=cls.super_user_name,
password=cls.super_user_pass)
project = mommy.make('Project', users=[project_member, project_admin])
cls.url = reverse(viewname='label_upload', args=[project.id])
create_default_roles()
assign_user_to_role(project_member=project_admin, project=project, role_name=settings.ROLE_PROJECT_ADMIN)
assign_user_to_role(project_member=project_member, project=project, role_name=settings.ROLE_ANNOTATOR)
def help_to_upload_file(self, filename, expected_status):
with open(os.path.join(DATA_DIR, filename), 'rb') as f:
response = self.client.post(self.url, data={'file': f})
self.assertEqual(response.status_code, expected_status)
def test_allows_project_admin_to_upload_label(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
self.help_to_upload_file('valid_labels.json', status.HTTP_201_CREATED)
def test_disallows_project_member_to_upload_label(self):
self.client.login(username=self.project_member_name,
password=self.project_member_pass)
self.help_to_upload_file('valid_labels.json', status.HTTP_403_FORBIDDEN)
def test_try_to_upload_invalid_file(self):
self.client.login(username=self.super_user_name,
password=self.super_user_pass)
self.help_to_upload_file('invalid_labels.json', status.HTTP_400_BAD_REQUEST)
@classmethod
def doCleanups(cls):
remove_all_role_mappings()
class TestDocumentListAPI(APITestCase, TestUtilsMixin):
@classmethod

4
app/api/urls.py

@ -4,7 +4,7 @@ from rest_framework.urlpatterns import format_suffix_patterns
from .views import Me, Features, Users
from .views import ProjectList, ProjectDetail
from .views import LabelList, LabelDetail, ApproveLabelsAPI
from .views import LabelList, LabelDetail, ApproveLabelsAPI, LabelUploadAPI
from .views import DocumentList, DocumentDetail
from .views import AnnotationList, AnnotationDetail
from .views import TextUploadAPI, TextDownloadAPI, CloudUploadAPI
@ -24,6 +24,8 @@ urlpatterns = [
StatisticsAPI.as_view(), name='statistics'),
path('projects/<int:project_id>/labels',
LabelList.as_view(), name='label_list'),
path('projects/<int:project_id>/label-upload',
LabelUploadAPI.as_view(), name='label_upload'),
path('projects/<int:project_id>/labels/<int:label_id>',
LabelDetail.as_view(), name='label_detail'),
path('projects/<int:project_id>/docs',

24
app/api/views.py

@ -1,5 +1,8 @@
import json
from django.conf import settings
from django.contrib.auth.models import User
from django.db import transaction
from django.db.utils import IntegrityError
from django.shortcuts import get_object_or_404, redirect
from django_filters.rest_framework import DjangoFilterBackend
from django.db.models import Count, F, Q
@ -368,3 +371,24 @@ class RoleMappingDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = RoleMappingSerializer
lookup_url_kwarg = 'rolemapping_id'
permission_classes = [IsAuthenticated & IsProjectAdmin]
class LabelUploadAPI(APIView):
parser_classes = (MultiPartParser,)
permission_classes = [IsAuthenticated & IsProjectAdmin]
@transaction.atomic
def post(self, request, *args, **kwargs):
if 'file' not in request.data:
raise ParseError('Empty content')
labels = json.load(request.data['file'])
project = get_object_or_404(Project, pk=kwargs['project_id'])
try:
for label in labels:
serializer = LabelSerializer(data=label)
serializer.is_valid(raise_exception=True)
serializer.save(project=project)
return Response(status=status.HTTP_201_CREATED)
except IntegrityError:
content = {'error': 'IntegrityError: you cannot create a label with same name or shortkey.'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)

4
frontend/components/containers/labels/LabelActionMenu.vue

@ -15,7 +15,7 @@
</base-dialog>
<base-dialog :dialog="importDialog">
<label-import-form
:import-label="importLabels"
:upload-label="uploadLabel"
@close="importDialog=false"
/>
</base-dialog>
@ -58,7 +58,7 @@ export default {
},
methods: {
...mapActions('labels', ['createLabel', 'importLabels', 'exportLabels']),
...mapActions('labels', ['createLabel', 'uploadLabel', 'exportLabels']),
...mapActions('projects', ['setCurrentProject']),
handleDownload() {

39
frontend/components/organisms/labels/LabelImportForm.vue

@ -21,6 +21,10 @@
The file could not be uploaded. Maybe invalid format.
Please check available formats carefully.
</v-alert>
<h2>Example format</h2>
<code class="mb-10 pa-5 highlight">
<span>{{ exampleFormat }}</span>
</code>
<h2>Select a file</h2>
<v-file-input
v-model="file"
@ -42,7 +46,7 @@ export default {
BaseCard
},
props: {
importLabel: {
uploadLabel: {
type: Function,
default: () => {},
required: true
@ -57,6 +61,27 @@ export default {
}
},
computed: {
exampleFormat() {
const data = [
{
text: 'Dog',
suffix_key: 'a',
background_color: '#FF0000',
text_color: '#ffffff'
},
{
text: 'Cat',
suffix_key: 'c',
background_color: '#FF0000',
text_color: '#ffffff'
}
]
console.log(JSON.stringify(data, null, 4))
return JSON.stringify(data, null, 4)
}
},
methods: {
cancel() {
this.$emit('close')
@ -69,7 +94,7 @@ export default {
},
create() {
if (this.validate()) {
this.importLabel({
this.uploadLabel({
projectId: this.$route.params.id,
file: this.file
})
@ -85,3 +110,13 @@ export default {
}
}
</script>
<style scoped>
.highlight {
font-size: 100%;
width: 100%;
}
.highlight:before {
content: ''
}
</style>

4
frontend/services/label.service.js

@ -20,6 +20,10 @@ class LabelService {
updateLabel(projectId, labelId, payload) {
return this.request.patch(`/projects/${projectId}/labels/${labelId}`, payload)
}
uploadFile(projectId, payload, config = {}) {
return this.request.post(`/projects/${projectId}/label-upload`, payload, config)
}
}
export default new LabelService()

17
frontend/store/labels.js

@ -115,5 +115,22 @@ export const actions = {
.finally(() => {
commit('setLoading', false)
})
},
uploadLabel({ commit, dispatch }, data) {
commit('setLoading', true)
const formData = new FormData()
formData.append('file', data.file)
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
return LabelService.uploadFile(data.projectId, formData, config)
.then((response) => {
dispatch('getLabelList', data)
})
.finally(() => {
commit('setLoading', false)
})
}
}
Loading…
Cancel
Save