Skip to content

autocomplete feature for search#1186

Open
pelin-sayar wants to merge 34 commits intodevfrom
autocomplete
Open

autocomplete feature for search#1186
pelin-sayar wants to merge 34 commits intodevfrom
autocomplete

Conversation

@pelin-sayar
Copy link
Copy Markdown
Collaborator

@pelin-sayar pelin-sayar commented Nov 2, 2025

GitHub Issues addressed

  • This PR closes

What I did

  • Made an autocomplete feature for the search bar on the website. It displays relevant courses, instructors, and clubs while typing.
  • Created autocomplete method in search.py
  • Created autocomplete.js in static/search.
  • Made autocomplete serializers in serializers.py

Screenshots

  • Before
Screenshot 2026-04-05 at 11 48 37 AM Screenshot 2026-04-05 at 11 48 48 AM
  • After
Screenshot 2026-04-05 at 11 47 56 AM Screenshot 2026-04-05 at 11 48 08 AM

Testing

  • To test this navigate to the search bar and start typing, suggestions should be displayed and change as you type.
  • Unit tests are located in test_search_autocomplete.py
Screenshot_2026-02-01_at_6 41 34_PM Screenshot_2026-02-26_at_5 40 53_PM

Questions/Discussions/Notes

Summary by CodeRabbit

  • New Features

    • Real-time grouped autocomplete for Courses, Instructors, and Clubs with click-to-fill-and-submit behavior.
  • Backend

    • New autocomplete API powering concise, ranked, limited results with graceful handling of empty or failed queries.
    • Semester load now replaces existing sections for a given semester.
  • UI Updates

    • Streamlined search bar layout, integrated autocomplete dropdown, and new styling; preserves search mode and submits hidden filter fields (weekdays, min GPA).
  • Documentation

    • Added semester-data guide and updated grade-data and useful-commands docs.
  • Bug Fixes

    • Normalized two instructor name variants to improve grade import matching.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 2, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a typeahead autocomplete end-to-end: new DRF serializers and /api/autocomplete/ endpoint returning course, instructor, and club suggestions; client-side debounced autocomplete with AbortController; updated search bar template, CSS, JS, URL route, view signatures/exports, and related docs and management-command tweaks.

Changes

Cohort / File(s) Summary
Backend Serializers & API
tcf_website/api/serializers.py, tcf_website/urls.py
Added CourseAutocompleteSerializer, InstructorAutocompleteSerializer, ClubAutocompleteSerializer; registered path("api/autocomplete/", views.autocomplete, name="autocomplete").
Autocomplete View & Search Logic
tcf_website/views/search.py, tcf_website/views/__init__.py
Added autocomplete(request) DRF view; added/instrumented limits (fetch_clubs(..., limit), fetch_instructors(..., limit), fetch_courses(..., limit)), new get_instructor_results helper; exported autocomplete in views/__init__.py.
Frontend: JS, Template, CSS
tcf_website/static/search/autocomplete.js, tcf_website/templates/search/searchbar.html, tcf_website/static/search/searchbar.css
New client-side autocomplete (250ms debounce, AbortController, grouped results up to 8 per group, click-to-fill-and-submit, outside-click dismissal); updated searchbar markup (wrapper, id="search-input", hidden inputs weekdays, min_gpa, mode wiring) and styles for dropdown.
Mode Toggle
tcf_website/templates/club/mode_toggle.html
Added hidden name="mode" input to persist current mode (clubs vs courses); newline fix.
Semester & Grades management
tcf_website/management/commands/load_semester.py, tcf_website/management/commands/load_grades.py
load_semester now deletes existing Section rows for the semester before loading (destructive refresh); load_grades adds two instructor name normalization mappings (Hott → Robbie, Nguyen → Rich).
Docs
doc/semester-data.md, doc/grade-data.md, doc/useful-commands.md
Added semester-data.md; reorganized grade-data.md (new fetching section and FOIA note moved); merged semester command docs into useful-commands.md linking to new page.
Startup Script
scripts/container-startup.sh
Removed commented-out load_semester line (cleaned commented code).

Sequence Diagram

sequenceDiagram
    autonumber
    participant User
    participant Browser
    participant API as /api/autocomplete/
    participant View as views.search.autocomplete
    participant DB as Database/Index
    participant UI as Suggestions_DOM

    User->>Browser: type into search input
    Browser->>Browser: debounce 250ms, abort prior requests via AbortController
    Browser->>API: GET /api/autocomplete/?q=...&mode=...
    API->>View: invoke autocomplete view
    View->>DB: query courses, instructors, clubs (apply limits)
    DB-->>View: return matched results
    View-->>API: serialized JSON (courses, instructors, clubs)
    API-->>Browser: JSON response
    Browser->>UI: render grouped suggestions
    User->>UI: click suggestion
    UI->>Browser: set input value, clear suggestions, submit form
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Jay-Lalwani
  • ajnye

Poem

🐇 I nibble at queries with a soft little tap,
Debounced and tidy, I fetch each name and map,
Courses, clubs, instructors lined in a row,
Click a carrot suggestion — away we go!
Paws on the keyboard, autocomplete glow.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'autocomplete feature for search' directly and clearly describes the main changeset, which adds autocomplete functionality to the search interface across backend, frontend, and UI layers.
Description check ✅ Passed The PR description covers the main feature (autocomplete), lists key files modified, and includes screenshots and testing instructions, though the GitHub issue reference is incomplete.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch autocomplete

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
tcf_website/views/search.py (2)

348-396: Rename similarity_second to similarity_last for consistency.

Line 360 uses similarity_second for the last_name field, but the existing fetch_instructors function (line 215) uses similarity_last for the same purpose. This inconsistency reduces code readability.

