Repository: doccano/doccano
Issue: Can't change label after assigning the shortcut key

Thank you for the work you put into this amazing tool.
I have trouble when trying to rename the labels or changing the colors when they have a shortcut key assigned. 

How to reproduce the behavior:
Create a label, for instance "label_1", and assign a shortcut key to it.

Once the label is created, click on "Edit" and try to change the color. The message "You cannot use same label name or shortcut key." will then appear.

The same issue happens if you want to rename the label. For instance, try to rename the label to "Label_1" with a capital letter, and the same error message will appear.


To avoid this issue, I have to assign an unused shortcut key to the label while I change its name and color, validate, and then change the shortcut key back to the one I want to use.

**System Information**

*   Operating System: Windows 10
*   Installed on Heroku with the Heroku button.


Changed files:
app/api/serializers.py, app/api/tests/test_api.py

diff --git a/app/api/serializers.py b/app/api/serializers.py
index 8df0981d..2d0d38d1 100644
--- a/app/api/serializers.py
+++ b/app/api/serializers.py
@@ -19,11 +19,8 @@ class UserSerializer(serializers.ModelSerializer):
 class LabelSerializer(serializers.ModelSerializer):
 
     def validate(self, attrs):
-        if 'prefix_key' not in attrs and 'suffix_key' not in attrs:
-            return super().validate(attrs)
-
-        prefix_key = attrs['prefix_key']
-        suffix_key = attrs['suffix_key']
+        prefix_key = attrs.get('prefix_key')
+        suffix_key = attrs.get('suffix_key')
 
         # In the case of user don't set any shortcut key.
         if prefix_key is None and suffix_key is None:
@@ -37,13 +34,22 @@ class LabelSerializer(serializers.ModelSerializer):
         try:
             context = self.context['request'].parser_context
             project_id = context['kwargs']['project_id']
+            label_id = context['kwargs'].get('label_id')
         except (AttributeError, KeyError):
             pass  # unit tests don't always have the correct context set up
         else:
-            if Label.objects.filter(suffix_key=suffix_key,
-                                    prefix_key=prefix_key,
-                                    project=project_id).exists():
-                raise ValidationError('Duplicate key.')
+            conflicting_labels = Label.objects.filter(
+                suffix_key=suffix_key,
+                prefix_key=prefix_key,
+                project=project_id,
+            )
+
+            if label_id is not None:
+                conflicting_labels = conflicting_labels.exclude(id=label_id)
+
+            if conflicting_labels.exists():
+                raise ValidationError('Duplicate shortcut key.')
+
         return super().validate(attrs)
 
     class Meta:
diff --git a/app/api/tests/test_api.py b/app/api/tests/test_api.py
index adc6b009..928ceefd 100644
--- a/app/api/tests/test_api.py
+++ b/app/api/tests/test_api.py
@@ -256,7 +256,9 @@ class TestLabelDetailAPI(APITestCase):
                                                    email='fizz@buzz.com')
         project = mommy.make('Project', users=[project_member, super_user])
         cls.label = mommy.make('Label', project=project)
+        cls.label_with_shortcut = mommy.make('Label', suffix_key='l', project=project)
         cls.url = reverse(viewname='label_detail', args=[project.id, cls.label.id])
+        cls.url_with_shortcut = reverse(viewname='label_detail', args=[project.id, cls.label_with_shortcut.id])
         cls.data = {'text': 'example'}
 
     def test_returns_label_to_project_member(self):
@@ -277,6 +279,12 @@ class TestLabelDetailAPI(APITestCase):
         response = self.client.patch(self.url, format='json', data=self.data)
         self.assertEqual(response.data['text'], self.data['text'])
 
+    def test_allows_superuser_to_update_label_with_shortcut(self):
+        self.client.login(username=self.super_user_name,
+                          password=self.super_user_pass)
+        response = self.client.patch(self.url_with_shortcut, format='json', data={'suffix_key': 's'})
+        self.assertEqual(response.data['suffix_key'], 's')
+
     def test_disallows_project_member_to_update_label(self):
         self.client.login(username=self.project_member_name,
                           password=self.project_member_pass)