Files
21ae991246 feat: Implement Redis Caching Strategy (#319)
This commit fulfills the "Redis Caching Stratejisi" (2.1) tasks from `.jules/development-plan.md`.

- Verified that `CACHES` is configured for Native Django cache (`RedisCache`) in `aniscrap_core/settings.py`, handling the cache framework requirement.
- Verified that `AnimeViewSet` and `HomeViewSet` correctly implement `@cache_page` with 5-minute and 10-minute TTLs respectively. Added inline documentation to explicitly map these blocks to the development plan for traceability.
- Implemented user-specific badge caching in `UserBadgeViewSet` by injecting the `user.id` and `QUERY_STRING` into the cache key for a 30-minute TTL.
- Enforced the explicit cache invalidation strategy in `content/signals.py`, preserving `cache.clear()` when an `Anime` is saved or deleted via signals, while appending logs to confirm intent.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Senpai-YoloBot <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-22 12:22:42 +00:00

88 lines
3.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import logging
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from django.urls import reverse
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from .models import Anime, Episode, Subscription, Genre, Season
from .tasks import send_new_episode_email_task, send_websocket_notifications_task
logger = logging.getLogger(__name__)
@receiver(post_save, sender=Genre)
@receiver(post_delete, sender=Genre)
def clear_genre_cache(sender, instance, **kwargs):
"""
Clear the genres cache whenever a genre is added, updated, or deleted.
"""
cache.delete('all_genres')
@receiver(post_save, sender=Season)
@receiver(post_delete, sender=Season)
def clear_season_cache(sender, instance, **kwargs):
"""
Clear the anime seasons cache whenever a season is modified.
"""
cache.delete(f'anime_{instance.anime.id}_seasons')
@receiver(post_save, sender=Episode)
@receiver(post_delete, sender=Episode)
def clear_home_cache(sender, instance, **kwargs):
"""
Clear the homepage cache whenever an episode is added, updated, or deleted.
Also clears the anime seasons cache.
"""
cache.delete('home_latest_episodes')
cache.delete(f'anime_{instance.season.anime.id}_seasons')
# Invalidate cache pages
cache.clear()
@receiver(post_save, sender=Anime)
@receiver(post_delete, sender=Anime)
def clear_anime_cache(sender, instance, **kwargs):
"""
Cache invalidation strategy: signal tabanlı (AnimeAdmin'de save signal -> cache clear)
Clear cache when an Anime is saved (created/updated) or deleted.
"""
logger.info(f"AnimeAdmin save signal -> cache clear triggered for Anime {instance.id}")
cache.clear()
@receiver(post_save, sender=Episode)
def notify_subscribers(sender, instance, created, **kwargs):
"""
Notify subscribers when a new episode is released.
"""
if created:
from users.models import Notification # Avoid circular import
anime = instance.season.anime
subscribers = Subscription.objects.filter(anime=anime).select_related('user')
notifications = []
try:
link = reverse('watch', args=[instance.id])
except Exception:
# Fallback if route is missing
link = f"/watch/{instance.id}/"
for sub in subscribers:
notifications.append(Notification(
user=sub.user,
title=f"New Episode: {anime.title}",
message=f"Episode {instance.number} of {anime.title} is now available!",
link=link
))
if notifications:
Notification.objects.bulk_create(notifications)
# Send real-time notifications via WebSockets using a background task
# to prevent blocking the main thread with O(N) network operations.
user_ids = [sub.user.id for sub in subscribers]
title = f"New Episode: {anime.title}"
message = f"Episode {instance.number} of {anime.title} is now available!"
send_websocket_notifications_task.delay(user_ids, title, message, link)
# Trigger Email Task (Async)
send_new_episode_email_task.delay(instance.id)