fix HomeView N+1 and cache the curriculum tree
GET /api/home is the most-hit authenticated endpoint (every app open) and
was running one COUNT per node. Now it is a flat number of queries
regardless of grade size, and the shared curriculum skeleton is served
from cache; per-user progress/stats stay live.
content/views.py
- _node_status(): takes a precomputed set of completed lesson_ids and a
node's lesson_ids instead of firing its own COUNT query.
- HomeView.get(): one query for all completed lesson_ids of the user in
the grade, status computed in Python; grade tree (units -> nodes ->
lesson_ids, no per-user data) read from cache
(content:tree:grade:{id}:v{N}); missing grade -> 404 (was an
unhandled 500); grade with no units -> empty units, not 404.
- GradesListView: served from cache (content:grades:v{N}).
- curriculum_version() / bump_curriculum_version(): one shared version
key; bumping it invalidates every content cache key at once.
- both GET views get throttle_scope='read' + GuestAwareContentThrottle.
content/receivers.py
- post_save/post_delete on Grade/Unit/Node/Lesson -> bump_curriculum_version().
content/tests.py
- HomeViewScalabilityTests: query count flat for 2 vs 20 nodes; 2nd call
hits cache (no content_unit/node SQL); a new Node invalidates the tree;
missing grade 404; empty grade 200.
- GradesListCacheTests: list cached, invalidated on new Grade.
- ReadThrottleTests: /api/home returns 429 past the limit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from .models import UserAnswer, UserLessonCompletion
|
||||
from .models import Grade, Lesson, Node, Unit, UserAnswer, UserLessonCompletion
|
||||
from .services import touch_node_progress_on_answer, update_node_progress_on_lesson_completion
|
||||
|
||||
|
||||
@@ -17,3 +17,18 @@ def _on_lesson_completion_created(sender, instance, created, **kwargs):
|
||||
if not created:
|
||||
return
|
||||
update_node_progress_on_lesson_completion(instance.user_id, instance.lesson)
|
||||
|
||||
|
||||
# با هر تغییرِ برنامهی درسی (از ادمین یا seed)، نسخهی کشِ محتوا یک واحد جلو
|
||||
# میرود و همهی کلیدهای content:grades / content:tree را یکجا باطل میکند.
|
||||
@receiver(post_save, sender=Grade, dispatch_uid='content_bump_curriculum_grade_save')
|
||||
@receiver(post_delete, sender=Grade, dispatch_uid='content_bump_curriculum_grade_delete')
|
||||
@receiver(post_save, sender=Unit, dispatch_uid='content_bump_curriculum_unit_save')
|
||||
@receiver(post_delete, sender=Unit, dispatch_uid='content_bump_curriculum_unit_delete')
|
||||
@receiver(post_save, sender=Node, dispatch_uid='content_bump_curriculum_node_save')
|
||||
@receiver(post_delete, sender=Node, dispatch_uid='content_bump_curriculum_node_delete')
|
||||
@receiver(post_save, sender=Lesson, dispatch_uid='content_bump_curriculum_lesson_save')
|
||||
@receiver(post_delete, sender=Lesson, dispatch_uid='content_bump_curriculum_lesson_delete')
|
||||
def _bump_curriculum_cache_version(sender, **kwargs):
|
||||
from .views import bump_curriculum_version
|
||||
bump_curriculum_version()
|
||||
|
||||
Reference in New Issue
Block a user