From b1e8d4cabdc1c604f37aa4e6bc8e2004d2126a8e Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Tue, 15 Oct 2019 11:38:21 +0200
Subject: [PATCH] Fix #936: Support byYear filtering in Subsonic API (#936)

---
 api/funkwhale_api/subsonic/views.py | 30 +++++++++++++++++++++++++++++
 api/tests/subsonic/test_views.py    | 29 ++++++++++++++++++++++++++++
 changes/changelog.d/936.enhancement |  1 +
 3 files changed, 60 insertions(+)
 create mode 100644 changes/changelog.d/936.enhancement

diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py
index 6633224df..60209a92d 100644
--- a/api/funkwhale_api/subsonic/views.py
+++ b/api/funkwhale_api/subsonic/views.py
@@ -1,3 +1,6 @@
+"""
+Documentation of Subsonic API can be found at http://www.subsonic.org/pages/api.jsp
+"""
 import datetime
 import functools
 
@@ -427,7 +430,34 @@ class SubsonicViewSet(viewsets.GenericViewSet):
                 Q(tagged_items__tag__name=genre)
                 | Q(artist__tagged_items__tag__name=genre)
             )
+        elif type == "byYear":
+            try:
+                boundaries = [
+                    int(data.get("fromYear", 0)),
+                    int(data.get("toYear", 99999999)),
+                ]
 
+            except (TypeError, ValueError):
+                return response.Response(
+                    {
+                        "error": {
+                            "code": 10,
+                            "message": "Invalid fromYear or toYear parameter",
+                        }
+                    }
+                )
+            # because, yeah, the specification explicitly state that fromYear can be greater
+            # than toYear, to indicate reverse ordering…
+            # http://www.subsonic.org/pages/api.jsp#getAlbumList2
+            from_year = min(boundaries)
+            to_year = max(boundaries)
+            queryset = queryset.filter(
+                release_date__year__gte=from_year, release_date__year__lte=to_year
+            )
+            if boundaries[0] <= boundaries[1]:
+                queryset = queryset.order_by("release_date")
+            else:
+                queryset = queryset.order_by("-release_date")
         try:
             offset = int(data["offset"])
         except (TypeError, KeyError, ValueError):
diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py
index 298ad34f7..d58cc3932 100644
--- a/api/tests/subsonic/test_views.py
+++ b/api/tests/subsonic/test_views.py
@@ -469,6 +469,35 @@ def test_get_album_list2_by_genre(f, db, logged_in_api_client, factories):
     }
 
 
+@pytest.mark.parametrize(
+    "params, expected",
+    [
+        ({"type": "byYear", "fromYear": 1902, "toYear": 1903}, [2, 3]),
+        # Because why not, it's supported in Subsonic API…
+        # http://www.subsonic.org/pages/api.jsp#getAlbumList2
+        ({"type": "byYear", "fromYear": 1903, "toYear": 1902}, [3, 2]),
+    ],
+)
+def test_get_album_list2_by_year(params, expected, db, logged_in_api_client, factories):
+    albums = [
+        factories["music.Album"](
+            playable=True, release_date=datetime.date(1900 + i, 1, 1)
+        )
+        for i in range(5)
+    ]
+    url = reverse("api:subsonic-get_album_list2")
+    base_params = {"f": "json"}
+    base_params.update(params)
+    response = logged_in_api_client.get(url, base_params)
+
+    assert response.status_code == 200
+    assert response.data == {
+        "albumList2": {
+            "album": serializers.get_album_list2_data([albums[i] for i in expected])
+        }
+    }
+
+
 @pytest.mark.parametrize("f", ["json"])
 @pytest.mark.parametrize(
     "tags_field",
diff --git a/changes/changelog.d/936.enhancement b/changes/changelog.d/936.enhancement
new file mode 100644
index 000000000..2c16ffe54
--- /dev/null
+++ b/changes/changelog.d/936.enhancement
@@ -0,0 +1 @@
+Support byYear filtering in Subsonic API (#936)
-- 
GitLab