Repository: hydroshare/hydroshare

Issue:
(BUG) Author order changed after clicking the view botton
See screenshots below. It works fine when I only have 5 authors, but this time I have 12 authors. I use "drag and drop" to rearrange the author order in the editing mode, but after clicking the view button, the order changed. Tested in Chrome and MS Edge multiple times.


![1](https://user-images.githubusercontent.com/49882529/178112886-f8011d59-3d88-4f6e-ad26-c17ea12ba92c.png)
![2](https://user-images.githubusercontent.com/49882529/178112887-6b2148f3-5f0c-4e31-9af9-a33dab1a5790.png)




Discussion:
Update: Now the author's order at the beginning and in the "How to Cite" section are different.

Link:   https://www.hydroshare.org/resource/59175cf99a58462f901cdf56ec79ddbe/


![Capture](https://user-images.githubusercontent.com/49882529/178155660-82b19641-a0b2-4c06-8a85-e4387c7c023c.PNG)


When I open [the resource](https://www.hydroshare.org/resource/59175cf99a58462f901cdf56ec79ddbe/) right now, the order of the two sections match:
<img width="727" alt="image" src="https://user-images.githubusercontent.com/17934193/178271375-1ad80a16-2478-433e-8708-c38baa52e8ff.png">

<img width="713" alt="image" src="https://user-images.githubusercontent.com/17934193/178271435-12c194bd-a390-4e22-a03b-83601727386b.png">

[v1.56.1](https://github.com/hydroshare/hydroshare/tree/1.56.1), firefox and chrome
The particular resource mentioned here is fixed, because I fixed it manually (at @JianWangUSU s request) by deleting authors then adding them in the order needed, and because they need to publish this resource in a hurry.  But the systemic problem that leads to the occurrence of this remains.
Thanks @dtarb that's what I suspected, good to have confirmation. I'm looking into this...
Related to #4679 which is slated for fix in [1.57](https://github.com/hydroshare/hydroshare/tree/rc-1.57)
Thank you all for your quick response. I have one more enhancement suggestion besides this. It would be great if I could change the owners' order. I hope "Jian Wang" could be listed as the second owner for the following resource. It seems like drag and drop doesn't work for the owner's order.

![image](https://user-images.githubusercontent.com/49882529/178276654-3b9dc4c2-ed85-4ba9-b0e6-c060b259c8b1.png)


Another resource Link: https://www.hydroshare.org/resource/a065c17a6a02463ca9e7d69f22baed10/

Owner order is not a concept that we have to date attributed meaning to.  What is the reason for there needing to be an order in owners.
> Owner order is not a concept that we have to date attributed meaning to. What is the reason for there needing to be an order in owners.

I got it. Thanks. To me, I would think owner's order represents the contribution to the repository. The first owner contributes the most to codes and data.
We have published all related resources. Thanks for your help again.
I reproduced the primary issue (author order changing within the "Authors" section between edit and view mode) on [1.56.1](https://github.com/hydroshare/hydroshare/tree/1.56.1) and verified that it was present at least as far back as 1.53.

I haven't yet seen a case where the "How to Cite" section is out of sync with the "Authors" section _after page refresh_ but will continue to investigate that

Resolving patch:
diff --git a/hs_core/management/commands/reorder_authors.py b/hs_core/management/commands/reorder_authors.py
new file mode 100644
--- /dev/null
+++ b/hs_core/management/commands/reorder_authors.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+"""
+Fix duplicate author "order" values
+
+Related to https://github.com/hydroshare/hydroshare/issues/4695
+"""
+
+from django.core.management.base import BaseCommand
+from hs_core.models import BaseResource
+from hs_core.hydroshare.utils import set_dirty_bag_flag
+
+
+class Command(BaseCommand):
+    help = "Fix duplicate author 'order' values"
+
+    def handle(self, *args, **options):
+        resources = BaseResource.objects.filter(raccess__published=False).only('object_id', 'short_id')
+        for res in resources:
+            if res.metadata is not None:
+                creators = res.metadata.creators.all()
+                is_dirty = False
+                for index, creator in enumerate(creators, start=1):
+                    if creator.order != index:
+                        print("*" * 100)
+                        print(f"Author out of order.\nR:{res.short_id}"
+                                f"\nExpected: {index}, got: {creator.order}")
+                        creator.order = index
+                        creator.save()
+                        is_dirty = True
+                if is_dirty:
+                    set_dirty_bag_flag(res)
diff --git a/hs_core/models.py b/hs_core/models.py
--- a/hs_core/models.py
+++ b/hs_core/models.py
@@ -604,7 +604,7 @@ def update(cls, element_id, **kwargs):
                             res_cr.order += 1
                             res_cr.save()
                     else:
-                        if res_cr.order > party.order:
+                        if res_cr.order > party.order and res_cr.order <= creator_order:
                             res_cr.order -= 1
                             res_cr.save()
 


Test patch:
diff --git a/hs_core/tests/api/native/test_reorder_authors_management_command.py b/hs_core/tests/api/native/test_reorder_authors_management_command.py
new file mode 100755
--- /dev/null
+++ b/hs_core/tests/api/native/test_reorder_authors_management_command.py
@@ -0,0 +1,232 @@
+from unittest import TestCase
+
+from django.contrib.auth.models import Group, User
+from django.core.management import call_command
+
+from hs_core.models import GenericResource, Creator
+from hs_core import hydroshare
+from hs_core.hydroshare import resource
+
+from dateutil import parser
+
+
+class TestReorderAuthorsCommand(TestCase):
+    def setUp(self):
+        super(TestReorderAuthorsCommand, self).setUp()
+
+        self.group, _ = Group.objects.get_or_create(name="Hydroshare Author")
+        self.user = hydroshare.create_account(
+            "user1@nowhere.com",
+            username="user1",
+            first_name="Creator_FirstName",
+            last_name="Creator_LastName",
+            superuser=False,
+            groups=[],
+        )
+
+        self.res = hydroshare.create_resource(
+            resource_type="GenericResource",
+            owner=self.user,
+            title="Generic resource",
+            keywords=["kw1", "kw2"],
+        )
+
+        # add 4 creators element (so in total we will have 5 creators)
+        resource.create_metadata_element(
+            self.res.short_id, "creator", name="John Smith"
+        )
+        resource.create_metadata_element(
+            self.res.short_id, "creator", name="Lisa McWill"
+        )
+        resource.create_metadata_element(
+            self.res.short_id, "creator", name="Kelly Anderson"
+        )
+        resource.create_metadata_element(
+            self.res.short_id, "creator", name="Mark Miller"
+        )
+
+        self.update_command = "reorder_authors"
+
+    def tearDown(self):
+        super(TestReorderAuthorsCommand, self).tearDown()
+        User.objects.all().delete()
+        Group.objects.all().delete()
+        GenericResource.objects.all().delete()
+        Creator.objects.all().delete()
+
+    def test_command_doesnt_alter_if_aready_correct(self):
+        """
+        Testing author order is maintained if they were already correct
+        """
+        citation_original = self.res.get_citation()
+
+        self.assertEqual(self.res.get_citation(), citation_original)
+        for index, creator in enumerate(self.res.metadata.creators.all(), start=1):
+            self.assertEqual(index, creator.order)
+
+        # run  update command to fix author order
+        call_command(self.update_command)
+
+        self.assertEqual(self.res.get_citation(), citation_original)
+        for index, creator in enumerate(self.res.metadata.creators.all(), start=1):
+            self.assertEqual(index, creator.order)
+
+    def test_command_alters_if_incorrect(self):
+        """
+        Testing author order is updated if it was incorrect
+        """
+
+        # Intentionally make a creator list with duplicate orders
+        john = self.res.metadata.creators.filter(name="John Smith").first()
+        lisa = self.res.metadata.creators.filter(name="Lisa McWill").first()
+        self.assertEqual(john.order, 2)
+        john.order = 3
+        john.save()
+        self.assertEqual(john.order, lisa.order)
+
+        # run  update command to fix author order
+        call_command(self.update_command)
+
+        for index, creator in enumerate(self.res.metadata.creators.all(), start=1):
+            self.assertEqual(index, creator.order)
+
+    def test_command_alters_missing_first_author(self):
+        """
+        Testing author order is updated if there was a missing first author
+        """
+
+        # Intentionally make a creator list with duplicate orders
+        john = self.res.metadata.creators.filter(name="John Smith").first()
+        hs_author = self.res.metadata.creators.filter(
+            name="Creator_LastName, Creator_FirstName"
+        ).first()
+        self.assertEqual(hs_author.order, 1)
+        hs_author.order = 2
+        hs_author.save()
+        self.assertEqual(john.order, hs_author.order)
+        self.assertIsNone(self.res.metadata.creators.filter(order=1).first())
+
+        # run  update command to fix author order
+        call_command(self.update_command)
+
+        for index, creator in enumerate(self.res.metadata.creators.all(), start=1):
+            self.assertEqual(index, creator.order)
+
+        # query for first author no longer raises exception
+        first_author = self.res.metadata.creators.filter(order=1).first()
+        self.assertIsNotNone(first_author)
+
+        john = self.res.metadata.creators.filter(name="John Smith").first()
+        hs_author = self.res.metadata.creators.filter(
+            name="Creator_LastName, Creator_FirstName"
+        ).first()
+        self.assertNotEqual(john.order, hs_author.order)
+
+        # Demonstrate that we don't actually know which author became "first" author
+        self.assertIn(john.order, [1, 2])
+        self.assertIn(hs_author.order, [1, 2])
+
+    def test_command_fixes_multiple_authors(self):
+        """
+        Testing author order is updated if there were multiple duplicate author orders
+        """
+
+        # Intentionally make a creator list with duplicate orders
+        lisa = self.res.metadata.creators.filter(name="Lisa McWill").first()
+        last_author = self.res.metadata.creators.last()
+        self.assertEqual(lisa.order, 3)
+        self.assertEqual(last_author.order, 5)
+        lisa.order = 2
+        last_author.order = 4
+        lisa.save()
+        last_author.save()
+
+        cit_original = self.res.get_citation()
+
+        # run  update command to fix author order
+        call_command(self.update_command)
+
+        for index, creator in enumerate(self.res.metadata.creators.all(), start=1):
+            self.assertEqual(index, creator.order)
+
+        self.assertEqual(self.res.get_citation(), cit_original)
+
+    def test_command_fixes_triplicate_authors(self):
+        """
+        Testing author order is updated if there were 3 authors with the same order
+
+        This tests the case where two authors had faulty orders due to being moved,
+        resulting in a triplicate of author.order.
+        """
+
+        # Intentionally make a creator list with duplicate orders
+        lisa = self.res.metadata.creators.filter(name="Lisa McWill").first()
+        last_author = self.res.metadata.creators.last()
+        self.assertEqual(lisa.order, 3)
+        self.assertEqual(last_author.order, 5)
+        lisa.order = 4
+        last_author.order = 4
+        lisa.save()
+        last_author.save()
+
+        cit_original = self.res.get_citation()
+
+        # run  update command to fix author order
+        call_command(self.update_command)
+
+        for index, creator in enumerate(self.res.metadata.creators.all(), start=1):
+            self.assertEqual(index, creator.order)
+
+        self.assertEqual(self.res.get_citation(), cit_original)
+
+    def test_command_maintains_citations(self):
+        """
+        Testing citation is maintained after author_order management command
+        """
+
+        # Intentionally make a creator list with duplicate orders
+        second_author = self.res.metadata.creators.filter(order=2).first()
+        fourth_author = self.res.metadata.creators.filter(order=4).first()
+        second_author.order = 3
+        fourth_author.order = 5
+        second_author.save()
+        fourth_author.save()
+
+        cit_original = self.res.get_citation()
+
+        # run  update command to fix author order
+        call_command(self.update_command)
+
+        self.assertEqual(self.res.get_citation(), cit_original)
+
+    def test_author_order_command_doesnt_touch_published(self):
+        """
+        Testing author_order management command does not alter published resources
+        """
+
+        # Intentionally make a creator list with duplicate orders
+        second_author = self.res.metadata.creators.filter(order=2).first()
+        fourth_author = self.res.metadata.creators.filter(order=4).first()
+        second_author.order = 3
+        fourth_author.order = 5
+        second_author.save()
+        fourth_author.save()
+
+        self.assertFalse(self.res.metadata.dates.filter(type="published").exists())
+        self.res.raccess.published = True
+        self.res.raccess.save()
+        resource.create_metadata_element(
+            self.res.short_id,
+            "date",
+            type="published",
+            start_date=parser.parse("8/10/2014"),
+        )
+        cit_pub = self.res.get_citation()
+
+        # run  update command to fix author order
+        call_command(self.update_command)
+
+        self.assertEqual(self.res.get_citation(), cit_pub)
+
+        self.assertEqual(self.res.metadata.creators.filter(order=3).count(), 2)
+        self.assertEqual(self.res.metadata.creators.filter(order=5).count(), 2)