Apply this diff:

     instructors = (
         Instructor.objects.annotate(
             similarity_first=TrigramSimilarity("first_name", query),
-            similarity_second=TrigramSimilarity("last_name", query),
+            similarity_last=TrigramSimilarity("last_name", query),
             similarity_full=TrigramSimilarity("full_name", query),
         )
         .annotate(
             max_similarity=Greatest(
                 F("similarity_first"),
-                F("similarity_second"),
+                F("similarity_last"),
                 F("similarity_full"),
                 output_field=FloatField(),
             )
         )

348-396: Consider extracting the instructor similarity logic to reduce duplication.

Both autocomplete and fetch_instructors (lines 207-238) compute instructor trigram similarities using nearly identical logic. This duplication increases maintenance burden.

Consider extracting a helper function like:

def _annotate_instructor_similarity(queryset, query):
    """Annotate instructor queryset with trigram similarity scores."""
    return queryset.annotate(
        similarity_first=TrigramSimilarity("first_name", query),
        similarity_last=TrigramSimilarity("last_name", query),
        similarity_full=TrigramSimilarity("full_name", query),
    ).annotate(
        max_similarity=Greatest(
            F("similarity_first"),
            F("similarity_last"),
            F("similarity_full"),
            output_field=FloatField(),
        )
    )

Then both functions can use: instructors = _annotate_instructor_similarity(Instructor.objects, query).filter(...)

tcf_website/templates/search/searchbar.html (1)

208-209: Fix inconsistent HTML attribute spacing.

Lines 208-209 use class = "..." and id = "..." with spaces around the equals sign, which is inconsistent with the rest of the file (e.g., lines 9, 10, 21 use class="..." without spaces).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12e0ca1 and 55c057a.

📒 Files selected for processing (6)
  • tcf_website/api/serializers.py (2 hunks)
  • tcf_website/static/search/autocomplete.js (1 hunks)
  • tcf_website/templates/search/searchbar.html (1 hunks)
  • tcf_website/urls.py (2 hunks)
  • tcf_website/views/__init__.py (1 hunks)
  • tcf_website/views/search.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
tcf_website/api/serializers.py (1)
tcf_website/models/models.py (18)
  • Meta (151-160)
  • Meta (190-200)
  • Meta (242-249)
  • Meta (440-457)
  • Meta (513-521)
  • Meta (853-872)
  • Meta (905-908)
  • Meta (933-939)
  • Meta (981-992)
  • Meta (1043-1052)
  • Meta (1093-1099)
  • Meta (1347-1358)
  • Meta (1387-1397)
  • Meta (1597-1603)
  • Meta (1625-1635)
  • Meta (1657-1667)
  • Course (536-872)
  • Instructor (293-457)
tcf_website/templates/search/searchbar.html (2)
tcf_website/static/search/filters.js (2)
  • clearFilters (299-335)
  • saveFiltersToLocalStorage (77-106)
tcf_website/static/club/mode_toggle.js (1)
  • updateSearchbarState (25-47)
tcf_website/views/search.py (3)
tcf_website/static/search/autocomplete.js (2)
  • response (44-47)
  • data (54-54)
tcf_website/api/serializers.py (2)
  • CourseAutocompleteSerializer (155-164)
  • InstructorAutocompleteSerializer (175-183)
tcf_website/models/models.py (2)
  • Instructor (293-457)
  • Course (536-872)
tcf_website/urls.py (1)
tcf_website/views/search.py (2)
  • search (76-151)
  • autocomplete (349-396)
tcf_website/views/__init__.py (1)
tcf_website/views/browse.py (1)
  • club_category (548-584)
🪛 GitHub Actions: Continuous Integration
tcf_website/static/search/autocomplete.js

[error] 8-8: prettier/prettier Delete extraneous whitespace at line 8 (formatting issue detected by ESLint). Command failed: npx eslint -c .config/.eslintrc.yml tcf_website/static/

🪛 GitHub Check: eslint
tcf_website/static/search/autocomplete.js

[failure] 142-142:
Insert ,


[failure] 132-132:
Replace !suggestionsContainer.contains(event.target)·&&·event.target·!==·searchInput with ⏎······!suggestionsContainer.contains(event.target)·&&⏎······event.target·!==·searchInput⏎····


[failure] 46-46:
Insert ,


[failure] 31-31:
Insert ⏎·····


[failure] 17-17:
Delete ··


[failure] 8-8:
Delete ··

🪛 Ruff (0.14.2)
tcf_website/api/serializers.py

164-164: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


180-183: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (7)
tcf_website/views/__init__.py (1)

10-10: LGTM!

The import reordering has no functional impact.

tcf_website/urls.py (1)

7-7: LGTM!

The autocomplete endpoint is properly configured with a clean URL pattern and named route.

Also applies to: 131-131

tcf_website/api/serializers.py (2)

155-164: LGTM!

The serializer correctly flattens the subdepartment relationship for a clean autocomplete response.


175-183: LGTM!

The serializer provides the minimal fields needed for instructor autocomplete.

tcf_website/templates/search/searchbar.html (3)

10-10: LGTM!

The id="search-input" attribute correctly enables the autocomplete JavaScript to target this input element.


204-205: LGTM!

The hidden inputs for weekdays and min_gpa are properly configured with appropriate default values and IDs that match the JavaScript in filters.js.


217-217: LGTM!

The autocomplete script is loaded after the DOM elements are in place, ensuring proper initialization.

Comment thread tcf_website/static/search/autocomplete.js
Comment thread tcf_website/static/search/autocomplete.js Outdated
Comment thread tcf_website/templates/search/searchbar.html Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tcf_website/views/search.py (1)

223-245: Add docstring for get_instructor_results to satisfy pylint

pylint is failing on this function (C0116: missing-function-docstring). Adding even a short descriptive docstring will clear the error and unblock CI.

-@api_view(["GET"])
-def autocomplete(request):
+@api_view(["GET"])
+def autocomplete(request):
@@
-def get_instructor_results(query, limit: int = 10):
+def get_instructor_results(query, limit: int = 10):
+    """Return instructors annotated with trigram similarity, limited to `limit` results."""
♻️ Duplicate comments (1)
tcf_website/static/search/autocomplete.js (1)

1-142: Blocking: fix lint/Prettier violations in autocomplete.js

CI is still red because Prettier/ESLint report whitespace and trailing-comma violations here (see failures at lines 8, 15, 43, 131, 141). Please run the project formatter (e.g., npx prettier --write tcf_website/static/search/autocomplete.js && npx eslint --fix tcf_website/static/search/autocomplete.js) before pushing again so the pipeline can pass.

🧹 Nitpick comments (1)
tcf_website/static/search/autocomplete.js (1)

82-118: Defensive guard before iterating suggestions

Right now we always call .forEach on data.courses/data.instructors, which will throw if either property is ever missing or null (for example, if the backend changes shape or a proxy strips fields). Wrapping the loops in the existing hasCourses/hasInstructors checks keeps the widget resilient.

-    data.courses.forEach((course) => {
-      const item = document.createElement("div");
-      item.classList.add("autocomplete-item");
-      item.style.cursor = "pointer";
-      item.textContent = `${course.subdepartment} ${course.number} — ${course.title}`;
-      item.addEventListener("click", () => {
-        searchInput.value = `${course.subdepartment} ${course.number}`;
-        clearSuggestions();
-        if (searchInput.form) {
-          searchInput.form.submit();
-        }
-      });
-      suggestionsContainer.appendChild(item);
-    });
+    if (hasCourses) {
+      data.courses.forEach((course) => {
+        const item = document.createElement("div");
+        item.classList.add("autocomplete-item");
+        item.style.cursor = "pointer";
+        item.textContent = `${course.subdepartment} ${course.number} — ${course.title}`;
+        item.addEventListener("click", () => {
+          searchInput.value = `${course.subdepartment} ${course.number}`;
+          clearSuggestions();
+          if (searchInput.form) {
+            searchInput.form.submit();
+          }
+        });
+        suggestionsContainer.appendChild(item);
+      });
+    }-    data.instructors.forEach((instructor) => {
-      const item = document.createElement("div");
-      item.classList.add("autocomplete-item");
-      item.style.cursor = "pointer";
-      item.textContent = instructor.full_name;
-      item.addEventListener("click", () => {
-        searchInput.value = instructor.full_name;
-        clearSuggestions();
-        if (searchInput.form) {
-          searchInput.form.submit();
-        }
-      });
-      suggestionsContainer.appendChild(item);
-    });
+    if (hasInstructors) {
+      data.instructors.forEach((instructor) => {
+        const item = document.createElement("div");
+        item.classList.add("autocomplete-item");
+        item.style.cursor = "pointer";
+        item.textContent = instructor.full_name;
+        item.addEventListener("click", () => {
+          searchInput.value = instructor.full_name;
+          clearSuggestions();
+          if (searchInput.form) {
+            searchInput.form.submit();
+          }
+        });
+        suggestionsContainer.appendChild(item);
+      });
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55c057a and c1de925.

📒 Files selected for processing (2)
  • tcf_website/static/search/autocomplete.js (1 hunks)
  • tcf_website/views/search.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tcf_website/views/search.py (2)
tcf_website/static/search/autocomplete.js (2)
  • response (41-44)
  • data (51-51)
tcf_website/api/serializers.py (2)
  • CourseAutocompleteSerializer (155-164)
  • InstructorAutocompleteSerializer (175-183)
🪛 GitHub Actions: Continuous Integration
tcf_website/views/search.py

[error] 223-223: pylint: C0116: Missing function or method docstring (missing-function-docstring)


[error] 223-223: pylint reported a failure for the file due to missing docstring at the specified line.

tcf_website/static/search/autocomplete.js

[error] 8-8: Prettier formatting check failed. Run 'prettier --write' to fix code style issues in this file.

🪛 GitHub Check: eslint
tcf_website/static/search/autocomplete.js

[failure] 141-141:
Insert ,


[failure] 131-131:
Replace !suggestionsContainer.contains(event.target)·&&·event.target·!==·searchInput with ⏎······!suggestionsContainer.contains(event.target)·&&⏎······event.target·!==·searchInput⏎····


[failure] 43-43:
Insert ,


[failure] 15-15:
Delete ··


[failure] 8-8:
Delete ··

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
tcf_website/static/search/autocomplete.js (1)

15-145: Fix remaining ESLint/Prettier formatting violations so CI passes

ESLint/Prettier are still complaining (per hints) around:

  • Line 15: stray spaces on a blank line.
  • Line 43: missing trailing comma in the multi-line fetch call’s arguments.
  • Line 135: long conditional that Prettier wants split across lines.
  • Line 145: missing trailing comma in the multi-line addEventListener call.

The simplest fix is to run the project formatter on this file:

npx prettier --write tcf_website/static/search/autocomplete.js
# and/or, if configured:
npx eslint --fix tcf_website/static/search/autocomplete.js

Then commit the updated file so the CI lint step goes green.

🧹 Nitpick comments (1)
tcf_website/static/search/autocomplete.js (1)

9-14: Avoid silently disabling autocomplete when .searchbar-wrapper is missing

Right now, if searchInput.closest(".searchbar-wrapper") returns null, the script returns and autocomplete is completely disabled without any signal. That’s brittle if the HTML changes or if there are multiple search bars.

Consider either:

  • Falling back to searchInput.parentElement (or another known container), or
  • Logging a warning so it’s obvious in dev tools when the wrapper is missing.

That will make the feature more resilient to template changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1de925 and af88366.

📒 Files selected for processing (3)
  • tcf_website/static/search/autocomplete.js (1 hunks)
  • tcf_website/static/search/searchbar.css (1 hunks)
  • tcf_website/templates/search/searchbar.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tcf_website/templates/search/searchbar.html
🧰 Additional context used
🪛 GitHub Actions: Continuous Integration
tcf_website/static/search/autocomplete.js

[error] 15-15: Prettier formatting check failed in tcf_website/static/search/autocomplete.js. Run 'prettier --write' to fix code style issues.

🪛 GitHub Check: eslint
tcf_website/static/search/autocomplete.js

[failure] 145-145:
Insert ,


[failure] 135-135:
Replace !suggestionsContainer.contains(event.target)·&&·event.target·!==·searchInput with ⏎······!suggestionsContainer.contains(event.target)·&&⏎······event.target·!==·searchInput⏎····


[failure] 43-43:
Insert ,


[failure] 15-15:
Delete ··

🔇 Additional comments (1)
tcf_website/static/search/autocomplete.js (1)

1-147: Autocomplete logic and error handling look solid

Nice job wiring this up: debounced input, AbortController-based cancellation, safe textContent usage, grouped headers, and outside-click handling all look correct and cohesive with the /api/autocomplete/ endpoint. No functional issues stand out in the core flow.

Comment on lines +306 to +360

/* Autocomplete suggestions positioning */
.searchbar-wrapper {
position: relative;
}

/* Remove focus outline from search input */
#search-input:focus {
outline: none !important;
box-shadow: none !important;
border-color: inherit;
}

.autocomplete-suggestions {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 1000;
background: white;
border: 1px solid #e2e8f0;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-top: 0;
max-height: 400px;
overflow: hidden;
display: none;
padding: 0.5rem 0;
}

.autocomplete-header {
padding: 0.75rem 1rem;
font-weight: 600;
font-size: 0.875rem;
color: #4a5568;
background-color: #f7fafc;
border-bottom: 1px solid #e2e8f0;
margin: 0;
}

.autocomplete-item {
padding: 0.75rem 1rem;
cursor: pointer;
border-bottom: 1px solid #f7fafc;
margin: 0;
transition: background-color 0.15s ease;
}

.autocomplete-item:hover {
background-color: #f7fafc;
}

.autocomplete-item:last-child {
border-bottom: none;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restore visible focus indication and allow dropdown scrolling

#search-input:focus removes both outline and box-shadow with !important, which effectively hides focus state for keyboard users and is an accessibility regression. Also, .autocomplete-suggestions uses overflow: hidden, so taller result sets will be clipped instead of scrollable.

Consider:

-/* Remove focus outline from search input */
-#search-input:focus {
-  outline: none !important;
-  box-shadow: none !important;
-  border-color: inherit;
-}
+/* Provide a visible, accessible focus style for the search input */
+#search-input:focus {
+  outline: 2px solid #d75626;
+  outline-offset: 2px;
+}

