Repository: doccano/doccano

Issue:
Mutli-label text classification export issues: same classes but in different orders
How to reproduce the behaviour
---------
<!-- Before submitting an issue, make sure to check the docs and closed issues and FAQ to see if any of the solutions work for you. https://github.com/doccano/doccano/wiki/Frequently-Asked-Questions -->
We are two annotators on a multi-label classification project. When I export the annotations, for some examples, me and my co-annotator have put the same labels, but on the exported CSV, they do not appear in the same order:

Annotator 1:

| text | labels |
| example 1 | label1#label2#label3 |

Annotator 2:

| text | labels |
| example 1 | label2#label3#label1 |

As I try to use these CSVs for comparing our annotations, this brings more difficulty.

<!-- Include a code example or the steps that led to the problem. Please try to be as specific as possible. -->

Your Environment
---------
<!-- Include details of your environment.-->
*   Operating System: Debian
*   Python Version Used: Don't know, I pulled the latest version from Docker Hub
*   When you install doccano: 3 days ago
*   How did you install doccano (Heroku button etc): Docker


Discussion:

Patch:
diff --git a/backend/api/views/download/writer.py b/backend/api/views/download/writer.py
index 5de1264e..a4d5293a 100644
--- a/backend/api/views/download/writer.py
+++ b/backend/api/views/download/writer.py
@@ -84,7 +84,7 @@ class CsvWriter(BaseWriter):
         return {
             'id': record.id,
             'data': record.data,
-            'label': '#'.join(record.label),
+            'label': '#'.join(sorted(record.label)),
             **record.metadata
         }
 
@@ -144,6 +144,7 @@ class FastTextWriter(LineWriter):
 
     def create_line(self, record):
         line = [f'__label__{label}' for label in record.label]
+        line.sort()
         line.append(record.data)
         line = ' '.join(line)
         return line


Test patch:
diff --git a/backend/api/tests/download/test_writer.py b/backend/api/tests/download/test_writer.py
index 2c48cb92..720244dd 100644
--- a/backend/api/tests/download/test_writer.py
+++ b/backend/api/tests/download/test_writer.py
@@ -32,6 +32,16 @@ class TestCSVWriter(unittest.TestCase):
         }
         self.assertEqual(line, expected)
 
+    def test_label_order(self):
+        writer = CsvWriter('.')
+        record1 = Record(id=0, data='', label=['labelA', 'labelB'], user='', metadata={})
+        record2 = Record(id=0, data='', label=['labelB', 'labelA'], user='', metadata={})
+        line1 = writer.create_line(record1)
+        line2 = writer.create_line(record2)
+        expected = 'labelA#labelB'
+        self.assertEqual(line1['label'], expected)
+        self.assertEqual(line2['label'], expected)
+
     @patch('os.remove')
     @patch('zipfile.ZipFile')
     @patch('csv.DictWriter.writerow')