Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/publish-pages.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
on:
# Runs on pushes targeting the default branch
push:
branches: [$default-branch]
branches: [main]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand All @@ -26,7 +26,7 @@ jobs:
run: yarn workspace mit-open build-storybook

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
path: ./frontends/mit-open/storybook-static

Expand All @@ -45,4 +45,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
16 changes: 16 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
Release Notes
=============

Version 0.13.19
---------------

- remove erronous export string (#1257)
- Install django-silk nad fix topics api perf (#1250)
- change xpro ETL dict key back (#1252)
- reindexing fixes (#1247)
- Pin dependencies (#1225)
- Plain text news/events titles/authors; standardize html cleanup (#1248)
- Condensed list card components for user lists (#1251)
- Change readable_id values for podcasts and episodes (#1232)
- adjust / refactor channel detail header (#1234)
- use main not "$default-branch" (#1249)
- Update dependency ruff to v0.5.1 (#1241)
- Update dependency Django to v4.2.14 (#1240)

Version 0.13.18 (Released July 10, 2024)
---------------

Expand Down
24 changes: 24 additions & 0 deletions channels/migrations/0011_add_topic_detail_related_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.13 on 2024-07-10 16:43

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("learning_resources", "0055_alter_relationship_options_ordering"),
("channels", "0010_alter_channel_name_regex"),
]

operations = [
migrations.AlterField(
model_name="channeltopicdetail",
name="topic",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="channel_topic_details",
to="learning_resources.learningresourcetopic",
),
),
]
38 changes: 32 additions & 6 deletions channels/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.core.validators import RegexValidator
from django.db import models
from django.db.models import JSONField, deletion
from django.db.models.functions import Concat
from imagekit.models import ImageSpecField, ProcessedImageField
from imagekit.processors import ResizeToFit

Expand All @@ -14,7 +15,7 @@
LearningResourceOfferor,
LearningResourceTopic,
)
from main.models import TimestampedModel
from main.models import TimestampedModel, TimestampedModelQuerySet
from main.utils import frontend_absolute_url
from profiles.utils import avatar_uri, banner_uri
from widgets.models import WidgetList
Expand All @@ -23,9 +24,27 @@
AVATAR_MEDIUM_MAX_DIMENSION = 90


class ChannelQuerySet(TimestampedModelQuerySet):
"""Custom queryset for Channels"""

def annotate_channel_url(self):
"""Annotate the channel for serialization"""
return self.annotate(
_channel_url=Concat(
models.Value(frontend_absolute_url("/c/")),
"channel_type",
models.Value("/"),
"name",
models.Value("/"),
)
)


class Channel(TimestampedModel):
"""Channel for any field/subject"""

objects = ChannelQuerySet.as_manager()

# Channel configuration
name = models.CharField(
max_length=100,
Expand Down Expand Up @@ -91,13 +110,17 @@ def __str__(self):
"""Str representation of channel"""
return self.title

class Meta:
unique_together = ("name", "channel_type")

@property
def channel_url(self) -> str:
"""Return the channel url"""
return frontend_absolute_url(f"/c/{self.channel_type}/{self.name}/")
return getattr(
self,
"_channel_url",
frontend_absolute_url(f"/c/{self.channel_type}/{self.name}/"),
)

class Meta:
unique_together = ("name", "channel_type")


class ChannelTopicDetail(TimestampedModel):
Expand All @@ -110,7 +133,10 @@ class ChannelTopicDetail(TimestampedModel):
related_name="topic_detail",
)
topic = models.ForeignKey(
LearningResourceTopic, null=True, on_delete=models.SET_NULL
LearningResourceTopic,
null=True,
on_delete=models.SET_NULL,
related_name="channel_topic_details",
)


Expand Down
7 changes: 1 addition & 6 deletions channels/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,7 @@ def get_lists(self, instance):
"""Return the channel's list of LearningPaths"""
return [
LearningPathPreviewSerializer(channel_list.channel_list).data
for channel_list in ChannelList.objects.filter(channel=instance)
.prefetch_related(
"channel_list", "channel__lists", "channel__featured_list"
)
.all()
.order_by("position")
for channel_list in instance.lists.all()
]

def get_channel_url(self, instance) -> str:
Expand Down
16 changes: 14 additions & 2 deletions channels/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging

from django.contrib.auth.models import User
from django.db.models import Prefetch
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import mixins, viewsets
Expand All @@ -13,7 +14,7 @@

from channels.api import get_group_role_name, remove_user_role
from channels.constants import CHANNEL_ROLE_MODERATORS
from channels.models import Channel
from channels.models import Channel, ChannelList
from channels.permissions import ChannelModeratorPermissions, HasChannelPermission
from channels.serializers import (
ChannelCreateSerializer,
Expand Down Expand Up @@ -75,8 +76,19 @@ def get_queryset(self):
"""Return a queryset"""
return (
Channel.objects.prefetch_related(
"lists", "sub_channels", "sub_channels__channel"
Prefetch(
"lists",
queryset=ChannelList.objects.prefetch_related(
"channel_list", "channel__lists", "channel__featured_list"
).order_by("position"),
),
"sub_channels",
Prefetch(
"sub_channels__channel",
queryset=Channel.objects.annotate_channel_url(),
),
)
.annotate_channel_url()
.select_related(
"featured_list", "topic_detail", "department_detail", "unit_detail"
)
Expand Down
2 changes: 1 addition & 1 deletion channels/views_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def test_no_excess_queries(user_client, django_assert_num_queries, related_count
sub_channels / lists.
"""
# This isn't too important; we care it does not scale with number of related items
expected_query_count = 11
expected_query_count = 10

topic_channel = ChannelFactory.create(is_topic=True)
ChannelListFactory.create_batch(related_count, channel=topic_channel)
Expand Down
12 changes: 12 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ def prevent_requests(mocker, request): # noqa: PT004
autospec=True,
side_effect=DoNotUseRequestException,
)


@pytest.fixture(autouse=True)
def _disable_silky(settings):
"""Disable django-silky during tests"""
settings.SILKY_INTERCEPT_PERCENT = 0

settings.MIDDLEWARE = tuple(
middleware
for middleware in settings.MIDDLEWARE
if middleware != "silk.middleware.SilkyMiddleware"
)
4 changes: 2 additions & 2 deletions frontends/api/src/generated/v0/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1198,11 +1198,11 @@ export interface LearningResourceTopic {
*/
parent?: number | null
/**
* Get the channel url for the topic if it exists
*
* @type {string}
* @memberof LearningResourceTopic
*/
channel_url: string | null
channel_url: string
}
/**
* Serializer for News FeedItem
Expand Down
4 changes: 2 additions & 2 deletions frontends/api/src/generated/v1/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2285,11 +2285,11 @@ export interface LearningResourceTopic {
*/
parent?: number | null
/**
* Get the channel url for the topic if it exists
*
* @type {string}
* @memberof LearningResourceTopic
*/
channel_url: string | null
channel_url: string
}
/**
* SearchResponseSerializer with OpenAPI annotations for Learning Resources search
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import React, { useCallback, useMemo, useState } from "react"
import { LearningResourceCard, LearningResourceListCard } from "ol-components"
import {
LearningResourceCard,
LearningResourceListCard,
LearningResourceListCardCondensed,
} from "ol-components"
import * as NiceModal from "@ebay/nice-modal-react"
import type {
LearningResourceCardProps,
Expand Down Expand Up @@ -104,7 +108,9 @@ const ResourceCard: React.FC<ResourceCardProps> = ({ resource, ...others }) => {
type ResourceListCardProps = Omit<
LearningResourceListCardProps,
"href" | "onAddToLearningPathClick" | "onAddToUserListClick"
>
> & {
condensed?: boolean
}

/**
* Just like `ol-components/LearningResourceListCard`, but with builtin actions:
Expand All @@ -115,7 +121,8 @@ type ResourceListCardProps = Omit<
*/
const ResourceListCard: React.FC<ResourceListCardProps> = ({
resource,
...others
condensed,
...props
}) => {
const {
getDrawerHref,
Expand All @@ -126,16 +133,20 @@ const ResourceListCard: React.FC<ResourceListCardProps> = ({
inUserList,
inLearningPath,
} = useResourceCard(resource)

const ListCardComponent = condensed
? LearningResourceListCardCondensed
: LearningResourceListCard
return (
<>
<LearningResourceListCard
<ListCardComponent
resource={resource}
href={resource ? getDrawerHref(resource.id) : undefined}
onAddToLearningPathClick={handleAddToLearningPathClick}
onAddToUserListClick={handleAddToUserListClick}
inUserList={inUserList}
inLearningPath={inLearningPath}
{...others}
{...props}
/>
<SignupPopover anchorEl={anchorEl} onClose={handleClosePopover} />
</>
Expand Down
Loading