and:

-  max-height: 400px;
-  overflow: hidden;
+  max-height: 400px;
+  overflow-y: auto;

This keeps the custom look while preserving keyboard focus visibility and avoiding clipped suggestions.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
tcf_website/views/search.py (3)

207-221: Instructor search refactor looks good; consider relocating the threshold comment

Centralizing the instructor similarity query in get_instructor_results and letting fetch_instructors just map to simple dicts is clean and keeps the logic reusable for both the HTML search view and the API.

Minor nit: the # arbitrarily chosen threshold comment now lives in fetch_instructors, but the actual similarity_threshold = 0.5 is defined in get_instructor_results. Moving that comment down next to similarity_threshold (or removing it) would avoid confusion.

Also applies to: 223-246


248-260: Tighten limit handling and double‑check type‑hint compatibility

The normalize_search_query helper is a nice way to make CS3100 match the indexed CS 3100 pattern.

Two small suggestions around limit:

  1. if limit: will silently ignore limit=0 (treating it as “no limit”). Using an explicit is not None check is clearer and avoids that edge case:
-def fetch_courses(query, filters, limit: int | None = None):
+def fetch_courses(query, filters, limit: int | None = None):
@@
-    if limit:
+    if limit is not None:
         results = results[:limit]
  1. The int | None annotation requires Python 3.10+ unless you’re using from __future__ import annotations. If the project targets earlier versions, consider switching to Optional[int] instead (and importing it), or ensuring the runtime is 3.10+.

