Skip to content

ClubForum Calendar event list view#1178

Open
R0hit-0 wants to merge 36 commits intodevfrom
ClubForum+-Calender
Open

ClubForum Calendar event list view#1178
R0hit-0 wants to merge 36 commits intodevfrom
ClubForum+-Calender

Conversation

@R0hit-0
Copy link
Collaborator

@R0hit-0 R0hit-0 commented Oct 26, 2025

What I did

  • Created list view for Calendar events.
  • Pulled data from Presence.io virginia events
  • Events are listed by date and users can view the given description of the event

Screenshots

Screenshot 2025-10-26 at 12 47 48 PM

Testing

  • Loaded data from virginia Presence.io events and tested event descriptions and data tags

Summary by CodeRabbit

  • New Features

    • Calendar page with tag filtering and event detail pages; calendar link in sidebar
    • Course page: server-rendered sections, improved enrollment/waitlist display, "Show more" for sections
    • Redesigned review submission flow and inline word-count/duplicate checks
    • Ad-block detection modal shown when ads fail to load
    • Added a new engineering team member entry
  • Bug Fixes

    • Guarded semester handling to avoid failures when no latest semester exists
  • Documentation

    • Dev docs updated with guidance for loading full data locally

@R0hit-0 R0hit-0 requested a review from Jay-Lalwani October 26, 2025 16:52
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

📝 Walkthrough

Walkthrough

Adds a calendar feature and Presence integration: new Presence service, calendar views/templates/URLs, tag coloring, settings for Presence, caching/retry logic, tests, and many UI/script refactors including removal of legacy API serializers/views and several client-side scripts and templates.

Changes

Cohort / File(s) Summary
Presence & Settings
tcf_core/settings/base.py, tcf_core/settings/dev.py, tcf_core/context_processors.py
Added PRESENCE_* settings (subdomain, timeout, cache TTL) and a guard in searchbar_context for when latest_semester is None; dev DB helper functions and local DB defaults introduced.
Presence Service
tcf_website/services/presence.py
New Presence API client with shared session, exponential-backoff retries, namespaced cache keys, TTL-controlled caching, and public functions get_events() and get_event_details().
Calendar Feature
tcf_website/views/calendar.py, tcf_website/urls.py, tcf_website/views/__init__.py
New calendar views (calendar_overview, event_detail), routing for calendar pages, exports added, event normalization/grouping/formatting and dev fallbacks implemented.
Templates & UI
tcf_website/templates/calendar/*, tcf_website/templates/base/sidebar.html, tcf_website/templates/common/adblock_modal.html, tcf_website/templates/base/index.html, tcf_website/templates/browse/browse.html, tcf_website/templates/course/course_professor.html, tcf_website/templates/reviews/*, tcf_website/templates/qa/qa.html
Added calendar overview and detail templates, sidebar calendar link, adblock modal and deferred AdSense load with onerror, conditional browse rendering, server-rendered sections and review form refactors; many template adjustments for new flows.
Template Tags
tcf_website/templatetags/custom_tags.py
Added tag_color(tag_name) filter with special-case mapping and deterministic hash fallback to Bootstrap color classes.
Tests
tcf_website/tests/test_calendar_overview.py, tcf_website/tests/test_event_detail.py, other test updates
Added tests for calendar overview sorting and event-detail 404 behavior; several API-related tests removed/updated.
Removed API Layer
tcf_website/api/serializers.py, tcf_website/api/views.py, tcf_website/api/urls.py
Deleted DRF serializers, viewsets, and API URL routing — removes REST API surface and related viewset logic. Attention: large functional removal.
Static JS/CSS & Frontend Behavior Removals
tcf_website/static/* (instr_data.js, new_answer.js, new_review.js, club_review.js, sync_review_sliders.js, course_professor.css, reviews/new_review.css, etc.)
Removed multiple legacy client-side scripts that powered cascading dropdowns, duplicates checks, review sliders, and instructor lazy-loading; replaced or reworked several CSS/JS behaviors and templates to server-render or use new fetch-based flows.
Data Migration & Other Ops
tcf_website/migrations/0024_seed_browse_schools.py, docker-compose.yml, doc/dev.md
Added migration to seed two School rows, adjusted docker-compose env and defaults, and documented dev instructions for loading full data.
Misc
tcf_website/views/review.py, tcf_website/views/browse.py, tcf_website/views/team_info.json, other view adjustments
Refactored review/new_review flow, guarded department lookup, server-populated sections, added new team member JSON entry, and adjusted imports/exports in views.

Sequence Diagram

sequenceDiagram
    actor User
    participant View as Django View
    participant Service as Presence Service
    participant Cache as Django Cache
    participant API as Presence API

    User->>View: GET /clubs/calendar/
    activate View
    View->>Service: get_events()
    activate Service
    Service->>Cache: Check cache (presence::{subdomain}::events)
    alt Cache Hit
        Cache-->>Service: Return cached events
    else Cache Miss
        Service->>API: HTTP GET /events (with timeout & retries)
        activate API
        API-->>Service: JSON events
        deactivate API
        Service->>Cache: Store events (TTL)
    end
    Service-->>View: Events list
    deactivate Service
    View->>View: Normalize, sort, group by date, split upcoming/past
    View-->>User: Render calendar_overview.html
    deactivate View

    User->>View: GET /clubs/calendar/event/{event_uri}/
    activate View
    View->>Service: get_event_details(event_uri)
    activate Service
    Service->>Cache: Check cache (presence::{subdomain}::event::{uri})
    alt Cache Hit
        Cache-->>Service: Return cached details
    else Cache Miss
        Service->>API: HTTP GET /events/{uri}
        activate API
        API-->>Service: JSON event detail
        deactivate API
        Service->>Cache: Store event detail (TTL)
    end
    Service-->>View: Event detail or None
    deactivate Service
    alt Event Found
        View-->>User: Render event_detail.html
    else Event Not Found
        View-->>User: HTTP 404
    end
    deactivate View
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ajnye

Poem

🐰 Hopped in with a calendar tune,

Fetching events by sun and moon,
Tags in color, past tucked away,
Cached and retried to save the day —
A little hare brings events to play. 🗓️✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides what was done and includes a screenshot, but is missing sections for GitHub issues and does not follow the template structure. Add GitHub Issues addressed section, expand testing details, and follow the provided template structure more closely.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a calendar event list view for ClubForum.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ClubForum+-Calender

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
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: 13

🧹 Nitpick comments (4)
tcf_website/services/presence.py (1)

22-41: Consider documenting exception behavior.

The functions get_events() and get_event_details() will raise exceptions (via _get) if the API is unreachable after retries or returns HTTP errors. Consider adding docstring notes about what exceptions callers should expect to handle.

Example docstring addition:

def get_events():
    """
    Returns a list of upcoming events from Presence. Cached briefly to avoid rate limits.
    
    Raises:
        requests.RequestException: If the API is unreachable after retries
        requests.HTTPError: If the API returns an error status code
    """
tcf_website/tests/test_calendar_overview.py (2)

6-8: Use Django's override_settings decorator instead of direct mutation.

Directly mutating settings.ENABLE_CLUB_CALENDAR can cause issues in test isolation and doesn't follow Django testing best practices.

Apply this diff:

+from django.test import TestCase, Client, override_settings
-from django.test import TestCase, Client
 from django.conf import settings


+@override_settings(ENABLE_CLUB_CALENDAR=True)
 class CalendarOverviewTests(TestCase):
+    """Test cases for the calendar overview view."""
+    
     def setUp(self):
-        settings.ENABLE_CLUB_CALENDAR = True
         self.client = Client()

10-12: Consider asserting a specific expected status code.

The test currently accepts both 200 and 302, which makes it less precise. If the calendar should always return 200 when enabled (or always redirect), consider asserting the specific expected status code for clearer test semantics.

tcf_website/templates/calendar/calendar_overview.html (1)

39-119: Consider extracting event card into a reusable template.

The event card rendering logic is duplicated between past events (lines 39-72) and upcoming events (lines 87-119). This could be refactored into a reusable template include to improve maintainability.

You could create a new file tcf_website/templates/calendar/_event_card.html:

<div class="card rating-card mb-2">
  <div class="row no-gutters">
    <div class="col-md-8">
      <div class="card-body">
        <div class="fw-bold text-dark mb-1">{{ event.name }}</div>
        <div class="mb-2">
          <span class="text-primary fw-semibold">{{ event.org }}</span>
          {% if event.location %} • <span class="text-muted">{{ event.location }}</span>{% endif %}
        </div>
        {% if event.tags and event.tags|length > 0 %}
          <div class="mb-2">
            {% for t in event.tags|slice:":3" %}
              <span class="badge {{ t|tag_color }} text-white me-1">{{ t }}</span>
            {% endfor %}
          </div>
        {% endif %}
        {% if event.desc_text %}
          <div class="text-muted text-truncate" style="max-width: 700px;">{{ event.desc_text }}</div>
        {% endif %}
      </div>
    </div>
    <div class="col-md-4 d-flex align-items-center justify-content-center">
      <a class="btn btn-outline-primary btn-sm"
         href="{% url 'event_detail' event_uri=event.uri %}"
         title="View event details">
         Details
      </a>
    </div>
  </div>
</div>

Then include it using: {% include "calendar/_event_card.html" with event=ev %}

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24a3a32 and 5e4606a.

📒 Files selected for processing (12)
  • tcf_core/context_processors.py (1 hunks)
  • tcf_core/settings/base.py (2 hunks)
  • tcf_website/services/presence.py (1 hunks)
  • tcf_website/templates/base/sidebar.html (1 hunks)
  • tcf_website/templates/calendar/calendar_overview.html (1 hunks)
  • tcf_website/templates/calendar/disabled.html (1 hunks)
  • tcf_website/templates/calendar/event_detail.html (1 hunks)
  • tcf_website/templatetags/custom_tags.py (2 hunks)
  • tcf_website/tests/test_calendar_overview.py (1 hunks)
  • tcf_website/urls.py (1 hunks)
  • tcf_website/views/__init__.py (1 hunks)
  • tcf_website/views/calendar.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tcf_website/urls.py (1)
tcf_website/views/calendar.py (2)
  • calendar_overview (43-101)
  • event_detail (104-144)
tcf_website/views/__init__.py (1)
tcf_website/views/calendar.py (2)
  • calendar_overview (43-101)
  • event_detail (104-144)
tcf_website/views/calendar.py (1)
tcf_website/services/presence.py (2)
  • get_events (22-31)
  • get_event_details (34-40)
🪛 GitHub Actions: Continuous Integration
tcf_website/tests/test_calendar_overview.py

[error] 14-14: Pylint: trailing newlines (trailing-newlines)


[error] 1-1: Pylint: Missing module docstring (missing-module-docstring)


[error] 5-5: Pylint: Missing class docstring (missing-class-docstring)


[error] 10-10: Pylint: Missing function or method docstring (missing-function-docstring)

tcf_core/context_processors.py

[error] 34-34: Pylint: Missing function or method docstring (missing-function-docstring)

tcf_website/views/calendar.py

[error] 34-34: Pylint: trailing whitespace (trailing-whitespace)


[error] 68-68: Pylint: trailing whitespace (trailing-whitespace)


[error] 90-90: Pylint: Line too long (114/100) (line-too-long)


[error] 91-91: Pylint: Line too long (106/100) (line-too-long)


[error] 146-146: Pylint: Trailing newlines (trailing-newlines)


[error] 1-1: Pylint: Missing module docstring (missing-module-docstring)


[error] 23-23: Pylint: Redefining name 'datetime' from outer scope (line 5) (redefined-outer-name)


[error] 23-23: Pylint: Reimport 'datetime' (reimported)


[error] 23-23: Pylint: Import outside toplevel (datetime.datetime) (import-outside-toplevel)


[error] 43-43: Pylint: Missing function or method docstring (missing-function-docstring)


[warning] 112-112: Pylint: Consider explicitly re-raising using 'except Exception as exc' and 'raise Http404('Event not found') from exc' (raise-missing-from)


[error] 5-5: Pylint: Standard import order: 'from django.conf import settings' should come after standard imports (wrong-import-order)


[error] 2-2: Pylint: Unused import 'get_object_or_404' from django.shortcuts (unused-import)

tcf_website/templatetags/custom_tags.py

[error] 26-26: Pylint: trailing whitespace (trailing-whitespace)


[error] 29-29: Pylint: trailing whitespace (trailing-whitespace)


[error] 53-53: Pylint: trailing whitespace (trailing-whitespace)


[error] 57-57: Pylint: trailing whitespace (trailing-whitespace)


[error] 68-68: Pylint: trailing whitespace (trailing-whitespace)


[error] 72-72: Pylint: trailing whitespace (trailing-whitespace)


[error] 4-4: Pylint: standard import 'import hashlib' should be placed before 'from django import template' (wrong-import-order)

🪛 Ruff (0.14.1)
tcf_website/views/calendar.py

111-111: Do not catch blind exception: Exception

(BLE001)


112-112: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


112-112: Avoid specifying long messages outside the exception class

(TRY003)


115-115: Avoid specifying long messages outside the exception class

(TRY003)

tcf_website/templatetags/custom_tags.py

70-70: Probable use of insecure hash functions in hashlib: md5

(S324)

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

44-44: LGTM!

The calendar view exports follow the existing pattern and are properly integrated with the URL routing.

tcf_website/templatetags/custom_tags.py (1)

70-70: MD5 usage is acceptable here.

The static analysis tool flagged MD5 as insecure, but in this context, it's being used for deterministic color generation (not cryptography), so this is perfectly safe.

tcf_core/settings/base.py (2)

137-137: LGTM!

The context processor integration follows Django conventions and properly exposes the calendar feature flag to templates.


258-263: LGTM!

The Presence/Calendar configuration settings are well-structured with sensible defaults. The timeout and cache durations are appropriate for external API calls.

tcf_website/services/presence.py (1)

1-20: LGTM!

The API client implementation is well-structured with appropriate retry logic, timeout handling, and error propagation. The use of backoff for exponential retry on network errors is a best practice.

tcf_website/templates/calendar/disabled.html (1)

1-11: LGTM!

The disabled calendar template is clean and straightforward. Minor note: there are extra blank lines at the end (lines 10-11), but this is a very minor style issue.

tcf_website/templates/base/sidebar.html (1)

63-75: LGTM!

The calendar navigation item is properly gated with the feature flag and follows the existing sidebar pattern. The active state logic and icon usage are consistent with other sidebar items.

tcf_core/context_processors.py (1)

36-36: Verify default value consistency.

The context processor defaults ENABLE_CLUB_CALENDAR to False, but in tcf_core/settings/base.py (line 260), the setting defaults to True. This mismatch might be intentional (fail-safe), but please verify this is the desired behavior to avoid confusion.

tcf_website/urls.py (1)

130-131: LGTM!

The new URL patterns for calendar functionality are well-structured and follow Django conventions. The route names are descriptive and the event_uri parameter type is appropriate.

tcf_website/templates/calendar/calendar_overview.html (1)

16-22: Good UX approach for future functionality.

Including the disabled search input as a placeholder for upcoming functionality is a nice touch. It sets user expectations and shows the direction of the feature.

Copy link
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: 4

♻️ Duplicate comments (1)
tcf_website/views/calendar.py (1)

5-8: Prune unused imports.

settings and get_object_or_404 are unused and keep tripping lint; drop them so CI passes.

🧹 Nitpick comments (1)
tcf_website/templatetags/custom_tags.py (1)

70-74: Swap to a non-legacy hash for deterministic colors.

hashlib.md5 fires our security linters (Ruff S324) even in non-crypto use. Switch to hashlib.sha256 (or similar modern hash) so we stay compliant without changing behavior; just adjust the modulus to reuse the existing color list.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd557b and dc9112b.

📒 Files selected for processing (9)
  • tcf_core/context_processors.py (1 hunks)
  • tcf_core/settings/base.py (2 hunks)
  • tcf_website/services/presence.py (1 hunks)
  • tcf_website/templates/base/sidebar.html (1 hunks)
  • tcf_website/templates/calendar/calendar_overview.html (1 hunks)
  • tcf_website/templates/calendar/event_detail.html (1 hunks)
  • tcf_website/templatetags/custom_tags.py (2 hunks)
  • tcf_website/tests/test_calendar_overview.py (1 hunks)
  • tcf_website/views/calendar.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • tcf_website/templates/calendar/event_detail.html
  • tcf_website/templates/base/sidebar.html
  • tcf_core/settings/base.py
  • tcf_website/tests/test_calendar_overview.py
  • tcf_website/services/presence.py
  • tcf_core/context_processors.py
🧰 Additional context used
🧬 Code graph analysis (1)
tcf_website/views/calendar.py (1)
tcf_website/services/presence.py (2)
  • get_events (25-34)
  • get_event_details (37-43)
🪛 GitHub Actions: Continuous Integration
tcf_website/views/calendar.py

[warning] 91-92: C0301: Line too long (114/100) (line-too-long).


[warning] 142-142: C0305: Trailing newlines (trailing-newlines).


[warning] 26-26: W0621: Redefining name 'datetime' from outer scope (line 3) (redefined-outer-name).


[warning] 26-26: W0404: Reimport 'datetime' (imported line 3) (reimported).


[warning] 26-26: C0415: Import outside toplevel (datetime.datetime) (import-outside-toplevel).


[warning] 109-109: W0707: Consider explicitly re-raising using 'except Exception as exc' and 'raise Http404("Event not found") from exc' (raise-missing-from).


[warning] 5-5: W0611: Unused settings imported from django.conf (unused-import).


[warning] 7-7: W0611: Unused get_object_or_404 imported from django.shortcuts (unused-import).

🪛 Ruff (0.14.3)
tcf_website/views/calendar.py

108-108: Do not catch blind exception: Exception

(BLE001)


109-109: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


109-109: Avoid specifying long messages outside the exception class

(TRY003)


112-112: Avoid specifying long messages outside the exception class

(TRY003)

tcf_website/templatetags/custom_tags.py

71-71: Probable use of insecure hash functions in hashlib: md5

(S324)

Copy link
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: 7

Caution

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

⚠️ Outside diff range comments (1)
tcf_core/context_processors.py (1)

17-31: Handle None case when no semesters exist.

The pipeline failure confirms that Semester.latest() can return None, causing an AttributeError when accessing .number on line 21. This occurs when the database has no semester records, which can happen in testing or fresh environments.

Apply this diff to handle the None case gracefully:

 def searchbar_context(request):
     """Provide context for the search bar."""
     latest_semester = Semester.latest()
+    
+    if latest_semester is None:
+        return {
+            "disciplines": Discipline.objects.all().order_by("name"),
+            "subdepartments": Subdepartment.objects.all().order_by("mnemonic"),
+            "semesters": Semester.objects.none(),
+        }
+    
     recent_semesters = Semester.objects.filter(
         number__gte=latest_semester.number - 50  # 50 = 5 years * 10 semesters
     ).order_by("-number")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3abdfa and b508f44.

📒 Files selected for processing (8)
  • tcf_core/context_processors.py (1 hunks)
  • tcf_core/settings/base.py (3 hunks)
  • tcf_core/settings/prod.py (1 hunks)
  • tcf_website/templates/base/sidebar.html (1 hunks)
  • tcf_website/templates/calendar/event_detail.html (1 hunks)
  • tcf_website/tests/test_calendar_overview.py (1 hunks)
  • tcf_website/tests/test_event_detail.py (1 hunks)
  • tcf_website/views/calendar.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tcf_website/templates/calendar/event_detail.html
🧰 Additional context used
🧬 Code graph analysis (2)
tcf_website/views/calendar.py (1)
tcf_website/services/presence.py (2)
  • get_events (25-34)
  • get_event_details (37-43)
tcf_website/tests/test_event_detail.py (1)
tcf_website/tests/test_calendar_overview.py (1)
  • setUp (11-12)
🪛 GitHub Actions: Continuous Integration
tcf_website/tests/test_calendar_overview.py

[warning] 24-24: C0301: Line too long (122/100) (line-too-long)


[warning] 25-25: C0301: Line too long (122/100) (line-too-long)


[warning] 26-26: C0301: Line too long (122/100) (line-too-long)


[warning] 38-38: C0305: Trailing newlines (trailing-newlines)

tcf_website/views/calendar.py

[warning] 141-141: C0305: Trailing newlines (trailing-newlines)


[warning] 5-5: W0611: Unused settings imported from django.conf (unused-import)

tcf_website/tests/test_event_detail.py

[warning] 18-18: W0107: Unnecessary pass statement (unnecessary-pass)


[warning] 5-5: W0611: Unused Http404 imported from django.http (unused-import)

tcf_core/settings/base.py

[warning] 261-261: C0305: Trailing newlines (trailing-newlines)

tcf_core/context_processors.py

[error] 21-21: AttributeError: 'NoneType' object has no attribute 'number' in searchbar_context during calendar view.

🪛 Ruff (0.14.3)
tcf_core/settings/prod.py

31-31: env may be undefined, or defined from star imports

(F405)

tcf_website/views/calendar.py

108-108: Avoid specifying long messages outside the exception class

(TRY003)


111-111: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (9)
tcf_core/context_processors.py (1)

32-32: LGTM!

The trailing blank line addition is a minor formatting improvement with no functional impact.

tcf_core/settings/prod.py (1)

27-34: LGTM! Production cache configuration addresses previous feedback.

The Redis cache configuration is appropriate for production and directly addresses the past review comment about not using the in-memory LocMemCache in production. The setup uses environment-based configuration with a sensible default.

Note: The Ruff F405 warning about env is a false positive—env is imported from base.py via the star import on line 3.

tcf_core/settings/base.py (3)

52-61: LGTM! Cleaner database configuration structure.

Moving the default DATABASES configuration to base settings with sensible defaults (localhost:5432) and then updating it for non-dev environments is a cleaner approach than the previous inline conditional blocks.


99-105: LGTM! Better pattern for environment-specific database configuration.

Using update() to modify only the RDS-specific keys is cleaner than redefining the entire DATABASES["default"] dictionary. This preserves the base configuration while overriding only what's necessary for production.


256-260: LGTM! Presence configuration constants are well-chosen.

The default values are appropriate:

  • 8-second timeout prevents hanging on slow API responses
  • 5-minute cache (300s) balances data freshness with API load
  • "virginia" subdomain aligns with the PR objectives
tcf_website/views/calendar.py (4)

13-43: LGTM! Helper functions are well-structured.

The helper functions are clean and properly handle edge cases:

  • _safe_text safely extracts plain text from HTML
  • _sort_key correctly uses start_utc (addressing past review feedback)
  • _format_datetime handles missing/invalid dates gracefully
  • _is_upcoming treats events without dates as upcoming (reasonable default)

45-102: LGTM! Calendar overview logic is sound.

The function properly:

  • Fetches and normalizes event data
  • Sorts chronologically using the corrected _sort_key
  • Separates upcoming vs. past events
  • Groups by date with "TBD" handling
  • Passes organized data to the template

The line-length and whitespace issues from past reviews have been addressed.


103-139: LGTM! Event detail view handles errors appropriately.

The function correctly:

  • Fetches event details with proper exception chaining (from exc)
  • Returns 404 for missing events
  • Formats all event fields with sensible defaults
  • Passes comprehensive data to the template

The exception handling addresses past review feedback.


5-5: Remove unused import.

The settings import is not used anywhere in the module. The ENABLE_CLUB_CALENDAR check appears to have been removed in a previous iteration.

Apply this diff:

 from datetime import datetime, timezone
 
-from django.conf import settings
 from django.http import Http404
 from django.shortcuts import render

Likely an incorrect or invalid review comment.

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d1650b and 9f8873a.

📒 Files selected for processing (2)
  • tcf_website/urls.py (1 hunks)
  • tcf_website/views/__init__.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tcf_website/views/__init__.py (1)
tcf_website/views/calendar.py (2)
  • calendar_overview (45-102)
  • event_detail (103-139)
tcf_website/urls.py (1)
tcf_website/views/calendar.py (2)
  • calendar_overview (45-102)
  • event_detail (103-139)
🔇 Additional comments (1)
tcf_website/views/__init__.py (1)

43-43: LGTM!

The import follows the established pattern for exposing views from submodules and enables proper routing in urls.py.

Copy link
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_core/settings/base.py (1)

136-136: Critical: The flags context processor is causing an AttributeError.

This newly added context processor is triggering an AttributeError when the calendar view attempts to render (see pipeline failure on calendar.py line 95). The processor implementation must be missing an attribute or have a coding error.

This issue is already flagged in the review of calendar.py lines 95-102 with a verification script to examine the processor implementation.

🧹 Nitpick comments (1)
tcf_website/tests/test_calendar_overview.py (1)

24-41: Fix inconsistent indentation in dictionary literals.

The dictionary entries have inconsistent indentation. Lines 27, 33, and 39 (the "uri" and "startDateTimeUtc" keys) are indented more than the preceding keys, which hurts readability.

Apply this diff to align all keys consistently:

         mock_events = [
-        {
-            "eventName": "Event C",
-            "organizationName": "Org C",
-                "uri": "c",
-                "startDateTimeUtc": "2025-01-03T10:00:00Z",
-            },
-            {
-                "eventName": "Event A",
-                "organizationName": "Org A",
-                "uri": "a",
-                "startDateTimeUtc": "2025-01-01T10:00:00Z",
-            },
-            {
-                "eventName": "Event B",
-                "organizationName": "Org B",
-                "uri": "b",
-                "startDateTimeUtc": "2025-01-02T10:00:00Z",
-            },
+            {
+                "eventName": "Event C",
+                "organizationName": "Org C",
+                "uri": "c",
+                "startDateTimeUtc": "2025-01-03T10:00:00Z",
+            },
+            {
+                "eventName": "Event A",
+                "organizationName": "Org A",
+                "uri": "a",
+                "startDateTimeUtc": "2025-01-01T10:00:00Z",
+            },
+            {
+                "eventName": "Event B",
+                "organizationName": "Org B",
+                "uri": "b",
+                "startDateTimeUtc": "2025-01-02T10:00:00Z",
+            },
         ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f8873a and 14a2b7a.

📒 Files selected for processing (4)
  • tcf_core/settings/base.py (4 hunks)
  • tcf_website/tests/test_calendar_overview.py (1 hunks)
  • tcf_website/tests/test_event_detail.py (1 hunks)
  • tcf_website/views/calendar.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tcf_website/views/calendar.py (1)
tcf_website/services/presence.py (2)
  • get_events (25-34)
  • get_event_details (37-43)
tcf_website/tests/test_calendar_overview.py (1)
tcf_website/tests/test_event_detail.py (1)
  • setUp (12-13)
🪛 GitHub Actions: Continuous Integration
tcf_website/views/calendar.py

[error] 95-95: Internal server error on /clubs/calendar/: AttributeError triggered by context processing (see previous failing context_processors.py).

tcf_website/tests/test_event_detail.py

[error] 16-16: IndentationError: expected an indented block after function definition.

🪛 Ruff (0.14.4)
tcf_website/views/calendar.py

108-108: Avoid specifying long messages outside the exception class

(TRY003)


111-111: Avoid specifying long messages outside the exception class

(TRY003)

tcf_website/tests/test_event_detail.py

16-16: Expected an indented block after function definition

(invalid-syntax)

🔇 Additional comments (5)
tcf_website/views/calendar.py (2)

107-111: LGTM: Exception handling is correct.

The exception handling properly catches errors from the presence service, preserves the traceback with from exc, and returns a 404 as expected. The static analysis warning (TRY003) about the exception message length is a false positive—"Event not found" is concise and appropriate.


95-102: The review comment is incorrect and should be dismissed.

The flags context processor is properly implemented. It uses getattr(settings, "ENABLE_CLUB_CALENDAR", False) with a defensive fallback to False, which is the correct Django pattern for optional settings. This approach will not raise an AttributeErrorgetattr() with a default value returns the default when the attribute is missing. No evidence of an actual error exists in the codebase, and the render call in calendar.py should function normally with the current context processor setup.

Likely an incorrect or invalid review comment.

tcf_core/settings/base.py (3)

52-61: LGTM: Database configuration is properly structured.

The PostgreSQL database configuration correctly uses environment variables with sensible defaults for host and port. This follows Django best practices.


99-105: LGTM: AWS RDS configuration update is correct.

Using DATABASES["default"].update() properly overrides the base configuration for non-dev environments without redefining the entire dictionary. This is cleaner than the previous approach.


257-260: LGTM: Presence configuration constants are well-defined.

The presence feature flags use appropriate defaults (subdomain "virginia", 8-second timeout, 5-minute cache) and follow the project's naming conventions. These integrate cleanly with the presence service implementation.

Copy link
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 (2)
tcf_website/tests/test_event_detail.py (2)

34-34: Same URL path concern applies here.

This test has the same potential URL mismatch as test_event_detail_not_found_on_exception. If the route is at /clubs/calendar/event/, update this test URL as well.

Apply the same fix if verification confirms the mismatch:

-       resp = self.client.get("/clubs/events/test-uri/")
+       resp = self.client.get("/clubs/calendar/event/test-uri/")

15-19: Remove or implement the placeholder test.

This empty test provides no validation and causes a pipeline failure. Past reviews have already flagged this issue twice. Either implement the test with proper mocking and assertions for the happy path, or remove it entirely to avoid false confidence in test coverage.

Consider implementing it:

+   @patch('tcf_website.views.calendar.presence.get_event_details')
-   def test_event_detail_route_ok(self):
+   def test_event_detail_route_ok(self, mock_get_details):
        """Test that event detail route handles valid event."""
-       # Assuming a valid event URI, but since mocked, we'll add mocks
-       # Will be covered by mock tests
-       pass
+       mock_get_details.return_value = {
+           "eventName": "Test Event",
+           "organizationName": "Test Org",
+           "uri": "test-uri"
+       }
+       
+       resp = self.client.get("/clubs/events/test-uri/")
+       self.assertEqual(resp.status_code, 200)
+       self.assertContains(resp, "Test Event")

Or simply remove it:

-   def test_event_detail_route_ok(self):
-       """Test that event detail route handles valid event."""
-       # Assuming a valid event URI, but since mocked, we'll add mocks
-       # Will be covered by mock tests
-       pass
-
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14a2b7a and 0f840dc.

📒 Files selected for processing (1)
  • tcf_website/tests/test_event_detail.py (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Continuous Integration
tcf_website/tests/test_event_detail.py

[error] 19-19: W0107: Unnecessary pass statement (unnecessary-pass). Pylint: Unnecessary pass.

…context processor, and correct test URLs

Amp-Thread-ID: https://ampcode.com/threads/T-f27f5a04-14ba-4b2a-9cae-d01d74db0f30
Co-authored-by: Amp <amp@ampcode.com>
@R0hit-0
Copy link
Collaborator Author

R0hit-0 commented Nov 15, 2025

@coderabbitai generate docstrings to improve docstring coverage.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 15, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 15, 2025

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #1209

coderabbitai bot added a commit that referenced this pull request Nov 15, 2025
Docstrings generation was requested by @R0hit-0.

* #1178 (comment)

The following files were modified:

* `tcf_core/context_processors.py`
* `tcf_website/services/presence.py`
* `tcf_website/templatetags/custom_tags.py`
* `tcf_website/tests/test_calendar_overview.py`
* `tcf_website/tests/test_event_detail.py`
* `tcf_website/views/calendar.py`
@R0hit-0
Copy link
Collaborator Author

R0hit-0 commented Nov 15, 2025

@CodeRabbit generate docstrings to improve docstring coverage to over 80%

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 15, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

coderabbitai bot added a commit that referenced this pull request Nov 15, 2025
Docstrings generation was requested by @R0hit-0.

* #1178 (comment)

The following files were modified:

* `tcf_core/context_processors.py`
* `tcf_website/services/presence.py`
* `tcf_website/templatetags/custom_tags.py`
* `tcf_website/tests/test_calendar_overview.py`
* `tcf_website/tests/test_event_detail.py`
* `tcf_website/views/calendar.py`
Copy link
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 (2)
tcf_website/tests/test_event_detail.py (1)

1-30: Consider adding a happy-path test for complete coverage.

The current tests thoroughly validate error handling (exception and None return), but there's no test verifying the successful case where get_event_details returns valid event data and the view responds with HTTP 200.

Consider adding a test like this:

@patch('tcf_website.views.calendar.presence.get_event_details')
def test_event_detail_success(self, mock_get_details):
    """Test that event detail renders successfully with valid data."""
    mock_get_details.return_value = {
        "eventName": "Test Event",
        "organizationName": "Test Org",
        "startDateTimeUtc": "2025-11-20T18:00:00Z",
        "uri": "test-uri"
    }
    
    resp = self.client.get("/clubs/calendar/event/test-uri/")
    self.assertEqual(resp.status_code, 200)
    self.assertContains(resp, "Test Event")
tcf_website/views/calendar.py (1)

111-112: Add blank line between function definitions.

PEP 8 requires two blank lines between module-level function definitions. There should be a blank line between the end of calendar_overview (line 111) and the start of event_detail (line 112).

Apply this diff:

         },
     )
+
 def event_detail(request, event_uri):
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f840dc and dcb4963.

📒 Files selected for processing (5)
  • tcf_core/context_processors.py (2 hunks)
  • tcf_website/templates/calendar/calendar_overview.html (1 hunks)
  • tcf_website/tests/test_calendar_overview.py (1 hunks)
  • tcf_website/tests/test_event_detail.py (1 hunks)
  • tcf_website/views/calendar.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • tcf_core/context_processors.py
  • tcf_website/templates/calendar/calendar_overview.html
  • tcf_website/tests/test_calendar_overview.py
🧰 Additional context used
🧬 Code graph analysis (2)
tcf_website/views/calendar.py (1)
tcf_website/services/presence.py (2)
  • get_events (25-34)
  • get_event_details (37-43)
tcf_website/tests/test_event_detail.py (1)
tcf_website/tests/test_calendar_overview.py (1)
  • setUp (11-12)
🪛 Ruff (0.14.4)
tcf_website/views/calendar.py

117-117: Avoid specifying long messages outside the exception class

(TRY003)


120-120: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (2)
tcf_website/views/calendar.py (2)

112-148: LGTM!

The event_detail view is well-implemented with proper exception handling, defensive data extraction with defaults, and comprehensive event formatting. The exception chain is preserved using from exc, and the None check provides clear 404 behavior.

Note: Ruff flags TRY003 on lines 117 and 120, suggesting exception messages should be defined in exception classes rather than inline. However, for simple 404 responses, the current approach is perfectly acceptable and readable.


47-111: LGTM!

The calendar_overview view is well-structured with clear logic flow: fetch events, normalize data, sort, separate upcoming/past, group by date, and render. The unique tags collection (lines 67-71) provides filtering capability for the UI. Defensive programming with or [] and or "" handles missing data gracefully.

Copy link
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

🤖 Fix all issues with AI agents
In `@tcf_website/services/presence.py`:
- Around line 46-52: Add a concise docstring to the function get_event_details
describing its purpose (fetches event details, using cache), parameters
(event_uri: str), return value (event data dict or JSON), and side-effects
(reads/writes cache via _cache_key/cache with CACHE_TTL and calls _get on BASE).
Place the docstring immediately below the def get_event_details(...) signature
and mirror style/format used by get_events() for consistency in coverage and
documentation.
🧹 Nitpick comments (1)
tcf_website/services/presence.py (1)

9-11: Consider consistent defensive access for PRESENCE_SUBDOMAIN.

CACHE_TTL and TIMEOUT use getattr with defaults, but BASE accesses settings.PRESENCE_SUBDOMAIN directly. If the setting is missing, this raises AttributeError at module import time. If this is intentional (fail-fast for required config), that's fine—otherwise consider getattr with an explicit error or default.

Comment on lines 46 to 52
def get_event_details(event_uri: str):
key = _cache_key(f"event::{event_uri}")
data = cache.get(key)
if data is None:
data = _get(f"{BASE}/events/{event_uri}")
cache.set(key, data, CACHE_TTL)
return data
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a docstring for consistency and coverage goals.

get_events() has a docstring but get_event_details() does not. Given the PR comments mention targeting >80% docstring coverage, adding one here would help.

📝 Proposed docstring
 def get_event_details(event_uri: str):
+    """
+    Returns details for a specific event by URI. Cached briefly to avoid rate limits.
+
+    Args:
+        event_uri: The unique identifier for the event from Presence.
+
+    Returns:
+        Event details dictionary from the Presence API.
+    """
     key = _cache_key(f"event::{event_uri}")
📝 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 get_event_details(event_uri: str):
key = _cache_key(f"event::{event_uri}")
data = cache.get(key)
if data is None:
data = _get(f"{BASE}/events/{event_uri}")
cache.set(key, data, CACHE_TTL)
return data
def get_event_details(event_uri: str):
"""
Returns details for a specific event by URI. Cached briefly to avoid rate limits.
Args:
event_uri: The unique identifier for the event from Presence.
Returns:
Event details dictionary from the Presence API.
"""
key = _cache_key(f"event::{event_uri}")
data = cache.get(key)
if data is None:
data = _get(f"{BASE}/events/{event_uri}")
cache.set(key, data, CACHE_TTL)
return data
🤖 Prompt for AI Agents
In `@tcf_website/services/presence.py` around lines 46 - 52, Add a concise
docstring to the function get_event_details describing its purpose (fetches
event details, using cache), parameters (event_uri: str), return value (event
data dict or JSON), and side-effects (reads/writes cache via _cache_key/cache
with CACHE_TTL and calls _get on BASE). Place the docstring immediately below
the def get_event_details(...) signature and mirror style/format used by
get_events() for consistency in coverage and documentation.

ritilahoti and others added 8 commits February 8, 2026 12:37
#1223)

* fix(api): critical remove public facing api; keep internal server-side rendering

* fix(tests): remove API tests since API module was deleted

* fix(reviews): avoid empty reviews

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…alendar samples, seed schools

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
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: 13

🤖 Fix all issues with AI agents
In `@tcf_website/services/presence.py`:
- Around line 31-35: The backoff decorator on _get currently retries all
RequestException subclasses (including HTTPError from resp.raise_for_status()),
causing 4xx client errors to be retried; update the retry policy so client
errors are not retried by adding a giveup predicate or changing the exception
handling: in the _get function/decorator (symbols: _get, backoff.on_exception,
requests.RequestException, resp.raise_for_status()), detect HTTPError and its
response status (e.response.status_code) and call giveup for 4xx responses (or
check resp.status_code before calling raise_for_status and only raise/allow
retries for 5xx/connection/timeouts) so only network/5xx errors are retried with
backoff.

In `@tcf_website/templates/base/index.html`:
- Around line 88-91: The current inline onerror handler on the AdSense script
(onerror="$('#adblockModal').modal({backdrop:'static',keyboard:false}).modal('show');")
will show an undismissable modal (id: adblockModal) on any load failure; update
the modal UI instead of making it permanent: modify the adblock modal
(adblockModal) to include a visible dismiss/continue control (e.g., a "Continue
anyway" link/button) and/or a standard close button, and relax the modal options
(remove backdrop:'static' and/or set keyboard:true) so users can proceed on
transient network errors; optionally, move the onerror handler to call a small
JS function (e.g., showAdblockModal()) that can implement a timeout +
bait-element check before showing the modal for more reliable adblock detection.

In `@tcf_website/templates/calendar/calendar_overview.html`:
- Line 51: Remove the hardcoded inline style "min-width: 500px" from the
dropdown element (the div with class "dropdown-menu p-3" and
aria-labelledby="tagFilterDropdown") and instead assign a specific CSS class
(e.g., .tag-filter-dropdown) in the template; then add CSS rules for that class
that preserve the intended desktop sizing but provide a responsive override for
small viewports (for example using a media query to reset min-width and/or set
width: 100% / max-width: 100% or min-width: auto below a breakpoint) so the
dropdown does not cause horizontal overflow on mobile.
- Around line 202-208: The filterEvents function currently hides cards with no
tags because cardTags becomes [] and selectedTags.some(...) returns false;
update the logic in filterEvents (referencing function filterEvents, variable
eventCards, card.dataset.tags and getSelectedTags) so that cards with no tags
are shown by default—i.e., treat an empty cardTags array as a match (e.g.,
consider cardTags.length === 0 as true) or otherwise include a condition that
displays tagless events instead of relying solely on selectedTags.some(...).

In `@tcf_website/templates/common/adblock_modal.html`:
- Around line 3-9: The modal element with id "adblockModal" sets
aria-labelledby="adblockModalLabel" but there is no element with that id; update
the modal title element (the <h5> that reads "Ad Blocker Detected") to include
id="adblockModalLabel" so screen readers correctly associate the modal with its
label (ensure the id matches aria-labelledby exactly).

In `@tcf_website/templates/course/course_professor.html`:
- Around line 234-249: The widthratio usage can produce an empty string when a
limit is zero, creating invalid CSS like width: %; — update the progress-bar
style attributes for both enrollment and waitlist to guard against zero limits
by checking section.enrollment_limit and section.waitlist_limit (or using a
default 0) before calling the {% widthratio %} so the style always renders a
valid numeric percentage; locate the progress-bar divs that use widthratio in
the template (the enrollment progress-bar and the waitlist progress-bar) and
wrap or replace the width expression with a conditional that yields 0% when the
corresponding limit is zero.
- Around line 286-294: The Q&A tab link (anchor with id="qaTab" and
href="#qaTabContent") has no matching content pane inside the surrounding <div
class="tab-content"> (which currently only contains `#reviewsTabContent`), causing
a blank panel; fix by either adding a corresponding tab pane element with
id="qaTabContent" and class="tab-pane" (matching the structure and classes used
by `#reviewsTabContent` and tying into the existing tabs behavior), or remove the
qaTab anchor from the <ul class="nav nav-tabs"> so there is no orphaned link.

In `@tcf_website/templates/reviews/new_review.html`:
- Around line 94-96: The placeholder promises "100+ words" but the HTML
minlength="100" enforces characters, not words; fix by adding client-side
word-count validation for the textarea with id="reviewtext": on input and on
submit (select the form via
document.getElementById('reviewtext').closest('form')), count words by splitting
the textarea.value on whitespace, and if words < 100 call
textarea.setCustomValidity("Please enter at least 100 words.") and
reportValidity()/preventDefault() on submit; otherwise clear the custom validity
so the form can submit. Alternatively, if the intent was characters, change the
placeholder text to "aim for 100+ characters" and keep minlength="100".
- Around line 206-237: The submit handler attached to
document.getElementById('reviewForm') prevents default but has no error handling
for the two fetch flows (the /reviews/check_duplicate/ and
/reviews/check_zero_hours_per_week/ requests), so network/server errors leave
the form unusable; update the fetch chains in that handler to (1) check
response.ok and throw on non-2xx before r.json(), (2) add .catch handlers for
both fetch calls that restore form usability by enabling
document.getElementById('submitBtn') and either submitting the form or showing a
user-facing error, and (3) ensure zeroHoursModalShown logic is preserved so
repeated submits don’t re-open the modal unexpectedly.

In `@tcf_website/views/browse.py`:
- Around line 115-116: The Http404 message raised when latest_semester is None
in the browse view is longer than 100 chars; shorten or extract it to a constant
to satisfy pylint. Update the raise in the function containing latest_semester
to use either a shorter string (e.g., "No semester data loaded. Run
migrations.") or a named constant (e.g., NO_SEMESTER_ERROR = "No semester data
loaded. Run migrations and ensure the database has semester data.") and
reference that constant in the raise Http404(...) call so the line length is
under 100 characters.

In `@tcf_website/views/calendar.py`:
- Around line 95-97: The inline comment above the dev-only sample data fallback
is over the 100-char limit; shorten or wrap it so the line length is <=100. Edit
the comment that precedes the if block using settings.DEBUG and events (the one
explaining _sample_events_for_dev()) to a shorter sentence or split into two
shorter comment lines immediately above the if statement so the if block and
calls to settings.DEBUG and _sample_events_for_dev() remain unchanged.
- Around line 145-147: Insert the required blank lines between the top-level
function definitions: add two blank lines between the end of calendar_overview
and the start of event_detail so event_detail is separated by a blank line pair
per PEP8 (E302); locate the definitions by the function names calendar_overview
and event_detail and update the spacing accordingly.

In `@tcf_website/views/review.py`:
- Around line 142-199: In _handle_course_review_get, guard against
Semester.latest() returning None before accessing latest.year by checking if
latest is None and handling it (e.g. messages.info(request, "No semesters
available; please try later.") and return redirect("/browse?mode=courses") or
another suitable early return), so replace direct uses of latest.year in the
semesters queries with a branch that only runs those queries when latest is not
None and otherwise performs the early redirect/response.
🧹 Nitpick comments (9)
docker-compose.yml (1)

9-14: Hardcoded db:5432 in command doesn't use the declared env vars.

Lines 11–12 declare DB_HOST and DB_PORT as environment variables, but line 14 hardcodes db:5432 in the wait-for-it command. If someone updates the env vars, the wait-for-it target won't follow.

Suggested fix
    command:
-      bash -c "/wait-for-it.sh db:5432 -- \
+      bash -c "/wait-for-it.sh $${DB_HOST}:$${DB_PORT} -- \

($$ escapes to a literal $ in compose, so the shell inside the container resolves the env vars at runtime.)

doc/dev.md (1)

41-50: Good documentation addition.

Minor note: the database dump instructions (steps 1–4 under "Course browse") duplicate steps 5–6 in the setup section above. Consider referencing the existing steps instead to avoid divergence if the process changes.

tcf_website/views/review.py (1)

114-122: Duplicate hours_per_week computation.

ReviewForm.save(commit=False) (line 65-73) already computes and sets hours_per_week on the instance. Lines 116-121 repeat the exact same calculation. Remove the redundant assignment here or in the form's save().

Proposed fix
         if form.is_valid():
             instance = form.save(commit=False)
             instance.user = request.user
-            instance.hours_per_week = (
-                instance.amount_reading
-                + instance.amount_writing
-                + instance.amount_group
-                + instance.amount_homework
-            )
             instance.save()
tcf_website/services/presence.py (1)

32-32: Return type hint is inaccurate — get_events expects a list, not a dict.

_get is typed as returning dict, but the /events endpoint returns a JSON array (list). Consider dict | list or using a more generic type.

♻️ Suggested fix
-def _get(url: str, params: dict | None = None) -> dict:
+def _get(url: str, params: dict | None = None) -> dict | list:
tcf_website/templates/calendar/calendar_overview.html (1)

86-117: Near-identical event card markup is duplicated between past and upcoming sections.

The card template for past events (Lines 87-116) and upcoming events (Lines 135-164) are virtually identical. Extract into a shared partial (e.g., calendar/_event_card.html) and {% include %} it in both places to reduce maintenance burden.

Also applies to: 134-165

tcf_website/views/calendar.py (2)

117-135: Consider extracting duplicate date-grouping logic into a helper.

The grouping logic at lines 117–122 and 124–129 is identical. A small helper would reduce duplication:

def _group_by_date(events):
    groups = {}
    for ev in events:
        dt = ev["start_utc"] or ""
        date_key = dt[:10] if len(dt) >= 10 else "TBD"
        groups.setdefault(date_key, []).append(ev)
    return dict(sorted(
        groups.items(),
        key=lambda x: x[0] if x[0] != "TBD" else "9999-12-31"
    ))

This would also absorb the sorting step from lines 131–135.


77-105: calendar_overview normalization and dev fallback look solid.

One tiny nit: sorted(list(all_tags)) on line 105 — sorted() already returns a list, so wrapping in list() is unnecessary.

-    unique_tags = sorted(list(all_tags))
+    unique_tags = sorted(all_tags)
tcf_website/templates/course/course_professor.html (2)

268-284: Inline script for section toggle is clean.

The DOMContentLoaded listener with null-checks (if (showMoreBtn && sectionsList)) is defensive and correct. The Django template tags within the JS string are evaluated server-side, so this works as expected.

One minor note: this script block sits inside {% block content %} rather than {% block js %}. While it works (DOMContentLoaded guarantees DOM readiness), moving it to {% block js %} would be more consistent with the rest of the page's script organization.


222-257: Extract hardcoded SIS URL to a settings constant for maintainability.

The SIS URL appears in multiple places across the codebase (template and management commands). Extracting it to a Django settings constant would simplify future updates if the URL structure changes.

Additionally, verify that section.units handles decimal values correctly with the pluralize filter. Since units is stored as a CharField (string) rather than a numeric field, values like "1.0" will incorrectly pluralize to "Units" instead of "Unit" because Django's filter checks string equality against "1", not numeric equivalence. If SIS data includes decimal units (e.g., "1.5"), consider converting to numeric type or adjusting the template logic.

Comment on lines +31 to +35
@backoff.on_exception(backoff.expo, (requests.RequestException,), max_tries=3)
def _get(url: str, params: dict | None = None) -> dict:
resp = _SESSION.get(url, params=params or {}, timeout=TIMEOUT)
resp.raise_for_status()
return resp.json()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Backoff retries non-retriable HTTP errors (4xx) unnecessarily.

raise_for_status() throws HTTPError (a subclass of RequestException), so client errors like 403/404 will be retried 3 times with exponential backoff. This wastes time and could aggravate rate limits. Consider limiting retries to connection/timeout errors, or adding a giveup predicate for 4xx responses.

🔧 Proposed fix — skip retries on client errors
+def _is_client_error(exc):
+    """Don't retry 4xx responses — they won't succeed on retry."""
+    if isinstance(exc, requests.HTTPError) and exc.response is not None:
+        return 400 <= exc.response.status_code < 500
+    return False
+
-@backoff.on_exception(backoff.expo, (requests.RequestException,), max_tries=3)
+@backoff.on_exception(
+    backoff.expo,
+    (requests.RequestException,),
+    max_tries=3,
+    giveup=_is_client_error,
+)
 def _get(url: str, params: dict | None = None) -> dict:
🤖 Prompt for AI Agents
In `@tcf_website/services/presence.py` around lines 31 - 35, The backoff decorator
on _get currently retries all RequestException subclasses (including HTTPError
from resp.raise_for_status()), causing 4xx client errors to be retried; update
the retry policy so client errors are not retried by adding a giveup predicate
or changing the exception handling: in the _get function/decorator (symbols:
_get, backoff.on_exception, requests.RequestException, resp.raise_for_status()),
detect HTTPError and its response status (e.response.status_code) and call
giveup for 4xx responses (or check resp.status_code before calling
raise_for_status and only raise/allow retries for 5xx/connection/timeouts) so
only network/5xx errors are retried with backoff.

Comment on lines 88 to 91

<!--Google AdSense (after jQuery so onerror can show modal)-->
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1816162577232603"
crossorigin="anonymous" onerror="$('#adblockModal').modal({backdrop:'static',keyboard:false}).modal('show');"></script>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

onerror will fire on any network failure, not just ad blockers — potentially locking out legitimate users.

The onerror handler on the AdSense script triggers for any load failure (e.g., intermittent network issues, corporate firewalls, DNS failures, regions where Google domains are blocked). Combined with the undismissable modal (backdrop:'static', keyboard:false), a false positive completely locks the user out with no way to proceed — reloading will just fail again.

Consider either:

  1. Adding a dismiss/close option (e.g., "Continue anyway") so users aren't permanently stuck.
  2. Using a more reliable detection method (e.g., checking a known bait element after a timeout rather than relying solely on script onerror).
Proposed fix: add a secondary dismiss option

In tcf_website/templates/common/adblock_modal.html, add a small dismiss link below the button:

                 <button onclick="location.reload();" class="btn btn-primary bg-tcf-indigo pl-4 pr-4">I've Disabled It</button>
+                <div class="mt-2">
+                    <a href="#" data-dismiss="modal" class="text-muted small">Continue anyway</a>
+                </div>
🤖 Prompt for AI Agents
In `@tcf_website/templates/base/index.html` around lines 88 - 91, The current
inline onerror handler on the AdSense script
(onerror="$('#adblockModal').modal({backdrop:'static',keyboard:false}).modal('show');")
will show an undismissable modal (id: adblockModal) on any load failure; update
the modal UI instead of making it permanent: modify the adblock modal
(adblockModal) to include a visible dismiss/continue control (e.g., a "Continue
anyway" link/button) and/or a standard close button, and relax the modal options
(remove backdrop:'static' and/or set keyboard:true) so users can proceed on
transient network errors; optionally, move the onerror handler to call a small
JS function (e.g., showAdblockModal()) that can implement a timeout +
bait-element check before showing the modal for more reliable adblock detection.

<button class="btn btn-outline-primary dropdown-toggle" type="button" id="tagFilterDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Filters
</button>
<div class="dropdown-menu p-3" aria-labelledby="tagFilterDropdown" style="min-width: 500px;">
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded min-width: 500px will overflow on mobile viewports.

The inline style on the dropdown menu forces a 500px minimum width. On screens narrower than ~500px, this causes horizontal overflow. Move this to a CSS class and add a responsive override.

🔧 Proposed fix
-      <div class="dropdown-menu p-3" aria-labelledby="tagFilterDropdown" style="min-width: 500px;">
+      <div class="dropdown-menu p-3" aria-labelledby="tagFilterDropdown" style="min-width: min(500px, 90vw);">
📝 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
<div class="dropdown-menu p-3" aria-labelledby="tagFilterDropdown" style="min-width: 500px;">
<div class="dropdown-menu p-3" aria-labelledby="tagFilterDropdown" style="min-width: min(500px, 90vw);">
🤖 Prompt for AI Agents
In `@tcf_website/templates/calendar/calendar_overview.html` at line 51, Remove the
hardcoded inline style "min-width: 500px" from the dropdown element (the div
with class "dropdown-menu p-3" and aria-labelledby="tagFilterDropdown") and
instead assign a specific CSS class (e.g., .tag-filter-dropdown) in the
template; then add CSS rules for that class that preserve the intended desktop
sizing but provide a responsive override for small viewports (for example using
a media query to reset min-width and/or set width: 100% / max-width: 100% or
min-width: auto below a breakpoint) so the dropdown does not cause horizontal
overflow on mobile.

Comment on lines +202 to +208
function filterEvents() {
const selectedTags = getSelectedTags();
eventCards.forEach(card => {
const cardTags = card.dataset.tags ? card.dataset.tags.split(',').map(t => t.trim()) : [];
const hasSelectedTag = selectedTags.some(tag => cardTags.includes(tag));
card.style.display = hasSelectedTag ? '' : 'none';
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Events with no tags are always hidden when any filter is selected.

When card.dataset.tags is empty, the ternary on Line 205 yields []. Since no selected tag is found in an empty array, hasSelectedTag is always false, so tagless events are permanently hidden whenever any checkbox is checked (which is the initial state). Consider showing tagless events when they have no tags, or always showing them:

🔧 Proposed fix
         eventCards.forEach(card => {
             const cardTags = card.dataset.tags ? card.dataset.tags.split(',').map(t => t.trim()) : [];
-            const hasSelectedTag = selectedTags.some(tag => cardTags.includes(tag));
-            card.style.display = hasSelectedTag ? '' : 'none';
+            // Show tagless events always; for tagged events, require at least one selected tag match
+            const show = cardTags.length === 0 || selectedTags.some(tag => cardTags.includes(tag));
+            card.style.display = show ? '' : 'none';
         });
📝 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
function filterEvents() {
const selectedTags = getSelectedTags();
eventCards.forEach(card => {
const cardTags = card.dataset.tags ? card.dataset.tags.split(',').map(t => t.trim()) : [];
const hasSelectedTag = selectedTags.some(tag => cardTags.includes(tag));
card.style.display = hasSelectedTag ? '' : 'none';
});
function filterEvents() {
const selectedTags = getSelectedTags();
eventCards.forEach(card => {
const cardTags = card.dataset.tags ? card.dataset.tags.split(',').map(t => t.trim()) : [];
// Show tagless events always; for tagged events, require at least one selected tag match
const show = cardTags.length === 0 || selectedTags.some(tag => cardTags.includes(tag));
card.style.display = show ? '' : 'none';
});
🤖 Prompt for AI Agents
In `@tcf_website/templates/calendar/calendar_overview.html` around lines 202 -
208, The filterEvents function currently hides cards with no tags because
cardTags becomes [] and selectedTags.some(...) returns false; update the logic
in filterEvents (referencing function filterEvents, variable eventCards,
card.dataset.tags and getSelectedTags) so that cards with no tags are shown by
default—i.e., treat an empty cardTags array as a match (e.g., consider
cardTags.length === 0 as true) or otherwise include a condition that displays
tagless events instead of relying solely on selectedTags.some(...).

Comment on lines 3 to 9
<div class="modal fade" id="adblockModal" tabindex="-1" role="dialog" aria-labelledby="adblockModalLabel"
aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body text-center pt-4 pb-4">
<img src="{% static 'base/img/new_logo.svg' %}" alt="theCourseForum" class="img-fluid mb-3" style="max-height: 60px;">
<h5 class="mb-3">Ad Blocker Detected</h5>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

aria-labelledby references a non-existent element ID.

Line 3 sets aria-labelledby="adblockModalLabel", but no element in the modal has id="adblockModalLabel". The <h5> on Line 9 is the intended label target but is missing the corresponding id attribute. Screen readers won't associate the modal with its title.

♿ Proposed fix
-                <h5 class="mb-3">Ad Blocker Detected</h5>
+                <h5 id="adblockModalLabel" class="mb-3">Ad Blocker Detected</h5>
📝 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
<div class="modal fade" id="adblockModal" tabindex="-1" role="dialog" aria-labelledby="adblockModalLabel"
aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body text-center pt-4 pb-4">
<img src="{% static 'base/img/new_logo.svg' %}" alt="theCourseForum" class="img-fluid mb-3" style="max-height: 60px;">
<h5 class="mb-3">Ad Blocker Detected</h5>
<div class="modal fade" id="adblockModal" tabindex="-1" role="dialog" aria-labelledby="adblockModalLabel"
aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body text-center pt-4 pb-4">
<img src="{% static 'base/img/new_logo.svg' %}" alt="theCourseForum" class="img-fluid mb-3" style="max-height: 60px;">
<h5 id="adblockModalLabel" class="mb-3">Ad Blocker Detected</h5>
🤖 Prompt for AI Agents
In `@tcf_website/templates/common/adblock_modal.html` around lines 3 - 9, The
modal element with id "adblockModal" sets aria-labelledby="adblockModalLabel"
but there is no element with that id; update the modal title element (the <h5>
that reads "Ad Blocker Detected") to include id="adblockModalLabel" so screen
readers correctly associate the modal with its label (ensure the id matches
aria-labelledby exactly).

Comment on lines 206 to 237
document.getElementById('reviewForm').addEventListener('submit', function(e) {
e.preventDefault();
var form = this;
var formData = new FormData(form);

$.ajax({
type: "POST",
url: "/reviews/check_duplicate/",
data: $("#reviewForm").serialize(),
async: false,
success: function (check) {
if(check.duplicate) {
$("#duplicate-review").modal();
} else {
// Check if review has 0 total hours/week and warn user if it does
$.ajax({
type: "POST",
url: "/reviews/check_zero_hours_per_week/",
data: $("#reviewForm").serialize(),
async: false,
success: function (check) {
// Only show "zero-hours-review" modal once
if(check.zero && !zeroHoursModalShown) {
$("#zero-hours-review").modal();
zeroHoursModalShown = true;
} else {
// Timeout button for 3 seconds so user can't spam button while form is submitting
$("#submitBtn").prop("disabled", true);
setTimeout(enableButton, 3000);
// $("#reviewForm") didn't work for some reason
// Not a duplicate review and the zero hours modal has been shown or wasn't
// necessary to show, so proceed with normal form submission for new review
document.getElementById("reviewForm").action = "{% url 'new_review' %}";
document.getElementById("reviewForm").submit();
}
}
});
}
}
fetch('/reviews/check_duplicate/', {
method: 'POST',
body: formData,
headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
})
.then(r => r.json())
.then(check => {
if (check.duplicate) {
$('#duplicate-review').modal();
} else {
fetch('/reviews/check_zero_hours_per_week/', {
method: 'POST',
body: formData,
headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
})
.then(r => r.json())
.then(check => {
if (check.zero && !zeroHoursModalShown) {
$('#zero-hours-review').modal();
zeroHoursModalShown = true;
} else {
document.getElementById('submitBtn').disabled = true;
form.submit();
}
});
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling on fetch calls — form becomes unsubmittable on network/server errors.

If /reviews/check_duplicate/ or /reviews/check_zero_hours_per_week/ returns a non-200 response or the network fails, the rejected promise is unhandled. The user's submit is preventDefault()-ed on Line 207 but never recovered, leaving the form stuck.

🔧 Proposed fix — add .catch handlers
     fetch('/reviews/check_duplicate/', {
       method: 'POST',
       body: formData,
       headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
     })
     .then(r => r.json())
     .then(check => {
       if (check.duplicate) {
         $('#duplicate-review').modal();
       } else {
         fetch('/reviews/check_zero_hours_per_week/', {
           method: 'POST',
           body: formData,
           headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
         })
         .then(r => r.json())
         .then(check => {
           if (check.zero && !zeroHoursModalShown) {
             $('#zero-hours-review').modal();
             zeroHoursModalShown = true;
           } else {
             document.getElementById('submitBtn').disabled = true;
             form.submit();
           }
-        });
+        })
+        .catch(() => { form.submit(); });
       }
-    });
+    })
+    .catch(() => { form.submit(); });
📝 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
document.getElementById('reviewForm').addEventListener('submit', function(e) {
e.preventDefault();
var form = this;
var formData = new FormData(form);
$.ajax({
type: "POST",
url: "/reviews/check_duplicate/",
data: $("#reviewForm").serialize(),
async: false,
success: function (check) {
if(check.duplicate) {
$("#duplicate-review").modal();
} else {
// Check if review has 0 total hours/week and warn user if it does
$.ajax({
type: "POST",
url: "/reviews/check_zero_hours_per_week/",
data: $("#reviewForm").serialize(),
async: false,
success: function (check) {
// Only show "zero-hours-review" modal once
if(check.zero && !zeroHoursModalShown) {
$("#zero-hours-review").modal();
zeroHoursModalShown = true;
} else {
// Timeout button for 3 seconds so user can't spam button while form is submitting
$("#submitBtn").prop("disabled", true);
setTimeout(enableButton, 3000);
// $("#reviewForm") didn't work for some reason
// Not a duplicate review and the zero hours modal has been shown or wasn't
// necessary to show, so proceed with normal form submission for new review
document.getElementById("reviewForm").action = "{% url 'new_review' %}";
document.getElementById("reviewForm").submit();
}
}
});
}
}
fetch('/reviews/check_duplicate/', {
method: 'POST',
body: formData,
headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
})
.then(r => r.json())
.then(check => {
if (check.duplicate) {
$('#duplicate-review').modal();
} else {
fetch('/reviews/check_zero_hours_per_week/', {
method: 'POST',
body: formData,
headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
})
.then(r => r.json())
.then(check => {
if (check.zero && !zeroHoursModalShown) {
$('#zero-hours-review').modal();
zeroHoursModalShown = true;
} else {
document.getElementById('submitBtn').disabled = true;
form.submit();
}
});
}
});
document.getElementById('reviewForm').addEventListener('submit', function(e) {
e.preventDefault();
var form = this;
var formData = new FormData(form);
fetch('/reviews/check_duplicate/', {
method: 'POST',
body: formData,
headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
})
.then(r => r.json())
.then(check => {
if (check.duplicate) {
$('#duplicate-review').modal();
} else {
fetch('/reviews/check_zero_hours_per_week/', {
method: 'POST',
body: formData,
headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken') }
})
.then(r => r.json())
.then(check => {
if (check.zero && !zeroHoursModalShown) {
$('#zero-hours-review').modal();
zeroHoursModalShown = true;
} else {
document.getElementById('submitBtn').disabled = true;
form.submit();
}
})
.catch(() => { form.submit(); });
}
})
.catch(() => { form.submit(); });
🤖 Prompt for AI Agents
In `@tcf_website/templates/reviews/new_review.html` around lines 206 - 237, The
submit handler attached to document.getElementById('reviewForm') prevents
default but has no error handling for the two fetch flows (the
/reviews/check_duplicate/ and /reviews/check_zero_hours_per_week/ requests), so
network/server errors leave the form unusable; update the fetch chains in that
handler to (1) check response.ok and throw on non-2xx before r.json(), (2) add
.catch handlers for both fetch calls that restore form usability by enabling
document.getElementById('submitBtn') and either submitting the form or showing a
user-facing error, and (3) ensure zeroHoursModalShown logic is preserved so
repeated submits don’t re-open the modal unexpectedly.

Comment on lines +115 to +116
if latest_semester is None:
raise Http404("No semester data loaded. Run migrations and ensure the database has semester data.")
Copy link
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: line too long — CI is failing.

Line 116 is 107 characters, exceeding the 100-character pylint limit. Split the message or move it to a constant.

Proposed fix
     if latest_semester is None:
-        raise Http404("No semester data loaded. Run migrations and ensure the database has semester data.")
+        raise Http404(
+            "No semester data loaded. "
+            "Run migrations and ensure the database has semester data."
+        )
🧰 Tools
🪛 GitHub Actions: Continuous Integration

[error] 116-116: pylint: Line too long (107/100) (line-too-long)

🪛 Ruff (0.14.14)

[warning] 116-116: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In `@tcf_website/views/browse.py` around lines 115 - 116, The Http404 message
raised when latest_semester is None in the browse view is longer than 100 chars;
shorten or extract it to a constant to satisfy pylint. Update the raise in the
function containing latest_semester to use either a shorter string (e.g., "No
semester data loaded. Run migrations.") or a named constant (e.g.,
NO_SEMESTER_ERROR = "No semester data loaded. Run migrations and ensure the
database has semester data.") and reference that constant in the raise
Http404(...) call so the line length is under 100 characters.

Comment on lines +95 to +97
# Only in dev, when API returns no events (e.g. still 403), show sample data so the calendar UI is visible
if settings.DEBUG and not events:
events = _sample_events_for_dev()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Line too long (110/100) — pipeline failure on Line 95.

The inline comment exceeds the 100-character limit.

Proposed fix
-    # Only in dev, when API returns no events (e.g. still 403), show sample data so the calendar UI is visible
+    # In dev, when API returns no events (e.g. 403),
+    # show sample data so the calendar UI is visible.
     if settings.DEBUG and not events:
         events = _sample_events_for_dev()
📝 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
# Only in dev, when API returns no events (e.g. still 403), show sample data so the calendar UI is visible
if settings.DEBUG and not events:
events = _sample_events_for_dev()
# In dev, when API returns no events (e.g. 403),
# show sample data so the calendar UI is visible.
if settings.DEBUG and not events:
events = _sample_events_for_dev()
🧰 Tools
🪛 GitHub Actions: Continuous Integration

[error] 95-95: pylint: Line too long (110/100) (line-too-long)

🤖 Prompt for AI Agents
In `@tcf_website/views/calendar.py` around lines 95 - 97, The inline comment above
the dev-only sample data fallback is over the 100-char limit; shorten or wrap it
so the line length is <=100. Edit the comment that precedes the if block using
settings.DEBUG and events (the one explaining _sample_events_for_dev()) to a
shorter sentence or split into two shorter comment lines immediately above the
if statement so the if block and calls to settings.DEBUG and
_sample_events_for_dev() remain unchanged.

Comment on lines +145 to +147
)
def event_detail(request, event_uri):
"""Display detailed information for a specific event"""
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing blank lines between top-level function definitions.

PEP 8 (E302) requires two blank lines before a top-level function definition. event_detail starts immediately after calendar_overview with no separating blank lines.

Proposed fix
     )
+
+
 def event_detail(request, event_uri):
📝 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 event_detail(request, event_uri):
"""Display detailed information for a specific event"""
)
def event_detail(request, event_uri):
"""Display detailed information for a specific event"""
🤖 Prompt for AI Agents
In `@tcf_website/views/calendar.py` around lines 145 - 147, Insert the required
blank lines between the top-level function definitions: add two blank lines
between the end of calendar_overview and the start of event_detail so
event_detail is separated by a blank line pair per PEP8 (E302); locate the
definitions by the function names calendar_overview and event_detail and update
the spacing accordingly.

Comment on lines 142 to 199
def _handle_course_review_get(request, mode):
"""Handle GET for course reviews - require course context."""
course_id = request.GET.get("course")
instructor_id = request.GET.get("instructor")

# No context - need course ID, redirect to home
if not course_id:
messages.info(request, "Please select a course to review from a course page.")
return redirect("/browse?mode=courses")

course = get_object_or_404(Course, id=course_id)
latest = Semester.latest()

if instructor_id:
# Full context: course + instructor
instructor = get_object_or_404(Instructor, id=instructor_id)
instructors = None
# Semesters when this instructor taught this course (last 5 years)
semesters = (
Semester.objects.filter(
section__course=course,
section__instructors=instructor,
year__gte=latest.year - 5,
)
.distinct()
.order_by("-number")
)
else:
# Partial context: only course - let user pick instructor
instructor = None
instructors = (
Instructor.objects.filter(
section__course=course,
section__semester__year__gte=latest.year - 5,
hidden=False,
)
.distinct()
.order_by("last_name")[:50]
)
# All recent semesters for this course
semesters = (
Semester.objects.filter(section__course=course, year__gte=latest.year - 5)
.distinct()
.order_by("-number")
)

return render(
request,
"reviews/new_review.html",
{
"is_club": False,
"mode": mode,
"course": course,
"instructor": instructor,
"instructors": instructors,
"semesters": semesters,
},
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Semester.latest() may return None on an empty DB.

In _handle_course_review_get, latest = Semester.latest() is used without a None check (lines 153, 164, 175, 183). If no semesters exist, latest.year raises AttributeError. The department() view in browse.py (line 115) now guards against this — consider adding a similar guard here, or at minimum a clear early return/redirect.

🤖 Prompt for AI Agents
In `@tcf_website/views/review.py` around lines 142 - 199, In
_handle_course_review_get, guard against Semester.latest() returning None before
accessing latest.year by checking if latest is None and handling it (e.g.
messages.info(request, "No semesters available; please try later.") and return
redirect("/browse?mode=courses") or another suitable early return), so replace
direct uses of latest.year in the semesters queries with a branch that only runs
those queries when latest is not None and otherwise performs the early
redirect/response.

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.

4 participants