Repository: DDMAL/CantusDB

Issue:
sort columns of search results
In OldCantus the column headers of chant searches are clickable to order the search results within that column, like a spreadsheet:
![image](https://user-images.githubusercontent.com/67451875/200433412-ac07b01f-6d51-40e5-a5d3-70ee14f257b7.png)

NewCantus doesn't do this. It's a feature I use a lot, so it would be nice if it were there! (Actually I would love if the headers in the concordances tables worked the same way, but that may not be possible...)


Discussion:
some working out on my own:
in OldCantus, this column sorting is present in the following views:
- [Chant Search view](https://cantus.uwaterloo.ca/search?t=ponam%20in%20sion&op=starts&genre=All&field_position_value=&cid=&mode=&feast=&volpiano=All&field_differentia_new_value_op=contains&field_differentia_new_value=&order=field_full_text_ms&sort=desc)

It's not present in the following views:
- [Chant List view](https://cantus.uwaterloo.ca/chants?source=123610) (all chants in a given source) 

Resolving patch:
diff --git a/django/cantusdb_project/main_app/views/chant.py b/django/cantusdb_project/main_app/views/chant.py
--- a/django/cantusdb_project/main_app/views/chant.py
+++ b/django/cantusdb_project/main_app/views/chant.py
@@ -664,6 +664,52 @@ def get_context_data(self, **kwargs) -> dict:
         context = super().get_context_data(**kwargs)
         # Add to context a QuerySet of dicts with id and name of each Genre
         context["genres"] = Genre.objects.all().order_by("name").values("id", "name")
+        context["order"] = self.request.GET.get("order")
+        context["sort"] = self.request.GET.get("sort")
+
+        # build a url containing all the search parameters, excluding ordering parameters.
+        # this way, when someone clicks on a column heading, we can append the ordering parameters
+        # while retaining the search parameters
+        current_url = self.request.path
+        search_parameters = []
+
+        search_op = self.request.GET.get('op')
+        if search_op:
+            search_parameters.append(f"op={search_op}")
+        search_keyword = self.request.GET.get('keyword')
+        if search_keyword:
+            search_parameters.append(f"keyword={search_keyword}")
+        search_office = self.request.GET.get('office')
+        if search_office:
+            search_parameters.append(f'office={search_office}')
+        search_genre = self.request.GET.get('genre')
+        if search_genre:
+            search_parameters.append(f'genre={search_genre}')
+        search_cantus_id = self.request.GET.get('cantus_id')
+        if search_cantus_id:
+            search_parameters.append(f'cantus_id={search_cantus_id}')
+        search_mode = self.request.GET.get('mode')
+        if search_mode:
+            search_parameters.append(f'mode={search_mode}')
+        search_feast = self.request.GET.get('feast')
+        if search_feast:
+            search_parameters.append(f'feast={search_feast}')
+        search_position = self.request.GET.get('position')
+        if search_position:
+            search_parameters.append(f'position={search_position}')
+        search_melodies = self.request.GET.get('melodies')
+        if search_melodies:
+            search_parameters.append(f'melodies={search_melodies}')
+
+        if search_parameters:
+            joined_search_parameters = "&".join(search_parameters)
+            url_with_search_params = current_url + "?" + joined_search_parameters
+        else:
+            url_with_search_params = current_url + "?"
+            
+        context["url_with_search_params"] = url_with_search_params
+        print(self.request)
+
         return context
 
     def get_queryset(self) -> QuerySet:
@@ -705,6 +751,7 @@ def get_queryset(self) -> QuerySet:
             if self.request.GET.get("genre"):
                 genre_id = int(self.request.GET.get("genre"))
                 q_obj_filter &= Q(genre__id=genre_id)
+
             if self.request.GET.get("cantus_id"):
                 cantus_id = self.request.GET.get("cantus_id")
                 q_obj_filter &= Q(cantus_id__icontains=cantus_id)
@@ -726,6 +773,34 @@ def get_queryset(self) -> QuerySet:
                 # as a substring
                 feasts = Feast.objects.filter(name__icontains=feast)
                 q_obj_filter &= Q(feast__in=feasts)
+            if self.request.GET.get('order'):
+                if self.request.GET.get('order') == 'siglum':
+                    order = 'siglum'
+                elif self.request.GET.get('order') == 'incipit':
+                    order = 'incipit'
+                elif self.request.GET.get('order') == 'office':
+                    order = 'office'
+                elif self.request.GET.get('order') == 'genre':
+                    order = 'genre'
+                elif self.request.GET.get('order') == 'cantus_id':
+                    order = 'cantus_id'
+                elif self.request.GET.get('order') == 'mode':
+                    order = 'mode'
+                elif self.request.GET.get('order') == 'has_fulltext':
+                    order = 'manuscript_full_text'
+                elif self.request.GET.get('order') == 'has_melody':
+                    order = 'volpiano'
+                elif self.request.GET.get('order') == 'has_image':
+                    order = 'image_link'
+                else:
+                    order = 'siglum'
+            else:
+                order = 'siglum'
+            if self.request.GET.get('sort'):
+                if self.request.GET.get('sort') == "asc":
+                    order = order
+                elif self.request.GET.get('sort') == 'desc':
+                    order = "-" + order
             if not display_unpublished:
                 chant_set = Chant.objects.filter(source__published=True)
                 sequence_set = Sequence.objects.filter(source__published=True)
@@ -748,10 +823,7 @@ def get_queryset(self) -> QuerySet:
 
             # once unioned, the queryset cannot be filtered/annotated anymore, so we put union to the last
             queryset = chant_set.union(sequence_set)
-            # ordering with the folio string gives wrong order
-            # old cantus is also not strictly ordered by folio (there are outliers)
-            # so we order by id for now, which is the order that the chants are entered into the DB
-            queryset = queryset.order_by("siglum", "id")
+            queryset = queryset.order_by(order, "id")
 
         return queryset
 


Test patch: