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
19 changes: 19 additions & 0 deletions articles/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.conf import settings
from django.utils.decorators import method_decorator
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination
Expand All @@ -6,6 +8,7 @@
from articles.models import Article
from articles.serializers import ArticleSerializer
from main.constants import VALID_HTTP_METHODS
from main.utils import cache_page_for_all_users, clear_search_cache

# Create your views here.

Expand Down Expand Up @@ -37,3 +40,19 @@ class ArticleViewSet(viewsets.ModelViewSet):

permission_classes = [IsAdminUser]
http_method_names = VALID_HTTP_METHODS

@method_decorator(
cache_page_for_all_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)

def create(self, request, *args, **kwargs):
clear_search_cache()
return super().create(request, *args, **kwargs)

def destroy(self, request, *args, **kwargs):
clear_search_cache()
return super().destroy(request, *args, **kwargs)
15 changes: 13 additions & 2 deletions channels/views_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,14 @@ def test_channel_counts_view(client):
)


def test_channel_counts_view_is_cached_for_anonymous_users(client):
def test_channel_counts_view_is_cached_for_anonymous_users(client, settings):
"""Test the channel counts view is cached for anonymous users"""
settings.CACHES["redis"] = {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": settings.CELERY_BROKER_URL,
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
}

channel_count = 5
channels = ChannelFactory.create_batch(channel_count, channel_type="unit")
url = reverse(
Expand All @@ -499,8 +505,13 @@ def test_channel_counts_view_is_cached_for_anonymous_users(client):
assert len(response) == channel_count


def test_channel_counts_view_is_cached_for_authenticated_users(client):
def test_channel_counts_view_is_cached_for_authenticated_users(client, settings):
"""Test the channel counts view is cached for authenticated users"""
settings.CACHES["redis"] = {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": settings.CELERY_BROKER_URL,
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
}
channel_count = 5
channel_user = UserFactory.create()
client.force_login(channel_user)
Expand Down
9 changes: 9 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ def prevent_requests(mocker, request): # noqa: PT004
autospec=True,
side_effect=DoNotUseRequestException,
)


