feat: add personalized course recommendation system for students#1039
feat: add personalized course recommendation system for students#1039ayesha1145 wants to merge 1 commit intoalphaonelabs:mainfrom
Conversation
👀 Peer Review RequiredHi @ayesha1145! This pull request does not yet have a peer review. Before this PR can be merged, please request a review from one of your peers:
Thank you for contributing! 🎉 |
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
Note
|
| Cohort / File(s) | Summary |
|---|---|
Templates web/templates/dashboard/recommendations.html, web/templates/dashboard/student.html |
New standalone recommendations template and injected dashboard recommendations block. Both render responsive course cards with image fallback, subject, truncated title, teacher display name, price/Free, optional average rating, "View Course" links, and an empty-state with a "Browse Courses" link. |
Views / Logic web/views.py |
Added tiered recommendation logic: Tier 1 same-subject published courses, Tier 2 high-rated published courses (avg_rating ≥ 4.0), Tier 3 popular published courses; dashboard view populates recommendations (capped at 3) and a new @login_required course_recommendations view applies the same logic with a larger result limit and renders the recommendations template. |
Routing web/urls.py |
Added i18n-prefixed URL pattern dashboard/student/recommendations/ mapped to views.course_recommendations (name course_recommendations). |
Sequence Diagram
sequenceDiagram
participant Student as Student (Client)
participant View as RecommendationView
participant DB as Database
participant Tpl as TemplateRenderer
Student->>View: GET /dashboard/student/recommendations/ (authenticated)
activate View
View->>DB: Fetch student's enrollments (to exclude)
View->>DB: Query Tier 1 (published, same subject, exclude enrolled/teacher)
alt Tier 1 meets target
View->>View: select Tier 1 results
else
View->>DB: Query Tier 2 (published, avg_rating ≥ 4.0, exclude prior)
alt Tier1+Tier2 meet target
View->>View: combine Tier1 + Tier2
else
View->>DB: Query Tier 3 (published, popular by enrollment, exclude prior)
View->>View: combine tiers to reach target
end
end
View->>Tpl: Render `dashboard/recommendations.html` with recommendations
deactivate View
Tpl-->>Student: HTML response (cards or empty state)
Estimated Code Review Effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title 'feat: add personalized course recommendation system for students' directly and clearly summarizes the main change: implementing a new recommendation system feature for student users. |
| Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/templates/dashboard/recommendations.html`:
- Around line 25-27: The template uses an unnecessary escape in the price
display: change the string that renders the price from the escaped form (\"\\${{
course.price }}\") to an unescaped dollar sign (\"${{ course.price }}\") inside
the span that contains the if/else for course.price so the dollar sign is
rendered normally; update the span that includes the conditional around
course.price accordingly.
In `@web/templates/dashboard/student.html`:
- Around line 239-242: The "View Course" anchor (the link rendered by {% url
'course_detail' course.slug %} with text "View Course") lacks an explicit focus
style for keyboard users; update its class list to include accessible focus
utility classes (for example: focus:outline-none focus:ring-2
focus:ring-offset-2 focus:ring-teal-500 and a dark-mode counterpart like
dark:focus:ring-teal-400) so a visible focus ring appears when tabbing to the
link, preserving existing hover styles and transition classes.
- Around line 212-216: The "Course Recommendations" block is currently placed
after the main container's closing </div>, causing it to render outside the
layout; move the entire recommendations section (the {% if recommendations %}
... matching {% endif %} block labeled "Course Recommendations") so it sits
before the main container's closing </div> that opened near the top of the
template, preserving the container's padding and layout.
In `@web/views.py`:
- Around line 2637-2645: The dashboard and “See all” pages use different
ordering and per-tier limits for the same tier (top_rated_recs) — unify them by
moving the query building into a shared function
(web/recommendations.py:get_course_recommendations) that accepts a
limit/page_size and optional tier identifier; update both call sites (the
dashboard code that builds top_rated_recs and the “See all” view around lines
2698–2710) to call get_course_recommendations with only the desired limit, and
ensure the shared builder uses the same annotate/filter/order_by logic (e.g.,
.annotate(avg_rating=Avg("reviews__rating"),
enrollment_count=Count("enrollments")).filter(avg_rating__gte=4.0).order_by("-avg_rating",
"-enrollment_count")) so ordering and tier limits are consistent across
surfaces.
- Line 2625: The current enrolled_course_ids is built from all Enrollment rows
(enrollments.values_list("course_id", flat=True)) which hides courses with
non-blocking statuses like "rejected"; change the query that builds
enrolled_course_ids to filter enrollments by only the statuses that should
suppress recommendations (e.g., status__in=[...blocking_statuses...]) so it
returns course_ids for truly enrolled/blocked states; update the same logic used
later (the enrollments usage at the block around lines referenced 2679-2681) to
apply the same filtered status set and ensure both occurrences use the shared
filtered queryset or helper to avoid regressions.
- Around line 2642-2644: The Course queryset annotating avg_rating and
enrollment_count is over-counting enrollments due to the join with reviews;
update the Count("enrollments") to Count("enrollments", distinct=True) in both
places where .annotate(avg_rating=Avg("reviews__rating"),
enrollment_count=Count("enrollments")) appears so the enrollment_count is
de-duplicated (this will correct the tie-breaker ordering that uses
-enrollment_count).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 948e882c-ed0a-4c1a-89e8-cd711158651f
📒 Files selected for processing (4)
web/templates/dashboard/recommendations.htmlweb/templates/dashboard/student.htmlweb/urls.pyweb/views.py
ab303ca to
7f38ade
Compare
💬 Unresolved Review ConversationsHi @ayesha1145! 👋 This pull request currently has 1 unresolved review conversation. Please address all review feedback and push a new commit to resolve them before this PR can be merged. Steps to resolve:
Once all conversations are resolved, this notice will be removed automatically. Thank you! 🙏 |
|
Addressed CodeRabbit feedback:
|
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (2)
web/templates/dashboard/student.html (1)
212-249:⚠️ Potential issue | 🔴 CriticalCritical: Recommendations section is outside the container, and there's an unmatched closing tag.
The HTMLHint static analysis confirms an unpaired
</div>at line 249. Here's what's happening:
- Line 212 closes the main
.containerdiv that opened on line 7- Lines 214-248 place the recommendations section outside that container
- Line 249 adds an extra
</div>with no matching opening tagThis breaks the page layout (recommendations won't have proper container padding) and produces invalid HTML.
🐛 Proposed fix to restore proper nesting
Move the recommendations section inside the container by restructuring lines 212-249:
- </div> - <!-- Course Recommendations --> {% if recommendations %} <div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="flex items-center justify-between mb-4"> <!-- ... rest of recommendations content ... --> </div> </div> {% endif %} - </div> + </div> {% endblock content %}The final structure should have only one
</div>closing the container, placed after the recommendations section.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/templates/dashboard/student.html` around lines 212 - 249, The recommendations block is placed outside the main container and there's an unmatched closing </div>; move the entire "Course Recommendations" section (the {% if recommendations %} ... {% endif %} block) so it sits inside the main <div class="container"> (the container opened near the top) before the container's closing </div>, then remove the stray extra </div> so there is exactly one closing tag for the container; ensure the {% if recommendations %} block and its inner markup remain intact and properly nested.web/views.py (1)
2625-2625:⚠️ Potential issue | 🟠 MajorInclude
pendingenrollments in the exclusion set.A course with a pending enrollment can still be recommended here, but
enroll_courselater rejects that same course as already enrolled. That creates a broken recommendation path for students who have started checkout but not finished it yet. Please treatpendingas a blocking status in both queries.Possible adjustment
- enrolled_course_ids = enrollments.filter(status__in=["approved", "completed"]).values_list("course_id", flat=True) + enrolled_course_ids = enrollments.filter( + status__in=["pending", "approved", "completed"] + ).values_list("course_id", flat=True)enrolled_course_ids = Enrollment.objects.filter( - student=request.user, status__in=["approved", "completed"] + student=request.user, status__in=["pending", "approved", "completed"] ).values_list("course_id", flat=True)Also applies to: 2679-2681
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/views.py` at line 2625, The enrolled_course_ids query currently filters enrollments by status__in=["approved", "completed"] but should also treat "pending" as a blocking status; update the filter in the enrolled_course_ids assignment (and the similar query around lines 2679-2681) to include "pending" in the status__in list so pending enrollments are excluded from recommendations and match the enroll_course check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/templates/dashboard/recommendations.html`:
- Line 30: The decorative star icon element in the recommendations template (<i
class="fas fa-star mr-1"></i>) should be hidden from assistive technology; add
aria-hidden="true" to that <i> element so screen readers only announce the
rating text ({{ course.average_rating|floatformat:1 }}).
- Line 44: The decorative search icon element <i class="fas fa-search text-4xl
text-gray-300 dark:text-gray-600 mb-4"></i> should be hidden from assistive
tech; update that element in recommendations.html to include aria-hidden="true"
so screen readers ignore the icon while leaving the visible text unchanged.
- Line 17: The <i> element with classes "fas fa-book-open text-teal-500
dark:text-teal-300 text-3xl" is decorative and should be hidden from assistive
tech; update the <i> element (the icon with class "fas fa-book-open") to include
aria-hidden="true" so screen readers skip it while preserving visual display.
- Line 12: The long <div> element with class="bg-white dark:bg-gray-800
rounded-lg shadow hover:shadow-md transition duration-200 overflow-hidden flex
flex-col" exceeds the 120-char limit; break the class attribute across multiple
lines (or run djlint with 120-char width) so the <div> tag lines are <=120
characters—split class tokens onto separate lines for readability while keeping
the same classes and preserving the element start tag (the <div ...> element
shown in the template).
- Around line 34-37: The anchor element for the "View Course" button has a
single very long class attribute that exceeds the 120-character line limit;
reformat the <a href="{% url 'course_detail' course.slug %}" ...> element by
breaking the long class attribute into multiple lines (one or more class groups
per line) so each line is ≤120 chars, ensuring the {% url 'course_detail'
course.slug %} tag and the "View Course" text remain unchanged and the element
still renders the same; run djlint (120-char) to verify compliance.
In `@web/views.py`:
- Around line 2629-2656: The current tiered recommendation builders
(same_subject_recs, top_rated_recs, popular_recs) each slice independently and
then concatenate, allowing the dashboard recommendations list (recommendations)
to exceed the intended cap; update the logic so each subsequent tier is limited
by remaining_slots = MAX_DASHBOARD_SLOTS - len(recommendations) (e.g., 3 -
len(recommendations)) before querying/slicing top_rated_recs and popular_recs
and only add up to that remaining_slots, and likewise remove or replace fixed
[:4] truncations on the dedicated page with proper pagination or dynamic limits
so tier 2/3 queries use the remaining slot count instead of a fixed independent
slice. Ensure you update the variables referenced (same_subject_recs,
top_rated_recs, popular_recs, recommendations, enrolled_course_ids,
enrolled_subject_ids) so each tier respects remaining_slots.
- Around line 2672-2678: The docstring for the recommendation routine is out of
sync with the implementation: update the docstring in web/views.py for the
function get_personalized_recommendations (or the surrounding recommendation
function) so the tier order matches the code — 1) courses in subjects the
student is already enrolled in (but not yet enrolled in), 2) highest-rated
published courses the student hasn't enrolled in yet (rating fallback), and 3)
popular courses by enrollment count (popularity fallback); keep the brief
description and priority order format in the existing docstring.
---
Duplicate comments:
In `@web/templates/dashboard/student.html`:
- Around line 212-249: The recommendations block is placed outside the main
container and there's an unmatched closing </div>; move the entire "Course
Recommendations" section (the {% if recommendations %} ... {% endif %} block) so
it sits inside the main <div class="container"> (the container opened near the
top) before the container's closing </div>, then remove the stray extra </div>
so there is exactly one closing tag for the container; ensure the {% if
recommendations %} block and its inner markup remain intact and properly nested.
In `@web/views.py`:
- Line 2625: The enrolled_course_ids query currently filters enrollments by
status__in=["approved", "completed"] but should also treat "pending" as a
blocking status; update the filter in the enrolled_course_ids assignment (and
the similar query around lines 2679-2681) to include "pending" in the status__in
list so pending enrollments are excluded from recommendations and match the
enroll_course check.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 56c0910b-0929-4b6a-8101-f5d5916c7853
📒 Files selected for processing (4)
web/templates/dashboard/recommendations.htmlweb/templates/dashboard/student.htmlweb/urls.pyweb/views.py
7f38ade to
ec90020
Compare
|
Addressed CodeRabbit feedback:
|
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
web/templates/dashboard/student.html (1)
214-249:⚠️ Potential issue | 🟠 MajorFix the container closing order (mismatched
</div>+ section outside layout).The recommendations block is still outside the main container because the container closes at Line 212, then closes again at Line 249. This reintroduces the earlier layout bug and invalid HTML.
🐛 Proposed fix
- </div> - - <!-- Course Recommendations --> - {% if recommendations %} + <!-- Course Recommendations --> + {% if recommendations %} <div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="flex items-center justify-between mb-4"> <h2 class="text-xl font-semibold text-gray-800 dark:text-white"> @@ </div> {% endif %} </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/templates/dashboard/student.html` around lines 214 - 249, The recommendations block is rendered outside the main container due to a mismatched closing </div>; fix by removing the premature container close so the Course Recommendations div (the "{% if recommendations %}" block and its nested divs) sits inside the main layout wrapper, and ensure there is a single matching closing </div> for that main container at the end of the template so HTML nesting is balanced.web/views.py (1)
2624-2660: 🧹 Nitpick | 🔵 TrivialExtract a shared recommendation builder to prevent logic drift.
Both views duplicate the same multi-tier algorithm with near-identical query logic. Pull this into one helper and pass only
limitper surface to keep future fixes consistent.♻️ Refactor sketch
+def _build_course_recommendations(user: User, limit: int) -> list[Course]: + # shared 3-tier logic here + return recommendations @@ - # Course recommendations - ... - recommendations += list(popular_recs) + recommendations = _build_course_recommendations(request.user, limit=3) @@ def course_recommendations(request: HttpRequest) -> HttpResponse: @@ - ... - recommendations += list(popular) + recommendations = _build_course_recommendations(request.user, limit=8)Also applies to: 2673-2734
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/views.py` around lines 2624 - 2660, The course recommendation multi-tier query logic is duplicated; extract it into a single helper (e.g., get_recommended_courses or build_course_recommendations) that takes the user (request.user), enrolled_course_ids/enrollments and a limit argument and returns a list/queryset of recommended Course objects; move the existing logic that computes enrolled_subject_ids, same_subject_recs, top_rated_recs and popular_recs into that helper and replace both duplicated blocks in web.views (the block shown and the one at lines ~2673-2734) with calls to this helper passing only limit, ensuring you exclude the current user's courses and already-enrolled ids and preserve the same annotate/filter/order_by behavior.web/templates/dashboard/recommendations.html (1)
12-12:⚠️ Potential issue | 🟡 MinorRe-run djlint: class lines still exceed the 120-char limit.
Both class attributes are still too long for the configured template formatting rule. Please split these attributes across lines (or run djlint with width 120) to keep the template compliant and readable.
As per coding guidelines: "Format HTML/Django templates with djlint using 120-character line length".
Also applies to: 35-35
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/templates/dashboard/recommendations.html` at line 12, The long class attribute on the top-level div (the element with class="bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition duration-200 overflow-hidden flex flex-col") exceeds the 120-char djlint limit; split the class attribute across multiple lines (e.g., one class per line or logical groups) so each line stays under 120 chars and repeat the same change for the other offending div around the 35-35 occurrence; alternatively, reformat the template with djlint --width 120, but prefer explicit multi-line class attributes for readability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/templates/dashboard/recommendations.html`:
- Around line 46-47: The "Browse Courses" CTA anchor is missing dark-mode and
visible keyboard focus styles; update the anchor (the element containing the
"Browse Courses" text) to include dark: prefixed background/hover classes and
add focus/focus-visible ring classes (e.g., focus:outline-none, focus:ring-2,
focus:ring-offset-2, focus:ring-teal-500 and matching dark:focus:ring color) so
it matches other interactive buttons and is keyboard accessible.
- Line 12: The recommendation card's container div currently uses the Tailwind
class "shadow" instead of the project-standard card class "shadow-lg"; update
the div that has class="bg-white dark:bg-gray-800 rounded-lg shadow
hover:shadow-md transition duration-200 overflow-hidden flex flex-col" to use
"shadow-lg" (and remove or adjust the hover:shadow-md if you want consistent
card behavior) so it matches the project card convention used for project
templates.
In `@web/views.py`:
- Around line 2630-2636: The querysets (e.g., same_subject_recs built from
Course.objects.filter) only annotate avg_rating in one tier while templates call
the model property course.average_rating, causing per-card DB re-queries and
inconsistent display; update every recommendation queryset (including the blocks
around same_subject_recs and the other tiers at the noted ranges) to annotate a
single field like avg_rating (using Avg on ratings) and order by that annotated
name where needed, then change templates to read course.avg_rating with a
fallback to avoid calling the model property.
---
Duplicate comments:
In `@web/templates/dashboard/recommendations.html`:
- Line 12: The long class attribute on the top-level div (the element with
class="bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-md transition
duration-200 overflow-hidden flex flex-col") exceeds the 120-char djlint limit;
split the class attribute across multiple lines (e.g., one class per line or
logical groups) so each line stays under 120 chars and repeat the same change
for the other offending div around the 35-35 occurrence; alternatively, reformat
the template with djlint --width 120, but prefer explicit multi-line class
attributes for readability.
In `@web/templates/dashboard/student.html`:
- Around line 214-249: The recommendations block is rendered outside the main
container due to a mismatched closing </div>; fix by removing the premature
container close so the Course Recommendations div (the "{% if recommendations
%}" block and its nested divs) sits inside the main layout wrapper, and ensure
there is a single matching closing </div> for that main container at the end of
the template so HTML nesting is balanced.
In `@web/views.py`:
- Around line 2624-2660: The course recommendation multi-tier query logic is
duplicated; extract it into a single helper (e.g., get_recommended_courses or
build_course_recommendations) that takes the user (request.user),
enrolled_course_ids/enrollments and a limit argument and returns a list/queryset
of recommended Course objects; move the existing logic that computes
enrolled_subject_ids, same_subject_recs, top_rated_recs and popular_recs into
that helper and replace both duplicated blocks in web.views (the block shown and
the one at lines ~2673-2734) with calls to this helper passing only limit,
ensuring you exclude the current user's courses and already-enrolled ids and
preserve the same annotate/filter/order_by behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 247aa9ab-d7c6-4c59-829b-503e932d85c1
📒 Files selected for processing (4)
web/templates/dashboard/recommendations.htmlweb/templates/dashboard/student.htmlweb/urls.pyweb/views.py
ec90020 to
4c7d30b
Compare
4c7d30b to
8c510ef
Compare
|
Addressed CodeRabbit feedback:
|
Adds a personalized course recommendation system that surfaces relevant courses to students on their dashboard.
Changes:
Recommendation algorithm (3-tier priority):
Features:
Purpose
Adds a personalized course recommendation system for students, surfaced on the student dashboard and via a dedicated recommendations page.
Key Changes
Templates
Route
Views / Logic
Accessibility / UX
Impact / Notes