For example, if you want broader compatibility:

-from typing import Iterable
+from typing import Iterable, Optional
@@
-def fetch_courses(query, filters, limit: int | None = None):
+def fetch_courses(query, filters, limit: Optional[int] = None):
@@
-    if limit:
+    if limit is not None:
         results = results[:limit]

Also applies to: 295-297


352-370: Autocomplete endpoint wiring is correct; consider a minimum query length for load control

The DRF autocomplete view correctly:

  • Reads q from the query string.
  • Short‑circuits to empty results when there’s no input.
  • Uses the shared instructor/course query helpers.
  • Serializes with the new autocomplete serializers.

Given this will likely be called on each keypress, you may want to enforce a minimal query length (e.g., 2–3 characters) before hitting the database, to protect against unnecessary load from very short queries:

-    if not query:
-        return Response({"courses": [], "instructors": []})  # empty list if no input
+    # Avoid DB hits for empty or very short queries; adjust length threshold as needed.
+    if len(query) < 2:
+        return Response({"courses": [], "instructors": []})

This is optional, but can help in production traffic.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af88366 and f3bc963.

📒 Files selected for processing (4)
  • tcf_website/static/search/autocomplete.js (1 hunks)
  • tcf_website/urls.py (2 hunks)
  • tcf_website/views/__init__.py (1 hunks)
  • tcf_website/views/search.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tcf_website/urls.py
  • tcf_website/static/search/autocomplete.js
🧰 Additional context used
🧬 Code graph analysis (2)
tcf_website/views/__init__.py (1)
tcf_website/views/browse.py (1)
  • club_category (498-534)
tcf_website/views/search.py (1)
tcf_website/api/serializers.py (2)
  • CourseAutocompleteSerializer (155-164)
  • InstructorAutocompleteSerializer (175-183)
🔇 Additional comments (2)
tcf_website/views/__init__.py (1)

7-15: Exposing club_category via the views package is consistent and safe

Adding club_category to the browse import block keeps the public tcf_website.views surface consistent with other views and removes duplication without affecting behavior. Change looks good.

tcf_website/views/search.py (1)

8-18: New paginator/DRF/serializer imports align with usage

The added imports (paginator classes, api_view, Response, and the autocomplete serializers) are all used below and match the patterns in the rest of the module. No issues here.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
tcf_website/static/search/autocomplete.js (1)

49-52: Use ES6 property shorthand for cleaner code.

The signal property can use ES6 shorthand syntax since the property name and variable name are identical.

🔎 Proposed fix
      const response = await fetch(
        `/api/autocomplete/?q=${encodeURIComponent(query)}&mode=${mode}`,
-        { signal: signal },
+        { signal },
      );
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3bc963 and a0677da.

📒 Files selected for processing (7)
  • tcf_website/api/serializers.py
  • tcf_website/static/search/autocomplete.js
  • tcf_website/templates/club/mode_toggle.html
  • tcf_website/templates/search/searchbar.html
  • tcf_website/urls.py
  • tcf_website/views/__init__.py
  • tcf_website/views/search.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tcf_website/urls.py
  • tcf_website/views/init.py
🧰 Additional context used
🧬 Code graph analysis (3)
tcf_website/templates/club/mode_toggle.html (1)
tcf_website/static/club/mode_toggle.js (1)
  • updateSearchbarState (25-47)
tcf_website/api/serializers.py (1)
tcf_website/models/models.py (18)
  • Meta (151-160)
  • Meta (190-200)
  • Meta (242-249)
  • Meta (507-524)
  • Meta (580-588)
  • Meta (921-940)
  • Meta (963-966)
  • Meta (991-997)
  • Meta (1049-1060)
  • Meta (1111-1120)
  • Meta (1368-1379)
  • Meta (1408-1418)
  • Meta (1618-1624)
  • Meta (1646-1656)
  • Meta (1678-1688)
  • Course (603-940)
  • Instructor (293-524)
  • Club (219-249)
tcf_website/views/search.py (5)
tcf_website/static/search/autocomplete.js (6)
  • response (49-52)
  • instructors (84-84)
  • mode (46-46)
  • clubs (85-85)
  • data (59-59)
  • courses (83-83)
tcf_website/api/serializers.py (3)
  • CourseAutocompleteSerializer (155-164)
  • InstructorAutocompleteSerializer (175-183)
  • ClubAutocompleteSerializer (204-215)
tcf_website/static/search/filters.js (2)
  • filters (94-102)
  • filters (114-114)
tcf_website/views/browse.py (1)
  • parse_mode (150-153)
tcf_website/views/review.py (1)
  • parse_mode (23-26)
🪛 GitHub Actions: Continuous Integration
tcf_website/views/search.py

[error] 361-361: pylint: W0612 Unused variable 'mode' (unused-variable).

🪛 GitHub Check: eslint
tcf_website/static/search/autocomplete.js

[warning] 51-51:
Expected property shorthand

🪛 Ruff (0.14.10)
tcf_website/api/serializers.py

164-164: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


180-183: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tcf_website/views/search.py

361-361: Unpacked variable mode is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🔇 Additional comments (6)
tcf_website/templates/search/searchbar.html (1)

5-212: LGTM! Searchbar restructuring properly supports autocomplete.

The searchbar has been successfully restructured to accommodate the autocomplete feature:

  • Added searchbar-wrapper container for proper positioning of the dropdown
  • Added id="search-input" for the autocomplete script hook
  • Integrated mode toggle and search button inline
  • Included autocomplete.js script reference

The layout changes align well with the autocomplete requirements and maintain proper form structure.

tcf_website/api/serializers.py (1)

155-215: LGTM! Autocomplete serializers are well-designed.

The three new autocomplete serializers are properly implemented:

  • CourseAutocompleteSerializer: Exposes minimal fields (id, title, number, subdepartment mnemonic) needed for autocomplete suggestions
  • InstructorAutocompleteSerializer: Returns id and full_name for instructor suggestions
  • ClubAutocompleteSerializer: Provides id, name, and category_name for club suggestions

The use of source parameter to access related model fields (subdepartment.mnemonic, category.name) is correct and efficient.

tcf_website/views/search.py (4)

155-196: LGTM! fetch_clubs properly implements limit parameter.

The addition of the limit parameter and its application after building the queryset is correct. The threshold-based filtering is preserved, and the limit is applied at the end as expected for autocomplete results.


212-250: LGTM! Instructor fetching refactored with shared helper.

The refactoring to extract get_instructor_results is a good improvement:

  • Reduces code duplication between fetch_instructors and the autocomplete endpoint
  • Maintains the same trigram similarity logic
  • Properly applies the limit parameter

253-303: LGTM! fetch_courses properly implements limit parameter.

The addition of the limit parameter is correctly applied after filtering and ordering, which ensures:

  • Search normalization is still performed
  • Filters are applied before limiting
  • Similarity threshold is respected
  • Results are ordered by relevance before truncation

