Skip to content

Commit 3918b3a

Browse files
Add a public filter to the kolibri-public's ChannelMetadata viewset (#5486)
* Add a public filter to the kolibri-public's ChannelMetadata viewset * [pre-commit.ci lite] apply automatic fixes * fix test_channelmetadata_viewset.py * [pre-commit.ci lite] apply automatic fixes * fix code according to comments * [pre-commit.ci lite] apply automatic fixes * fix linting * fix linting * [pre-commit.ci lite] apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent f1619f8 commit 3918b3a

3 files changed

Lines changed: 143 additions & 7 deletions

File tree

contentcuration/kolibri_public/tests/test_channelmetadata_viewset.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from uuid import UUID
22

3-
from django.urls import reverse
43
from kolibri_public.models import ChannelMetadata
54
from kolibri_public.tests.utils.mixer import KolibriPublicMixer
65
from le_utils.constants.labels.subjects import SUBJECTSLIST
@@ -19,12 +18,20 @@ def test_annotate_countries(self):
1918
country2 = mixer.blend(Country, code="C2")
2019
country3 = mixer.blend(Country, code="C3")
2120

22-
channel = mixer.blend(ChannelMetadata, countries=[country1, country2, country3])
21+
channel = mixer.blend(
22+
ChannelMetadata, countries=[country1, country2, country3], public=False
23+
)
2324

2425
user = testdata.user("any@user.com")
2526
self.client.force_authenticate(user)
2627

27-
response = self.client.get(reverse("publicchannel-detail", args=[channel.id]))
28+
response = self.client.get(
29+
reverse_with_query(
30+
"publicchannel-detail",
31+
args=[channel.id],
32+
query={"public": "false"},
33+
),
34+
)
2835

2936
self.assertEqual(response.status_code, 200, response.content)
3037
self.assertCountEqual(response.data["countries"], ["C1", "C2", "C3"])
@@ -64,6 +71,7 @@ def setUp(self):
6471
| self.category_bitmasks[3]
6572
),
6673
countries=["C1", "C3"],
74+
public=False,
6775
)
6876
self.metadata2 = mixer.blend(
6977
ChannelMetadata,
@@ -73,6 +81,7 @@ def setUp(self):
7381
| self.category_bitmasks[3]
7482
),
7583
countries=["C1", "C2", "C3"],
84+
public=False,
7685
)
7786
self.metadata3 = mixer.blend(
7887
ChannelMetadata,
@@ -82,12 +91,16 @@ def setUp(self):
8291
| self.category_bitmasks[2]
8392
),
8493
countries=["C3"],
94+
public=False,
8595
)
8696

8797
def test_filter_by_categories_bitmask__provided(self):
8898
self.client.force_authenticate(self.user)
8999

90-
filter_query = {"categories": f"{self.categories[0]},{self.categories[1]}"}
100+
filter_query = {
101+
"categories": f"{self.categories[0]},{self.categories[1]}",
102+
"public": "false",
103+
}
91104

92105
response = self.client.get(
93106
reverse_with_query(
@@ -105,7 +118,10 @@ def test_filter_by_categories_bitmask__not_provided(self):
105118
self.client.force_authenticate(self.user)
106119

107120
response = self.client.get(
108-
reverse("publicchannel-list"),
121+
reverse_with_query(
122+
"publicchannel-list",
123+
query={"public": "false"},
124+
),
109125
)
110126
self.assertEqual(response.status_code, 200, response.content)
111127
self.assertCountEqual(
@@ -116,7 +132,7 @@ def test_filter_by_categories_bitmask__not_provided(self):
116132
def test_filter_by_countries(self):
117133
self.client.force_authenticate(self.user)
118134

119-
filter_query = {"countries": "C1,C2"}
135+
filter_query = {"countries": "C1,C2", "public": "false"}
120136

121137
response = self.client.get(
122138
reverse_with_query(

contentcuration/kolibri_public/tests/test_content_app.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,114 @@ def test_channelmetadata_has_exercises_filter(self):
500500
with_filter_response.data[0]["name"], self.channel_data["name"]
501501
)
502502

503+
def test_channelmetadata_public_filter_default_true(self):
504+
community_channel = models.ContentNode.objects.create(
505+
pk=uuid.uuid4().hex,
506+
channel_id=uuid.uuid4().hex,
507+
content_id=uuid.uuid4().hex,
508+
kind="topic",
509+
title="community channel",
510+
)
511+
models.ChannelMetadata.objects.create(
512+
id=uuid.uuid4().hex,
513+
name="community channel metadata",
514+
root=community_channel,
515+
# community channel
516+
public=False,
517+
)
518+
519+
# By default, only public channels should be returned
520+
response = self.client.get(reverse("publicchannel-list"))
521+
self.assertEqual(len(response.data), 1)
522+
self.assertEqual(response.data[0]["name"], self.channel_data["name"])
523+
self.assertTrue(response.data[0]["public"])
524+
525+
def test_channelmetadata_public_filter_explicit_true(self):
526+
community_channel = models.ContentNode.objects.create(
527+
pk=uuid.uuid4().hex,
528+
channel_id=uuid.uuid4().hex,
529+
content_id=uuid.uuid4().hex,
530+
kind="topic",
531+
title="community channel 2",
532+
)
533+
models.ChannelMetadata.objects.create(
534+
id=uuid.uuid4().hex,
535+
name="community channel metadata 2",
536+
root=community_channel,
537+
public=False,
538+
)
539+
540+
# Explicitly request only public channels
541+
response = self.client.get(reverse("publicchannel-list"), {"public": True})
542+
self.assertEqual(len(response.data), 1)
543+
self.assertEqual(response.data[0]["name"], self.channel_data["name"])
544+
self.assertTrue(response.data[0]["public"])
545+
546+
def test_channelmetadata_public_filter_explicit_false(self):
547+
community_channel = models.ContentNode.objects.create(
548+
pk=uuid.uuid4().hex,
549+
channel_id=uuid.uuid4().hex,
550+
content_id=uuid.uuid4().hex,
551+
kind="topic",
552+
title="community channel 3",
553+
)
554+
community_metadata = models.ChannelMetadata.objects.create(
555+
id=uuid.uuid4().hex,
556+
name="community channel metadata 3",
557+
root=community_channel,
558+
public=False,
559+
)
560+
561+
# Explicitly request only community channels
562+
response = self.client.get(reverse("publicchannel-list"), {"public": False})
563+
self.assertEqual(len(response.data), 1)
564+
self.assertEqual(response.data[0]["name"], community_metadata.name)
565+
self.assertFalse(response.data[0]["public"])
566+
567+
def test_channelmetadata_public_filter_mixed_channels(self):
568+
community_channel1 = models.ContentNode.objects.create(
569+
pk=uuid.uuid4().hex,
570+
channel_id=uuid.uuid4().hex,
571+
content_id=uuid.uuid4().hex,
572+
kind="topic",
573+
title="community channel 4",
574+
)
575+
models.ChannelMetadata.objects.create(
576+
id=uuid.uuid4().hex,
577+
name="community channel metadata 4",
578+
root=community_channel1,
579+
public=False,
580+
)
581+
582+
community_channel2 = models.ContentNode.objects.create(
583+
pk=uuid.uuid4().hex,
584+
channel_id=uuid.uuid4().hex,
585+
content_id=uuid.uuid4().hex,
586+
kind="topic",
587+
title="community channel 5",
588+
)
589+
models.ChannelMetadata.objects.create(
590+
id=uuid.uuid4().hex,
591+
name="community channel metadata 5",
592+
root=community_channel2,
593+
public=False,
594+
)
595+
596+
# Test public filter
597+
public_response = self.client.get(
598+
reverse("publicchannel-list"), {"public": True}
599+
)
600+
self.assertEqual(len(public_response.data), 1)
601+
self.assertTrue(public_response.data[0]["public"])
602+
603+
# Test community filter
604+
community_response = self.client.get(
605+
reverse("publicchannel-list"), {"public": False}
606+
)
607+
self.assertEqual(len(community_response.data), 2)
608+
for channel in community_response.data:
609+
self.assertFalse(channel["public"])
610+
503611
def test_filtering_coach_content_anon(self):
504612
response = self.client.get(
505613
reverse("publiccontentnode-list"),

contentcuration/kolibri_public/views.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,26 @@ class CharInFilter(BaseInFilter, CharFilter):
100100

101101

102102
class ChannelMetadataFilter(FilterSet):
103+
def __init__(self, data=None, *args, **kwargs):
104+
# if filterset is bound, use initial values as defaults
105+
if data is not None:
106+
data = data.copy()
107+
for name, f in self.base_filters.items():
108+
initial = f.extra.get("initial")
109+
# filter param is either missing or empty, use initial as default
110+
if not data.get(name) and initial:
111+
data[name] = initial
112+
super().__init__(data, *args, **kwargs)
113+
103114
available = BooleanFilter(method="filter_available", label="Available")
104115
has_exercise = BooleanFilter(method="filter_has_exercise", label="Has exercises")
105116
categories = CharFilter(method=bitmask_contains_and, label="Categories")
106117
countries = CharInFilter(field_name="countries", label="Countries")
118+
public = BooleanFilter(field_name="public", label="Public", initial=True)
107119

108120
class Meta:
109121
model = models.ChannelMetadata
110-
fields = ("available", "has_exercise", "categories", "countries")
122+
fields = ("available", "has_exercise", "categories", "countries", "public")
111123

112124
def filter_has_exercise(self, queryset, name, value):
113125
queryset = queryset.annotate(

0 commit comments

Comments
 (0)