@pytest.fixture(autouse=True)
def _use_dummy_redis_cache_backend(settings):
new_cache_settings = settings.CACHES.copy()
new_cache_settings["redis"] = {
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
}
settings.CACHES = new_cache_settings
12 changes: 4 additions & 8 deletions frontends/api/src/generated/v1/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16195,8 +16195,7 @@ export const LearningpathsApiAxiosParamCreator = function (
}
},
/**
* Get a list of related learning resources for a learning resource.
* @summary Nested Learning Resource List
* Viewset for LearningPath related resources
* @param {number} learning_resource_id The learning resource id of the learning path
* @param {number} [limit] Number of results to return per page.
* @param {number} [offset] The initial index from which to return the results.
Expand Down Expand Up @@ -16779,8 +16778,7 @@ export const LearningpathsApiFp = function (configuration?: Configuration) {
)(axios, operationBasePath || basePath)
},
/**
* Get a list of related learning resources for a learning resource.
* @summary Nested Learning Resource List
* Viewset for LearningPath related resources
* @param {number} learning_resource_id The learning resource id of the learning path
* @param {number} [limit] Number of results to return per page.
* @param {number} [offset] The initial index from which to return the results.
Expand Down Expand Up @@ -17133,8 +17131,7 @@ export const LearningpathsApiFactory = function (
.then((request) => request(axios, basePath))
},
/**
* Get a list of related learning resources for a learning resource.
* @summary Nested Learning Resource List
* Viewset for LearningPath related resources
* @param {LearningpathsApiLearningpathsItemsListRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
Expand Down Expand Up @@ -17673,8 +17670,7 @@ export class LearningpathsApi extends BaseAPI {
}

/**
* Get a list of related learning resources for a learning resource.
* @summary Nested Learning Resource List
* Viewset for LearningPath related resources
* @param {LearningpathsApiLearningpathsItemsListRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
LearningResource,
LearningResourceOfferor,
)
from main.utils import now_in_utc
from main.utils import clear_cache, now_in_utc


class Command(BaseCommand):
Expand Down Expand Up @@ -90,3 +90,4 @@ def handle(self, *args, **options): # noqa: ARG002
"Population of unit channel featured lists finished, "
f"took {total_seconds} seconds"
)
clear_cache()
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
upsert_department_data,
upsert_school_data,
)
from main.utils import now_in_utc
from main.utils import clear_cache, now_in_utc


class Command(BaseCommand):
Expand All @@ -26,3 +26,4 @@ def handle(self, *args, **options): # noqa: ARG002
f"Update of {len(schools)} schools & {len(departments)} "
f"departments finished, took {total_seconds} seconds"
)
clear_cache()
3 changes: 2 additions & 1 deletion learning_resources/management/commands/update_offered_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.core.management import BaseCommand

from learning_resources.utils import upsert_offered_by_data
from main.utils import now_in_utc
from main.utils import clear_cache, now_in_utc


class Command(BaseCommand):
Expand All @@ -21,3 +21,4 @@ def handle(self, *args, **options): # noqa: ARG002
self.stdout.write(
f"Update of {len(offerors)} offerors finished, took {total_seconds} seconds"
)
clear_cache()
3 changes: 2 additions & 1 deletion learning_resources/management/commands/update_platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.core.management import BaseCommand

from learning_resources.utils import upsert_platform_data
from main.utils import now_in_utc
from main.utils import clear_cache, now_in_utc


class Command(BaseCommand):
Expand All @@ -21,3 +21,4 @@ def handle(self, *args, **options): # noqa: ARG002
self.stdout.write(
f"Upserted {len(platform_codes)} platforms, took {total_seconds} seconds"
)
clear_cache()
79 changes: 78 additions & 1 deletion learning_resources/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
AnonymousAccessReadonlyPermission,
is_admin_user,
)
from main.utils import chunks
from main.utils import cache_page_for_all_users, cache_page_for_anonymous_users, chunks

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -159,6 +159,14 @@ def get_queryset(self) -> QuerySet:
"""
return self._get_base_queryset().filter(published=True)

@method_decorator(
cache_page_for_anonymous_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)


@extend_schema_view(
list=extend_schema(
Expand Down Expand Up @@ -508,6 +516,14 @@ def learning_paths(self, request, *args, **kwargs): # noqa: ARG002
serializer = SerializerClass(current_relationships, many=True)
return Response(serializer.data)

@method_decorator(
cache_page_for_anonymous_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)


@extend_schema_view(
create=extend_schema(summary="Learning Path Resource Relationship Add"),
Expand All @@ -533,6 +549,14 @@ class LearningPathItemsViewSet(ResourceListItemsViewSet, viewsets.ModelViewSet):
permission_classes = (permissions.HasLearningPathItemPermissions,)
http_method_names = VALID_HTTP_METHODS

@method_decorator(
cache_page_for_anonymous_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)

def create(self, request, *args, **kwargs):
request.data["parent"] = request.data.get("parent_id")
return super().create(request, *args, **kwargs)
Expand Down Expand Up @@ -568,6 +592,14 @@ class TopicViewSet(viewsets.ReadOnlyModelViewSet):
filter_backends = [DjangoFilterBackend]
filterset_class = TopicFilter

@method_decorator(
cache_page_for_all_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)


@extend_schema_view(
list=extend_schema(summary="List"),
Expand Down Expand Up @@ -819,6 +851,14 @@ class ContentTagViewSet(viewsets.ReadOnlyModelViewSet):
pagination_class = LargePagination
permission_classes = (AnonymousAccessReadonlyPermission,)

@method_decorator(
cache_page_for_all_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, *args, **kwargs):
return super().list(*args, **kwargs)


@extend_schema_view(
list=extend_schema(summary="List"),
Expand All @@ -838,6 +878,14 @@ class DepartmentViewSet(viewsets.ReadOnlyModelViewSet):
lookup_url_kwarg = "department_id"
lookup_field = "department_id__iexact"

@method_decorator(
cache_page_for_all_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, *args, **kwargs):
return super().list(*args, **kwargs)


@extend_schema_view(
list=extend_schema(summary="List"),
Expand All @@ -853,6 +901,14 @@ class SchoolViewSet(viewsets.ReadOnlyModelViewSet):
pagination_class = LargePagination
permission_classes = (AnonymousAccessReadonlyPermission,)

@method_decorator(
cache_page_for_all_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, *args, **kwargs):
return super().list(*args, **kwargs)


@extend_schema_view(
list=extend_schema(summary="List"),
Expand All @@ -868,6 +924,14 @@ class PlatformViewSet(viewsets.ReadOnlyModelViewSet):
pagination_class = LargePagination
permission_classes = (AnonymousAccessReadonlyPermission,)

@method_decorator(
cache_page_for_all_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, *args, **kwargs):
return super().list(*args, **kwargs)


@extend_schema_view(
list=extend_schema(summary="List"),
Expand All @@ -884,6 +948,14 @@ class OfferedByViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = (AnonymousAccessReadonlyPermission,)
lookup_field = "code"

@method_decorator(
cache_page_for_anonymous_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)


@extend_schema_view(
list=extend_schema(
Expand Down Expand Up @@ -994,6 +1066,11 @@ def get_queryset(self) -> QuerySet:
.distinct()
)

@method_decorator(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was some deliberate results randomization added to this featured list view so as not to give any apparent preference to any particular unit. Might be good to double-check with Ferdi to see if it's okay if that randomization is cached so all anonymous users see the same result for 24 hours before it's randomized again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double checked with ferdi. He said we can leave this as-is and have a separate ticket to handle the randomization on the frontend.

cache_page_for_anonymous_users(
settings.SEARCH_PAGE_CACHE_DURATION, cache="redis", key_prefix="search"
)
)
@extend_schema(
summary="List",
description="Get a paginated list of featured resources",
Expand Down
9 changes: 5 additions & 4 deletions main/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ def clean_data(data: str) -> str:

def clear_search_cache():
cache = caches["redis"]
search_keys = cache.keys("views.decorators.cache.cache_page.search.*")
cache.delete_many(search_keys)
search_keys = cache.keys("views.decorators.cache.cache_header.search.*")
cache.delete_many(search_keys)
if hasattr(cache, "keys"):
search_keys = cache.keys("views.decorators.cache.cache_page.search.*")
cache.delete_many(search_keys)
search_keys = cache.keys("views.decorators.cache.cache_header.search.*")
cache.delete_many(search_keys)
2 changes: 2 additions & 0 deletions news_events/management/commands/backpopulate_news_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from django.core.management import BaseCommand

from main.utils import clear_search_cache
from news_events.etl import pipelines
from news_events.models import FeedImage, FeedSource

Expand Down Expand Up @@ -55,3 +56,4 @@ def handle(self, *args, **options): # noqa: ARG002
f"Processed {etl_func} pipeline with {item_count} items"
)
self.stdout.write("Finished running news/events ETL pipelines")
clear_search_cache()
Loading