357-382: Verify autocomplete endpoint behavior and consider empty clubs response.

The autocomplete endpoint implementation looks solid overall. However, there's a minor inconsistency: when in club mode (line 368-370), the response only includes clubs, but the frontend autocomplete.js expects all three keys (courses, instructors, clubs) based on lines 73-75 and 82-84.

While the frontend uses optional chaining and defaults to empty arrays (data?.courses?.length > 0, data.courses || []), it would be more consistent to always return all three keys for predictable API behavior.

Consider updating the club mode response for consistency:

     if is_club:
         clubs = fetch_clubs(query, limit=5)
-        return Response({"clubs": ClubAutocompleteSerializer(clubs, many=True).data})
+        return Response({
+            "courses": [],
+            "instructors": [],
+            "clubs": ClubAutocompleteSerializer(clubs, many=True).data
+        })

Alternatively, verify that the frontend properly handles partial responses in all cases.



class ClubAutocompleteSerializer(serializers.ModelSerializer):
"""DEF Serializer for Club autocomplete"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in docstring.

The docstring has "DEF Serializer" but should be "DRF Serializer" (Django REST Framework).

🔎 Proposed fix
-    """DEF Serializer for Club autocomplete"""
+    """DRF Serializer for Club autocomplete"""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"""DEF Serializer for Club autocomplete"""
"""DRF Serializer for Club autocomplete"""
🤖 Prompt for AI Agents
In tcf_website/api/serializers.py around line 205, the docstring contains a typo
"DEF Serializer for Club autocomplete"; update the docstring to read "DRF
Serializer for Club autocomplete" to correctly reference Django REST Framework
and save the file.

Comment thread tcf_website/templates/club/mode_toggle.html
Comment thread tcf_website/views/search.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tcf_website/views/search.py (2)

155-197: Apply limit to queryset before materialization for better performance.

The limit is currently applied at line 195 after the queryset has been fully evaluated and converted to a list of dictionaries (lines 182-192). For autocomplete with limit=5, this means fetching and processing all matching clubs from the database, then discarding most of them.

🔎 Proposed refactor to apply limit earlier
 def fetch_clubs(query, limit: int | None = None):
     """Get club data using Django Trigram similarity."""
     threshold = 0.15
     if not query:
-        return list(
-            Club.objects.annotate(
+        qs = Club.objects.annotate(
                 max_similarity=Value(1.0, output_field=FloatField()),
                 category_slug=F("category__slug"),
                 category_name=F("category__name"),
-            ).values(
+            )
+        if limit:
+            qs = qs[:limit]
+        return list(
+            qs.values(
                 "id",
                 "name",
                 "description",
                 "max_similarity",
                 "category_slug",
                 "category_name",
             )
         )
 
     qs = (
         Club.objects.annotate(sim=TrigramSimilarity("combined_name", query))
         .annotate(max_similarity=F("sim"))
         .filter(max_similarity__gte=threshold)
         .annotate(category_slug=F("category__slug"), category_name=F("category__name"))
         .order_by("-max_similarity")
     )
+    
+    if limit:
+        qs = qs[:limit]
 
     qs = [
         {
             "id": c.id,
             "name": c.name,
             "description": c.description,
             "max_similarity": c.max_similarity,
             "category_slug": c.category_slug,
             "category_name": c.category_name,
         }
         for c in qs
     ]
 
-    if limit:
-        qs = qs[:limit]
     return qs

357-382: Consider adding error handling for production robustness.

The autocomplete endpoint currently has no error handling for database exceptions, serialization errors, or other potential failures. While DRF provides some default error handling, explicitly catching and logging exceptions would improve debuggability and allow for more graceful degradation.

💡 Example error handling pattern
@api_view(["GET"])
def autocomplete(request):
    """implement autocomplete for search bar"""
    try:
        query = request.GET.get("q", "").strip()
        _, is_club = parse_mode(request)

        if not query:
            return Response(
                {"courses": [], "instructors": [], "clubs": []}
            )

        if is_club:
            clubs = fetch_clubs(query, limit=5)
            return Response({"clubs": ClubAutocompleteSerializer(clubs, many=True).data})

        instructors = get_instructor_results(query, limit=5)
        courses = fetch_courses(query, filters={}, limit=5)

        return Response(
            {
                "courses": CourseAutocompleteSerializer(courses, many=True).data,
                "instructors": InstructorAutocompleteSerializer(
                    instructors, many=True
                ).data,
            }
        )
    except Exception as e:
        # Log the error for debugging
        logger.exception("Autocomplete error for query: %s", query)
        # Return empty results rather than 500 error
        return Response(
            {"courses": [], "instructors": [], "clubs": []},
            status=200
        )

Note: You'll need to add import logging and logger = logging.getLogger(__name__) at the top of the file.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0677da and 2b51f2a.

📒 Files selected for processing (1)
  • tcf_website/views/search.py
🔇 Additional comments (4)
tcf_website/views/search.py (4)

12-19: LGTM! DRF integration looks correct.

The imports for Django REST Framework decorators, Response, and the new autocomplete serializers are properly structured for the new API endpoint.


212-226: LGTM! Clean refactoring.

The extraction of query logic into get_instructor_results and the use of a dict comprehension with getattr makes the code more maintainable and testable.


228-250: LGTM! Efficient implementation.

The use of .only() to fetch minimal fields and applying the limit via queryset slicing ensures the database query includes a SQL LIMIT clause, making this efficient for autocomplete.


253-303: LGTM! Limit applied correctly.

The limit is applied to the queryset after filtering and ordering (lines 300-301), which ensures Django translates it to a SQL LIMIT clause for efficient autocomplete queries.

Comment on lines +357 to +382
@api_view(["GET"])
def autocomplete(request):
"""implement autocomplete for search bar"""
query = request.GET.get("q", "").strip()
_, is_club = parse_mode(request)

if not query:
return Response(
{"courses": [], "instructors": [], "clubs": []}
) # empty list if no input

if is_club:
clubs = fetch_clubs(query, limit=5)
return Response({"clubs": ClubAutocompleteSerializer(clubs, many=True).data})

instructors = get_instructor_results(query, limit=5)
courses = fetch_courses(query, filters={}, limit=5)

return Response(
{
"courses": CourseAutocompleteSerializer(courses, many=True).data,
"instructors": InstructorAutocompleteSerializer(
instructors, many=True
).data,
}
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix inconsistent response structure across modes.

The autocomplete endpoint returns different response structures depending on mode:

  • Empty query (lines 364-366): {"courses": [], "instructors": [], "clubs": []}
  • Club mode (line 370): {"clubs": [...]}
  • Course mode (lines 375-381): {"courses": [...], "instructors": [...]}

This inconsistency forces clients to handle different response shapes, complicating frontend logic and increasing the likelihood of bugs. For example, accessing response.courses in club mode would fail.

🔎 Proposed fix for consistent response structure
     if is_club:
         clubs = fetch_clubs(query, limit=5)
-        return Response({"clubs": ClubAutocompleteSerializer(clubs, many=True).data})
+        return Response({
+            "courses": [],
+            "instructors": [],
+            "clubs": ClubAutocompleteSerializer(clubs, many=True).data
+        })
 
     instructors = get_instructor_results(query, limit=5)
     courses = fetch_courses(query, filters={}, limit=5)
 
     return Response(
         {
             "courses": CourseAutocompleteSerializer(courses, many=True).data,
             "instructors": InstructorAutocompleteSerializer(
                 instructors, many=True
             ).data,
+            "clubs": [],
         }
     )
🤖 Prompt for AI Agents
In tcf_website/views/search.py around lines 357 to 382, the autocomplete
endpoint returns different response shapes per mode; change it to always return
the same keys ("courses", "instructors", "clubs") so clients can rely on a
consistent structure. Specifically, when query is empty return {"courses": [],
"instructors": [], "clubs": []}; when is_club is True return {"clubs":
ClubAutocompleteSerializer(clubs, many=True).data, "courses": [], "instructors":
[]}; and when course/instructor mode return the current {"courses":
CourseAutocompleteSerializer(...).data, "instructors":
InstructorAutocompleteSerializer(...).data, "clubs": []}. Ensure serializers are
used for present lists and empty lists are plain [].

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
tcf_website/api/serializers.py (1)

205-205: Fix typo in docstring.

The docstring contains "DEF Serializer" but should be "DRF Serializer" (Django REST Framework).

🔎 Proposed fix
-    """DEF Serializer for Club autocomplete"""
+    """DRF Serializer for Club autocomplete"""
tcf_website/views/search.py (1)

357-382: Fix inconsistent response structure across modes.

The autocomplete endpoint returns different response structures depending on mode:

  • Empty query: {"courses": [], "instructors": [], "clubs": []}
  • Club mode: {"clubs": [...]}
  • Course mode: {"courses": [...], "instructors": [...]}

This inconsistency forces clients to handle different response shapes, complicating frontend logic and increasing the likelihood of bugs (e.g., accessing response.courses in club mode would fail).

🔎 Proposed fix for consistent response structure
     if is_club:
         clubs = fetch_clubs(query, limit=5)
-        return Response({"clubs": ClubAutocompleteSerializer(clubs, many=True).data})
+        return Response({
+            "courses": [],
+            "instructors": [],
+            "clubs": ClubAutocompleteSerializer(clubs, many=True).data
+        })
 
     instructors = get_instructor_results(query, limit=5)
     courses = fetch_courses(query, filters={}, limit=5)
 
     return Response(
         {
             "courses": CourseAutocompleteSerializer(courses, many=True).data,
             "instructors": InstructorAutocompleteSerializer(
                 instructors, many=True
             ).data,
+            "clubs": [],
         }
     )
🧹 Nitpick comments (1)
tcf_website/static/search/autocomplete.js (1)

49-52: Use ES6 property shorthand.

Line 51 can use ES6 property shorthand syntax for cleaner code.

🔎 Proposed refactor
       const response = await fetch(
         `/api/autocomplete/?q=${encodeURIComponent(query)}&mode=${mode}`,
-        { signal: signal },
+        { signal },
       );
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53cd8c2 and e65d41f.

📒 Files selected for processing (8)
  • tcf_website/api/serializers.py
  • tcf_website/static/search/autocomplete.js
  • tcf_website/static/search/searchbar.css
  • tcf_website/templates/club/mode_toggle.html
  • tcf_website/templates/search/searchbar.html
  • tcf_website/urls.py
  • tcf_website/views/__init__.py
  • tcf_website/views/search.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tcf_website/static/search/searchbar.css
  • tcf_website/urls.py
🧰 Additional context used
🧬 Code graph analysis (3)
tcf_website/views/__init__.py (2)
tcf_website/views/browse.py (1)
  • club_category (498-534)
tcf_website/views/search.py (2)
  • search (77-152)
  • autocomplete (358-382)
tcf_website/views/search.py (3)
tcf_website/api/serializers.py (3)
  • CourseAutocompleteSerializer (155-164)
  • InstructorAutocompleteSerializer (175-183)
  • ClubAutocompleteSerializer (204-215)
tcf_website/views/browse.py (1)
  • parse_mode (150-153)
tcf_website/views/review.py (1)
  • parse_mode (23-26)
tcf_website/api/serializers.py (1)
tcf_website/models/models.py (18)
  • Meta (151-160)
  • Meta (190-200)
  • Meta (242-249)
  • Meta (507-524)
  • Meta (580-588)
  • Meta (921-940)
  • Meta (963-966)
  • Meta (991-997)
  • Meta (1049-1060)
  • Meta (1111-1120)
  • Meta (1368-1379)
  • Meta (1408-1418)
  • Meta (1618-1624)
  • Meta (1646-1656)
  • Meta (1678-1688)
  • Course (603-940)
  • Instructor (293-524)
  • Club (219-249)
🪛 GitHub Check: eslint
tcf_website/static/search/autocomplete.js

[warning] 51-51:
Expected property shorthand

🪛 Ruff (0.14.10)
tcf_website/api/serializers.py

164-164: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


180-183: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (10)
tcf_website/templates/club/mode_toggle.html (1)

16-20: LGTM! Previous duplicate parameter issue resolved.

The hidden input now correctly uses only the id attribute without a name attribute, avoiding the duplicate form parameter issue while still providing JavaScript access to the current mode value.

tcf_website/views/__init__.py (1)

9-9: LGTM! Clean API surface expansion.

The new exports (club_category and autocomplete) correctly expose the newly added view functions for public use.

Also applies to: 42-42

tcf_website/templates/search/searchbar.html (1)

5-212: LGTM! Well-structured autocomplete integration.

The template changes properly support the new autocomplete feature by adding the necessary wrapper, input ID, hidden fields, and script inclusion while maintaining the existing filter functionality.

tcf_website/api/serializers.py (2)

155-165: LGTM! Correct serializer implementation.

The CourseAutocompleteSerializer appropriately exposes the minimal fields needed for autocomplete and correctly uses source="subdepartment.mnemonic" to access the related subdepartment's mnemonic.


175-184: LGTM! Simple and correct serializer.

The InstructorAutocompleteSerializer correctly exposes only the essential fields (id and full_name) needed for instructor autocomplete.

tcf_website/views/search.py (5)

12-19: LGTM! Clean import additions.

The new imports for DRF decorators, Response, and autocomplete serializers are properly structured to support the new API endpoint.


155-196: LGTM! Clean limit support added.

The fetch_clubs function correctly adds optional limit parameter while maintaining backward compatibility for existing callers.


212-226: LGTM! Good refactoring for code reuse.

The fetch_instructors refactor appropriately delegates to the new get_instructor_results helper, enabling code reuse for the autocomplete endpoint while maintaining the existing return structure.


228-251: LGTM! Well-implemented search helper.

The get_instructor_results helper correctly computes trigram similarity across multiple name fields, applies the limit efficiently in the queryset, and returns results suitable for both autocomplete and search use cases.


253-303: LGTM! Backward-compatible limit support.

The fetch_courses function properly adds optional limit parameter while preserving existing behavior for callers that don't specify a limit.

Comment on lines +82 to +85
const MAX_RESULTS = 8;
const courses = (data.courses || []).slice(0, MAX_RESULTS);
const instructors = (data.instructors || []).slice(0, MAX_RESULTS);
const clubs = (data.clubs || []).slice(0, MAX_RESULTS);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we make this dynamic based on how big their screen is?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
tcf_website/management/commands/load_semester.py (1)

10-10: Consider explicit imports instead of star import.

The static analysis warning (F405) flags that Section may be undefined because it's imported via from tcf_website.models import *. While this works at runtime, explicit imports improve readability and help static analysis tools.

♻️ Suggested fix
-from tcf_website.models import *
+from tcf_website.models import (
+    Course,
+    Department,
+    Discipline,
+    Instructor,
+    School,
+    Section,
+    SectionTime,
+    Semester,
+    Subdepartment,
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/management/commands/load_semester.py` at line 10, Replace the
wildcard import with explicit model names: change "from tcf_website.models
import *" to import the specific models used in this command (at minimum
Section, and also include any other models referenced in this file such as
Course, Semester, Instructor, etc.). Update the import line to list these model
classes explicitly so static analysis (F405) can resolve symbols like Section
and improve readability.
doc/semester-data.md (1)

17-17: Add a warning about cascade deletion of user data.

The documentation states data will be deleted but doesn't mention that ScheduledCourse (user schedules) will also be cascade-deleted. Operators should be warned before running this command on production.

📝 Suggested addition
 Delete existing semester data (if exists) and load new data from csv into database:
+
+⚠️ **Warning**: This deletes all Section records for the semester, which cascades to delete related user schedules (ScheduledCourse) and section times.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/semester-data.md` at line 17, Update doc/semester-data.md to add a clear
warning that deleting existing semester data will cascade-delete user schedule
records; explicitly mention the ScheduledCourse model (user schedules) and
advise operators to NOT run the "Delete existing semester data (if exists) and
load new data from csv into database" command on production without backups and
confirmation. Place the warning directly after the existing sentence about
deletion, include recommended mitigations (backup DB, notify users, run in
maintenance window), and reference ScheduledCourse so operators know which user
data will be affected.
tcf_website/management/commands/load_grades.py (1)

179-183: Hardcoded name mappings are brittle—consider extracting to a configuration.

Embedding specific instructor name remaps inline will require code changes each time a new mismatch is discovered. Consider extracting these to a constant dictionary at the top of the file or an external configuration file.

♻️ Suggested refactor
+# Mapping of (last_name, first_and_middle) -> corrected first_name
+# Used when grade data names don't match instructor records (nicknames, etc.)
+INSTRUCTOR_NAME_OVERRIDES = {
+    ("Hott", "John Robert"): "Robbie",
+    ("Nguyen", "Nhat H"): "Rich",
+}
+
 class Command(BaseCommand):

Then in the parsing logic:

             last_name, first_and_middle = row["Primary Instructor Name"].split(",")
             first_name = first_and_middle.split(" ")[0]
 
-            if last_name == "Hott" and first_and_middle == "John Robert":
-                first_name = "Robbie"
-            elif last_name == "Nguyen" and first_and_middle == "Nhat H":
-                first_name = "Rich"
+            # Apply name overrides for known mismatches
+            override_key = (last_name, first_and_middle)
+            if override_key in INSTRUCTOR_NAME_OVERRIDES:
+                first_name = INSTRUCTOR_NAME_OVERRIDES[override_key]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/management/commands/load_grades.py` around lines 179 - 183,
Replace the brittle hardcoded if/elif name remaps with a lookup table: add a
module-level constant (e.g., INSTRUCTOR_NAME_OVERRIDES = {("Hott","John
Robert"): "Robbie", ("Nguyen","Nhat H"): "Rich"}) near the top of
load_grades.py, and in the parsing logic (where the current if/elif lives)
compute a key from last_name and first_and_middle and set first_name =
INSTRUCTOR_NAME_OVERRIDES.get(key, first_name); optionally normalize/strip the
name parts before lookup to preserve existing behavior.
doc/grade-data.md (1)

8-8: Clarify why fetch_grades.py is stored in Google Drive rather than the repository.

Storing a required script externally (in Google Drive) creates friction for new contributors and risks the script becoming out of sync or inaccessible. If there's a specific reason (e.g., contains credentials, too large), it would help to document that reason here. Otherwise, consider committing the script to the repository.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/grade-data.md` at line 8, The README entry saying "`fetch_grades.py` must
be in *root* of repository" is unclear about why the script is stored in Google
Drive; update doc/grade-data.md to state the specific reason (e.g., contains
credentials, large binary, legal/privacy restriction, or temporary external
hosting) and the recommended developer workflow (e.g., commit the script to
repo, add a placeholder stub, provide instructions to obtain from Google Drive,
or add a secure storage/credentials rotation note). Reference the script name
fetch_grades.py and include exact action items: either (1) commit
fetch_grades.py to the repo if safe, (2) if it contains secrets, add a
scrubbing/credential-free version and document how to supply credentials (env
vars/secret manager), or (3) if too large, add it to LFS and mention that in
doc/grade-data.md with download steps; ensure the doc includes a short sentence
about where to find the current copy, who maintains it, and how to update it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@doc/grade-data.md`:
- Line 19: The link in doc/grade-data.md currently points to
"useful-commands.py" which is incorrect; update the URL in the text "See
**Updating Grades** in [useful-commands.py](...)" to use the correct file
extension ".md" (i.e., change "useful-commands.py" to "useful-commands.md") so
the hyperlink points to the existing markdown file.

In `@tcf_website/management/commands/load_semester.py`:
- Around line 78-80: The current deletion of Section objects causes cascading
removal of ScheduledCourse and SectionTime; instead preserve and reattach user
schedules: before calling Section.objects.filter(semester=semester).delete(),
query and stash a mapping of existing ScheduledCourse objects keyed by their
Section.sis_section_number (and primary keys) then delete old
Section/SectionTime rows, create the new Section records, and reassign each
saved ScheduledCourse.section to the newly created Section that matches on
sis_section_number (handle missing matches by leaving section null or logging),
all inside a transaction; reference the Section, ScheduledCourse, SectionTime
models and the sis_section_number field when implementing the backup/reassociate
logic.

---

Nitpick comments:
In `@doc/grade-data.md`:
- Line 8: The README entry saying "`fetch_grades.py` must be in *root* of
repository" is unclear about why the script is stored in Google Drive; update
doc/grade-data.md to state the specific reason (e.g., contains credentials,
large binary, legal/privacy restriction, or temporary external hosting) and the
recommended developer workflow (e.g., commit the script to repo, add a
placeholder stub, provide instructions to obtain from Google Drive, or add a
secure storage/credentials rotation note). Reference the script name
fetch_grades.py and include exact action items: either (1) commit
fetch_grades.py to the repo if safe, (2) if it contains secrets, add a
scrubbing/credential-free version and document how to supply credentials (env
vars/secret manager), or (3) if too large, add it to LFS and mention that in
doc/grade-data.md with download steps; ensure the doc includes a short sentence
about where to find the current copy, who maintains it, and how to update it.

In `@doc/semester-data.md`:
- Line 17: Update doc/semester-data.md to add a clear warning that deleting
existing semester data will cascade-delete user schedule records; explicitly
mention the ScheduledCourse model (user schedules) and advise operators to NOT
run the "Delete existing semester data (if exists) and load new data from csv
into database" command on production without backups and confirmation. Place the
warning directly after the existing sentence about deletion, include recommended
mitigations (backup DB, notify users, run in maintenance window), and reference
ScheduledCourse so operators know which user data will be affected.

In `@tcf_website/management/commands/load_grades.py`:
- Around line 179-183: Replace the brittle hardcoded if/elif name remaps with a
lookup table: add a module-level constant (e.g., INSTRUCTOR_NAME_OVERRIDES =
{("Hott","John Robert"): "Robbie", ("Nguyen","Nhat H"): "Rich"}) near the top of
load_grades.py, and in the parsing logic (where the current if/elif lives)
compute a key from last_name and first_and_middle and set first_name =
INSTRUCTOR_NAME_OVERRIDES.get(key, first_name); optionally normalize/strip the
name parts before lookup to preserve existing behavior.

In `@tcf_website/management/commands/load_semester.py`:
- Line 10: Replace the wildcard import with explicit model names: change "from
tcf_website.models import *" to import the specific models used in this command
(at minimum Section, and also include any other models referenced in this file
such as Course, Semester, Instructor, etc.). Update the import line to list
these model classes explicitly so static analysis (F405) can resolve symbols
like Section and improve readability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61f12937-c5ec-4dfd-954a-a200c3833ebe

📥 Commits

Reviewing files that changed from the base of the PR and between e65d41f and 99bd7e8.

⛔ Files ignored due to path filters (6)
  • tcf_website/management/commands/grade_data/csv/2025_fall.csv is excluded by !**/*.csv
  • tcf_website/management/commands/grade_data/csv/2025_january.csv is excluded by !**/*.csv
  • tcf_website/management/commands/grade_data/csv/2025_spring.csv is excluded by !**/*.csv
  • tcf_website/management/commands/grade_data/csv/2025_summer.csv is excluded by !**/*.csv
  • tcf_website/management/commands/semester_data/csv/2026_fall.csv is excluded by !**/*.csv
  • tcf_website/management/commands/semester_data/csv/2026_spring.csv is excluded by !**/*.csv
📒 Files selected for processing (8)
  • doc/grade-data.md
  • doc/semester-data.md
  • doc/useful-commands.md
  • scripts/container-startup.sh
  • tcf_website/management/commands/load_grades.py
  • tcf_website/management/commands/load_semester.py
  • tcf_website/urls.py
  • tcf_website/views/__init__.py
✅ Files skipped from review due to trivial changes (2)
  • scripts/container-startup.sh
  • tcf_website/views/init.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tcf_website/urls.py

Comment thread doc/grade-data.md

## Updating Grade Data

See **Updating Grades** in [useful-commands.py](https://github.com/thecourseforum/theCourseForum2/blob/dev/doc/useful-commands.md)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Broken link: file extension should be .md, not .py.

The link references useful-commands.py but the actual file is useful-commands.md.

🔧 Proposed fix
-See **Updating Grades** in [useful-commands.py](https://github.com/thecourseforum/theCourseForum2/blob/dev/doc/useful-commands.md)
+See **Updating Grades** in [useful-commands.md](https://github.com/thecourseforum/theCourseForum2/blob/dev/doc/useful-commands.md)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
See **Updating Grades** in [useful-commands.py](https://github.com/thecourseforum/theCourseForum2/blob/dev/doc/useful-commands.md)
See **Updating Grades** in [useful-commands.md](https://github.com/thecourseforum/theCourseForum2/blob/dev/doc/useful-commands.md)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@doc/grade-data.md` at line 19, The link in doc/grade-data.md currently points
to "useful-commands.py" which is incorrect; update the URL in the text "See
**Updating Grades** in [useful-commands.py](...)" to use the correct file
extension ".md" (i.e., change "useful-commands.py" to "useful-commands.md") so
the hyperlink points to the existing markdown file.

Comment on lines +78 to +80
print(f"Deleting existing sections for {semester}...")
Section.objects.filter(semester=semester).delete()
print(f"Loading new data for {semester}...")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all models with ForeignKey to Section with CASCADE delete
rg -n "ForeignKey.*Section.*CASCADE" --type py

Repository: thecourseforum/theCourseForum2

Length of output: 277


🏁 Script executed:

#!/bin/bash
# Check lines around 1069 and 1959 to identify the models
echo "=== Checking line 1069 context ==="
sed -n '1055,1075p' tcf_website/models/models.py | cat -n

echo -e "\n=== Checking line 1959 context ==="
sed -n '1945,1975p' tcf_website/models/models.py | cat -n

echo -e "\n=== Checking load_semester.py context around line 79 ==="
sed -n '70,90p' tcf_website/management/commands/load_semester.py | cat -n

echo -e "\n=== Checking imports in load_semester.py ==="
head -20 tcf_website/management/commands/load_semester.py | cat -n

Repository: thecourseforum/theCourseForum2

Length of output: 3376


Cascade deletion will destroy user schedule data.

When Section.objects.filter(semester=semester).delete() executes, Django's cascade behavior automatically deletes all related ScheduledCourse records (user-saved schedules) and SectionTime records. This means users' saved course schedules are permanently destroyed on every semester data refresh—unintended data loss.

Consider one of these approaches:

  1. Preserve ScheduledCourse data by reassociating with newly created sections (matching by sis_section_number)
  2. Use models.SET_NULL or store ScheduledCourse separately before deletion
  3. Add explicit handling to back up or migrate user schedules before deletion
🧰 Tools
🪛 Ruff (0.15.7)

[error] 79-79: Section may be undefined, or defined from star imports

(F405)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/management/commands/load_semester.py` around lines 78 - 80, The
current deletion of Section objects causes cascading removal of ScheduledCourse
and SectionTime; instead preserve and reattach user schedules: before calling
Section.objects.filter(semester=semester).delete(), query and stash a mapping of
existing ScheduledCourse objects keyed by their Section.sis_section_number (and
primary keys) then delete old Section/SectionTime rows, create the new Section
records, and reassign each saved ScheduledCourse.section to the newly created
Section that matches on sis_section_number (handle missing matches by leaving
section null or logging), all inside a transaction; reference the Section,
ScheduledCourse, SectionTime models and the sis_section_number field when
implementing the backup/reassociate logic.

@artiehumphreys
Copy link
Copy Markdown
Member

CPU Timing with parallelized DB requests:
Screenshot 2026-03-29 at 2 37 19 PM

CPU timing with sequential accesses. Note total elapsed time as the SQL requests take ~80ms.
Screenshot 2026-03-29 at 2 35 25 PM

@artiehumphreys artiehumphreys self-assigned this Mar 29, 2026
@pelin-sayar pelin-sayar changed the title autocomplete so far, needs html fixes autocomplete feature for search Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants