Skip to content

Commit fec0f28

Browse files
authored
Channels API: improver performance, remove CRUD (#3388)
1 parent 4663f8d commit fec0f28

38 files changed

Lines changed: 692 additions & 3691 deletions

channels/api.py

Lines changed: 0 additions & 71 deletions
This file was deleted.

channels/api_test.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

channels/apps.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,5 @@ class ChannelsConfig(AppConfig):
99
name = "channels"
1010

1111
def ready(self):
12-
"""
13-
Ready handler. Import signals.
14-
"""
15-
import channels.signals # noqa: F401
12+
"""Ready handler."""
1613
from channels import schema # noqa: F401

channels/factories.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import factory
44
from factory.django import DjangoModelFactory
55

6-
from channels.api import create_channel_groups_and_roles
76
from channels.constants import ChannelType
87
from channels.models import (
98
Channel,
@@ -70,19 +69,6 @@ class ChannelFactory(DjangoModelFactory):
7069
),
7170
)
7271

73-
@factory.post_generation
74-
def create_roles(
75-
self,
76-
create,
77-
extracted, # noqa: ARG002
78-
**kwargs, # noqa: ARG002
79-
): # pylint: disable=unused-argument
80-
"""Create the channel groups and roles after the channel is created"""
81-
if not create:
82-
return
83-
84-
create_channel_groups_and_roles(self)
85-
8672
class Meta:
8773
model = Channel
8874
skip_postgeneration_save = True

channels/models.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.contrib.auth.models import Group
66
from django.core.validators import RegexValidator
77
from django.db import models
8-
from django.db.models import Case, JSONField, When, deletion
8+
from django.db.models import Case, JSONField, Prefetch, When, deletion
99
from django.db.models.functions import Concat
1010
from imagekit.models import ImageSpecField, ProcessedImageField
1111
from imagekit.processors import ResizeToFit
@@ -47,6 +47,40 @@ def annotate_channel_url(self):
4747
)
4848
)
4949

50+
def with_detail_relations(self) -> "ChannelQuerySet":
51+
"""
52+
Apply the prefetch/select_related/annotate chain needed to serialize
53+
a full ChannelSerializer without N+1 queries.
54+
"""
55+
return (
56+
self.prefetch_related(
57+
Prefetch(
58+
"lists",
59+
queryset=ChannelList.objects.select_related(
60+
"channel_list"
61+
).order_by("position"),
62+
),
63+
Prefetch(
64+
"sub_channels",
65+
queryset=SubChannel.objects.select_related(
66+
"parent_channel"
67+
).order_by("position"),
68+
),
69+
Prefetch(
70+
"sub_channels__channel",
71+
queryset=Channel.objects.annotate_channel_url(),
72+
),
73+
)
74+
.annotate_channel_url()
75+
.select_related(
76+
"featured_list",
77+
"topic_detail",
78+
"department_detail",
79+
"unit_detail",
80+
"pathway_detail",
81+
)
82+
)
83+
5084

5185
class Channel(TimestampedModel):
5286
"""Channel for any field/subject"""
@@ -132,6 +166,16 @@ def channel_url(self) -> str | None:
132166
return frontend_absolute_url(f"/c/{self.channel_type}/{self.name}/")
133167
return None
134168

169+
@property
170+
def ordered_lists(self) -> list[LearningResource]:
171+
"""The channel's LearningPaths, ordered by position.
172+
173+
Resolves through the prefetched ``lists`` relation when available
174+
(see ``ChannelQuerySet.with_detail_relations``) so serializing a
175+
channel does not trigger an extra query per list.
176+
"""
177+
return [channel_list.channel_list for channel_list in self.lists.all()]
178+
135179
class Meta:
136180
unique_together = ("name", "channel_type")
137181

channels/models_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
from channels.constants import ChannelType
66
from channels.factories import (
77
ChannelDepartmentDetailFactory,
8+
ChannelFactory,
89
ChannelTopicDetailFactory,
910
ChannelUnitDetailFactory,
11+
SubChannelFactory,
1012
)
13+
from channels.models import Channel
14+
from channels.serializers import ChannelSerializer
1115

1216
pytestmark = [pytest.mark.django_db]
1317

@@ -37,3 +41,19 @@ def test_channel_url_for_departments(published, channel_type, detail_factory):
3741
)
3842
else:
3943
assert channel.channel_url is None
44+
45+
46+
@pytest.mark.parametrize("sub_channel_count", [1, 5, 10])
47+
def test_with_detail_relations_serialization_has_no_extra_subchannel_queries(
48+
django_assert_num_queries, sub_channel_count
49+
):
50+
"""with_detail_relations should preload subchannel relations for serialization."""
51+
channel = ChannelFactory.create(is_topic=True)
52+
SubChannelFactory.create_batch(sub_channel_count, parent_channel=channel)
53+
54+
prefetched_channel = Channel.objects.with_detail_relations().get(pk=channel.pk)
55+
56+
with django_assert_num_queries(0):
57+
serialized = ChannelSerializer(prefetched_channel).data
58+
59+
assert len(serialized["sub_channels"]) == sub_channel_count

channels/permissions.py

Lines changed: 0 additions & 66 deletions
This file was deleted.

0 commit comments

Comments
 (0)