Skip to content

Commit ac3985d

Browse files
Re-add alphabetical sorting as default (fixes #1415) (#1416)
* fix(folderadmin): Implement .sort() on file_qs when no order_by query param is set * fix(folderadmin): conditionally convert file_qs to list and sort if no query params are set * fix(folderadmin): use file_list when no query params are set, else use file_qs * Revert "fix(folderadmin): use file_list when no query params are set, else use file_qs" This reverts commit f34c560. * chore: revert style changes made by black * test: add tests for folderadmin sorting * refactor: use .annotate() and Coalesce to create a temporary sort field instead of using Python sort * chore: clean up flake8 errors * chore: clean up flake8 errors --------- Co-authored-by: Filip Weidemann <[email protected]>
1 parent 3940e1c commit ac3985d

File tree

2 files changed

+103
-3
lines changed

2 files changed

+103
-3
lines changed

filer/admin/folderadmin.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from django.core.exceptions import PermissionDenied, ValidationError
1414
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
1515
from django.db import models, router
16-
from django.db.models import F, OuterRef, Subquery
16+
from django.db.models import F, OuterRef, Subquery, Case, When
17+
from django.db.models.functions import Coalesce, Lower
1718
from django.http import HttpResponse, HttpResponseRedirect
1819
from django.shortcuts import get_object_or_404, render
1920
from django.urls import path, reverse
@@ -317,13 +318,24 @@ def directory_listing(self, request, folder_id=None, viewtype=None):
317318

318319
folder_qs = folder_qs.order_by('name').select_related("owner")
319320
order_by = request.GET.get('order_by', None)
321+
order_by_annotation = None
320322
if order_by is None:
321-
order_by = "file"
322-
order_by = order_by.split(',')
323+
file_qs = file_qs.annotate(coalesce_sort_field=Coalesce(
324+
Case(
325+
When(name__exact='', then=None),
326+
When(name__isnull=False, then='name')
327+
),
328+
'original_filename'
329+
))
330+
order_by_annotation = Lower('coalesce_sort_field')
331+
332+
order_by = order_by.split(',') if order_by else []
323333
order_by = [field for field in order_by
324334
if re.sub(r'^-', '', field) in self.order_by_file_fields]
325335
if len(order_by) > 0:
326336
file_qs = file_qs.order_by(*order_by)
337+
elif order_by_annotation:
338+
file_qs = file_qs.order_by(order_by_annotation)
327339

328340
if folder.is_root and not search_mode:
329341
virtual_items = folder.virtual_folders

tests/test_admin.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,94 @@ def create_file(self, folder, filename=None):
748748
return file_obj
749749

750750

751+
class FolderAndFileSortingMixin(BulkOperationsMixin):
752+
def setUp(self):
753+
self.superuser = create_superuser()
754+
self.client.login(username='admin', password='secret')
755+
self.img = create_image()
756+
self.folder_1 = Folder(name='Pictures', parent=None)
757+
self.folder_1.save()
758+
self.nested_folder_2 = Folder(name='Nested 2', parent=self.folder_1)
759+
self.nested_folder_1 = Folder(name='Nested 1', parent=self.folder_1)
760+
self.nested_folder_1.save()
761+
self.nested_folder_2.save()
762+
self.create_file(folder=self.folder_1, filename='background.jpg')
763+
self.create_file(folder=self.folder_1, filename='A_Testfile.jpg')
764+
self.create_file(folder=self.folder_1, filename='Another_Test.jpg')
765+
newspaper_file = self.create_file(folder=self.folder_1, filename='Newspaper.pdf')
766+
newspaper_file.name = 'Zeitung'
767+
newspaper_file.save()
768+
renamed_file = self.create_file(folder=self.folder_1, filename='last_when_sorting_by_filename.jpg')
769+
renamed_file.name = 'A cute dog'
770+
renamed_file.save()
771+
772+
def tearDown(self):
773+
self.client.logout()
774+
for f in File.objects.all():
775+
f.delete()
776+
for folder in Folder.objects.all():
777+
folder.delete()
778+
779+
780+
class FilerFolderAndFileSortingTests(FolderAndFileSortingMixin, TestCase):
781+
# Assert that the folders are correctly sorted
782+
def test_filer_folder_sorting(self):
783+
response = self.client.get(reverse('admin:filer-directory_listing', kwargs={
784+
'folder_id': self.folder_1.pk
785+
}))
786+
self.assertEqual(response.status_code, 200)
787+
self.assertEqual(response.context['folder_children'].count(), 2)
788+
self.assertEqual(response.context['folder_children'][0].name, 'Nested 1')
789+
self.assertEqual(response.context['folder_children'][1].name, 'Nested 2')
790+
791+
# Default sorting should be alphabetically
792+
def test_filer_directory_listing_default_sorting(self):
793+
response = self.client.get(reverse('admin:filer-directory_listing', kwargs={
794+
'folder_id': self.folder_1.pk
795+
}))
796+
self.assertEqual(response.status_code, 200)
797+
# when using the default sort, the folder_files are of type `list`,
798+
# so we assert the length.
799+
self.assertEqual(len(response.context['folder_files']), 5)
800+
801+
expected_filenames = ['A cute dog', 'A_Testfile.jpg', 'Another_Test.jpg', 'background.jpg', 'Zeitung']
802+
for index, expected_filename in enumerate(expected_filenames):
803+
self.assertEqual(str(response.context['folder_files'][index]), expected_filename)
804+
805+
# Now, all columns with empty name should be alphabetically sorted by their filename,
806+
# after that, at the end of the list, all files with and explicit name should appear;
807+
# however, since we ONLY sort by name, the order of items without name is not defined
808+
# by their filename but rather by their creation date.
809+
# So, the order is expected to be ordered as they are created in the setUp method.
810+
def test_filer_directory_listing_sorting_with_order_by_param(self):
811+
response = self.client.get(reverse('admin:filer-directory_listing', kwargs={
812+
'folder_id': self.folder_1.pk
813+
}), {'order_by': 'name'})
814+
self.assertEqual(response.status_code, 200)
815+
# when using the default sort, the folder_files are of type `list`,
816+
# so we assert the length.
817+
self.assertEqual(len(response.context['folder_files']), 5)
818+
819+
expected_filenames = ['background.jpg', 'A_Testfile.jpg', 'Another_Test.jpg', 'A cute dog', 'Zeitung']
820+
for index, expected_filename in enumerate(expected_filenames):
821+
self.assertEqual(str(response.context['folder_files'][index]), expected_filename)
822+
823+
# Finally, we can define a fallback column to pass into `order_by` so that files without
824+
# any name are still sorted by something (in this case, their original_filename).
825+
# This should yield the expected order as well, but NOT the exact same order as the default sorting,
826+
# since we sort by name FIRST and all items with the same value again by original_filename.
827+
def test_filer_directory_listing_sorting_with_multiple_order_by_params(self):
828+
response = self.client.get(reverse('admin:filer-directory_listing', kwargs={
829+
'folder_id': self.folder_1.pk
830+
}), {'order_by': 'name,original_filename'})
831+
self.assertEqual(response.status_code, 200)
832+
self.assertEqual(len(response.context['folder_files']), 5)
833+
834+
expected_filenames = ['A_Testfile.jpg', 'Another_Test.jpg', 'background.jpg', 'A cute dog', 'Zeitung']
835+
for index, expected_filename in enumerate(expected_filenames):
836+
self.assertEqual(str(response.context['folder_files'][index]), expected_filename)
837+
838+
751839
class FilerBulkOperationsTests(BulkOperationsMixin, TestCase):
752840
def test_move_files_and_folders_action(self):
753841
# TODO: Test recursive (files and folders tree) move

0 commit comments

Comments
 (0)