Skip to content

Conversation

@Nachiket-Roy
Copy link
Contributor

@Nachiket-Roy Nachiket-Roy commented Dec 22, 2025

Summary

Implement HTMX-powered like/dislike system for issues

Changes:

Added HTMX endpoints for like/dislike/flag/save operations on issues
Enhanced user profile with HTMX-based follow/unfollow functionality
Implemented modals for displaying likers and flaggers lists
Added HTMX global configuration for better UX with loading states and JSON encoding
Updated templates to use HTMX attributes instead of JavaScript form submissions

Key Features:

  • Like/Dislike issues without page reload
  • Flag issues with toggle functionality
  • Follow/unfollow users without page refresh
  • Toast notifications for all HTMX responses
  • Loading indicators during requests

Summary by CodeRabbit

  • New Features

    • HTMX-driven issue interactions: like, dislike, flag, save, and a vote/state refresh endpoint
    • Live follow/unfollow with follower counts and reusable follow button (HTMX)
    • View "Liked By" and "Flagged By" lists in modals
    • Bookmark button with live toggle and quick-share (clipboard) fallback
    • Global HTMX UX: centralized toast notifications, loading-state handling, and event hooks
  • Bug Fixes

    • Fixed typo in followers section label

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions
Copy link
Contributor

👋 Hi @Nachiket-Roy!

This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:

  • The PR author
  • DonnieBLT
  • coderabbitai
  • copilot

Once a valid peer review is submitted, this check will pass automatically. Thank you!

@github-actions github-actions bot added files-changed: 9 PR changes 9 files needs-peer-review PR needs peer review labels Dec 22, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

Walkthrough

Adds HTMX-first endpoints and templates for issue interactions (like, dislike, flag, save, issue_votes) and follow/unfollow; migrates routing to explicit path entries; views return HTMX partials or JSON; base template gets global HTMX handlers; new modal/fragment templates and client-side modal/clipboard helpers added.

Changes

Cohort / File(s) Summary
URL Routing
blt/urls.py
Added explicit path routes and exports for issue actions and follow toggle: like_issue/<int:issue_pk>/, dislike_issue/<int:issue_pk>/, flag_issue/<int:issue_pk>/, save_issue/<int:issue_pk>/, issue_votes/<int:issue_pk>/, users/<str:username>/toggle-follow/. Removed admin re_path regex entries for like/dislike/flag.
Issue Interaction Views
website/views/issue.py
Added like_issue, dislike_issue, issue_votes, save_issue, flag_issue (POST-only). Endpoints toggle relations, recalc counts, optionally enqueue emails, and return HTMX fragments or JSON; templates expect normalized context keys (positive_votes, negative_votes, flags_count, user_vote, user_has_flagged, user_has_saved).
User Follow View
website/views/user.py
Added toggle_follow(request, username) (POST-only, login required). Toggles follow relation, computes follower_count, sends notification on new follower, returns HTMX partial (includes/_follow_button.html) or redirect for non-HX requests.
Global HTMX / Base Template
website/templates/base.html
Enabled HTMX extensions json-enc, loading-states; added global HTMX handlers (beforeRequest, afterRequest, afterSettle) to show JSON toasts, apply/restore loading states, and run afterSettle callbacks.
Like/Flag UI and Helpers
website/templates/includes/_like_section.html, website/templates/includes/_like_modal.html, website/templates/includes/_flag_modal.html, website/templates/includes/_like_dislike_share.html
New _like_section.html renders HTMX like/dislike/flag controls and counts; _like_modal.html and _flag_modal.html render lists of likers/flaggers; _like_dislike_share.html consolidates sharing (clipboard), modal open/close helpers, and HTMX wiring; removed legacy JS modal handlers.
Bookmark UI
website/templates/includes/_bookmark_section.html
Added bookmark fragment that POSTs to save_issue and swaps based on user_has_saved.
Follow Button Include
website/templates/includes/_follow_button.html
New HTMX-driven follow/unfollow fragment that posts to users/<username>/toggle-follow/, swaps follow-section-{{ user.id }}, and shows follower count.
Profile Template Adjustments
website/templates/profile.html
Replaced static follow button with HTMX-driven follow-section-{{ user.id }} wrapper, added follower count, and fixed "Follwings" → "Followings".

Sequence Diagram(s)

sequenceDiagram
    participant Browser as User/Browser
    participant HTMX as HTMX
    participant Server as Django View
    participant DB as Database
    participant Renderer as Template Renderer

    Note over Browser,Server: HTMX-driven issue action (like/dislike/flag/save/issue_votes)

    Browser->>HTMX: Click -> HX POST /like_issue/... or /save_issue/... or /issue_votes/...
    HTMX->>HTMX: beforeRequest (apply loading state)
    HTMX->>Server: HX POST (HX-Request header)
    Server->>DB: load issue, toggle relation (vote/flag/save), recalc counts
    alt send notification
        Server->>Server: enqueue/send email (optional)
    end
    Server->>Renderer: render partial (like/bookmark/modal) or produce JSON
    Server-->>HTMX: return HTML or JSON
    HTMX->>Browser: swap target element
    HTMX->>HTMX: afterSettle (show toast, restore state)
Loading
sequenceDiagram
    participant Browser as User/Browser
    participant HTMX as HTMX
    participant Server as Django View
    participant DB as Database
    participant Renderer as Template Renderer

    Note over Browser,Server: HTMX-driven follow toggle

    Browser->>HTMX: Click follow/unfollow -> HX POST /users/{username}/toggle-follow/
    HTMX->>Server: HX POST
    Server->>DB: toggle follow relation, compute follower_count
    alt new follower
        Server->>Server: send notification email (optional)
    end
    Server->>Renderer: render `includes/_follow_button.html`
    Server-->>HTMX: return partial HTML
    HTMX->>Browser: replace follow-section with updated HTML
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: implementing HTMX to replace full page reloads for like/dislike actions, matching the core changes across views, templates, and URL routing.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@github-actions
Copy link
Contributor

📊 Monthly Leaderboard

Hi @Nachiket-Roy! Here's how you rank for December 2025:

Rank User PRs Reviews Comments Total
🥇 #1 @Nachiket-Roy 19 29 54 414
#2 @Pritz395 8 22 50 268

Leaderboard based on contributions in December 2025. Keep up the great work! 🚀

@github-actions github-actions bot added the pre-commit: passed Pre-commit checks passed label Dec 22, 2025
@Nachiket-Roy Nachiket-Roy changed the title Replace full page reloads with HTMX for voting actions Replace full page reloads with HTMX for like/dislike actions Dec 22, 2025
@github-actions github-actions bot added the tests: passed Django tests passed label Dec 22, 2025
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: 5

Caution

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

⚠️ Outside diff range comments (1)
blt/urls.py (1)

181-221: Fix upvote_issue/downvote_issue URL kwarg mismatch and clean up duplicate vote route patterns

  1. Bug: URL kwarg names don't match view signatures (will crash)
    upvote_issue and downvote_issue routes use <int:issue_id> but like_issue() and dislike_issue() expect issue_pk parameter. Django will raise TypeError: like_issue() got an unexpected keyword argument 'issue_id'.

    Change both to use issue_pk:

    path("upvote_issue/<int:issue_pk>/", like_issue, name="upvote_issue"),
    path("downvote_issue/<int:issue_pk>/", dislike_issue, name="downvote_issue"),
  2. Duplicate URL patterns (recommended cleanup)
    like_issue, dislike_issue, flag_issue, save_issue, and unsave_issue have both old re_path entries (lines 588–612) and new path entries (lines 1255–1258). The old patterns match first, making the new ones unreachable. Consolidate to a single set once callers are migrated to the new syntax.

🧹 Nitpick comments (4)
website/templates/includes/_like_section.html (1)

5-21: Missing CSRF token for HTMX POST requests.

HTMX POST requests require CSRF token handling. While Django can read the CSRF token from cookies, it's more reliable to include it explicitly via hx-headers or ensure the HTMX configuration sends it automatically.

Additionally, the buttons don't show the current user's vote state (liked/disliked/flagged), which would improve UX by indicating the active state visually.

🔎 Suggested improvements
 {% load gravatar %}
 {% load custom_tags %}
 <div id="like-section-{{ object.id }}" class="flex items-center gap-4">
     <!-- Like -->
     <button hx-post="{% url 'like_issue' object.id %}"
             hx-target="#like-section-{{ object.id }}"
-            hx-swap="outerHTML">
-        ❤️ <span>{{ positive_votes }}</span>
+            hx-swap="outerHTML"
+            hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
+        {% if is_liked %}💖{% else %}❤️{% endif %} <span>{{ positive_votes }}</span>
     </button>
     <!-- Dislike -->
     <button hx-post="{% url 'dislike_issue' object.id %}"
             hx-target="#like-section-{{ object.id }}"
-            hx-swap="outerHTML">
-        💔 <span>{{ negative_votes }}</span>
+            hx-swap="outerHTML"
+            hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
+        {% if is_disliked %}💔{% else %}🖤{% endif %} <span>{{ negative_votes }}</span>
     </button>
     <!-- Flag -->
     <button hx-post="{% url 'flag_issue' object.id %}"
             hx-target="#like-section-{{ object.id }}"
-            hx-swap="outerHTML">
+            hx-swap="outerHTML"
+            hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
         🚩 <span>{{ flags_count }}</span>
     </button>
 </div>
website/templates/base.html (1)

140-154: Duplicate event listeners for htmx:afterRequest may cause issues.

There are two htmx:afterRequest listeners (lines 117-137 and 148-154). Both will execute, but the opacity/pointer-events restoration in the second listener may interfere with the loading-states extension you've added.

Consider consolidating into a single listener or relying on the loading-states extension for visual feedback instead of manual DOM manipulation.

🔎 Consolidated approach
-            // Show loading indicator during requests
-            document.body.addEventListener('htmx:beforeRequest', function(event) {
-                const target = event.detail.target;
-                if (target && target.style) {
-                    target.style.opacity = '0.6';
-                    target.style.pointerEvents = 'none';
-                }
-            });
-            
-            document.body.addEventListener('htmx:afterRequest', function(event) {
-                const target = event.detail.target;
-                if (target && target.style) {
-                    target.style.opacity = '1';
-                    target.style.pointerEvents = 'auto';
-                }
-            });

Instead, use the loading-states extension with CSS classes in your templates:

<button hx-post="..." data-loading-class="opacity-60 pointer-events-none">
website/templates/includes/_like_modal.html (1)

1-57: Consider extracting shared modal structure.

Both _like_modal.html and _flag_modal.html share nearly identical structure. Consider creating a reusable modal base template to reduce duplication and ease maintenance.

website/views/issue.py (1)

2160-2196: Save/flag HTMX toggles look good; consider minor consistency tweaks

The new toggle implementations for bookmarking and flagging are clear and HTMX‑friendly:

  • They toggle the issue_saved / issue_flaged relations.
  • HTMX requests return focused partials (_bookmark_section.html, _flag_section.html) with the right booleans (user_has_saved, user_has_flagged) and counts.
  • Non‑HTMX callers get simple JSON with a success flag and human‑readable message.

A couple of optional improvements to keep things consistent with the rest of the issue voting API:

  • For flag_issue, you already compute total_flag_votes; if you later render _like_dislike_share.html from any shared endpoint, consider also exposing a flags_count alias so all consumers can use a single naming convention (flags_count vs flags).
  • Similar to like_issue/dislike_issue, you might want to restrict save_issue and flag_issue to POST only using @require_POST to prevent state changes via GET and keep the semantics clear now that the HTMX buttons all post.

Overall, though, the toggle and HTMX behavior here looks solid.

Also applies to: 2249-2286

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 83b27ef and 826da88.

📒 Files selected for processing (9)
  • blt/urls.py
  • website/templates/base.html
  • website/templates/includes/_flag_modal.html
  • website/templates/includes/_like_dislike_share.html
  • website/templates/includes/_like_modal.html
  • website/templates/includes/_like_section.html
  • website/templates/profile.html
  • website/views/issue.py
  • website/views/user.py
🧰 Additional context used
🧬 Code graph analysis (2)
blt/urls.py (1)
website/views/issue.py (4)
  • like_issue (96-170)
  • dislike_issue (174-220)
  • flag_issue (2249-2286)
  • save_issue (2160-2196)
website/views/issue.py (1)
website/models.py (12)
  • UserProfile (896-1109)
  • Issue (590-739)
  • save (74-77)
  • save (277-289)
  • save (1400-1427)
  • save (1535-1538)
  • save (1728-1731)
  • save (1846-1860)
  • save (1951-1973)
  • save (2772-2775)
  • save (3304-3314)
  • save (3612-3621)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (5)
website/templates/profile.html (1)

65-65: Good fix: Typo corrected from "Follwings" to "Followings".

website/templates/base.html (1)

442-452: Good improvement: Unified toast message handling.

The update to use window.createMessage for form submission feedback provides consistent UX across the application.

website/templates/includes/_like_modal.html (1)

49-53: No action needed. The closeLikeModal() function is defined in _like_dislike_share.html and is properly accessible to the modal component.

Likely an incorrect or invalid review comment.

website/templates/includes/_flag_modal.html (2)

20-20: No changes needed. The template variable flagers correctly matches the context variable passed by the backend view (website/views/issue.py:1881), and this naming is consistently used across all related templates.

Likely an incorrect or invalid review comment.


49-53: The closeFlagModal() function is defined in _like_dislike_share.html (line 70), not missing. Both template includes work together in the same template context.

Likely an incorrect or invalid review comment.

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Dec 22, 2025
@github-actions github-actions bot added the changes-requested PR has requested changes from a reviewer label Dec 22, 2025
@github-actions github-actions bot added tests: failed Django tests failed and removed tests: passed Django tests passed labels Dec 22, 2025
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: 5

♻️ Duplicate comments (4)
website/views/user.py (1)

1916-1925: Verify that the template includes/_follow_button.html exists.

Past review comments flagged that this template doesn't exist in the codebase, which would cause TemplateDoesNotExist errors for HTMX requests.

Run the following script to verify the template exists:

#!/bin/bash
# Check if the follow button template exists
fd "_follow_button.html" --type f
website/views/issue.py (3)

227-248: Remove redundant is_authenticated check.

The @login_required decorator on line 227 already ensures request.user.is_authenticated is True, making the conditional check on line 240 redundant.

🔎 Proposed fix
 @login_required(login_url="/accounts/login")
 def issue_votes(request, issue_pk):
     """Return updated vote counts for HTMX"""
     issue = get_object_or_404(Issue, pk=issue_pk)

     total_upvotes = UserProfile.objects.filter(issue_upvoted=issue).count()
     total_downvotes = UserProfile.objects.filter(issue_downvoted=issue).count()
     total_flags = UserProfile.objects.filter(issue_flaged=issue).count()

-    user_vote = None
-    user_has_flagged = False
-    user_has_saved = False
-
-    if request.user.is_authenticated:
-        userprof = UserProfile.objects.get(user=request.user)
-        if userprof.issue_upvoted.filter(pk=issue.pk).exists():
-            user_vote = "upvote"
-        elif userprof.issue_downvoted.filter(pk=issue.pk).exists():
-            user_vote = "downvote"
-
-        user_has_flagged = userprof.issue_flaged.filter(pk=issue.pk).exists()
-        user_has_saved = userprof.issue_saved.filter(pk=issue.pk).exists()
+    userprof = UserProfile.objects.get(user=request.user)
+    
+    user_vote = None
+    if userprof.issue_upvoted.filter(pk=issue.pk).exists():
+        user_vote = "upvote"
+    elif userprof.issue_downvoted.filter(pk=issue.pk).exists():
+        user_vote = "downvote"
+
+    user_has_flagged = userprof.issue_flaged.filter(pk=issue.pk).exists()
+    user_has_saved = userprof.issue_saved.filter(pk=issue.pk).exists()

176-177: Add @require_POST to dislike_issue as well.

Same concern as like_issue — this state-changing operation should be restricted to POST requests.

🔎 Proposed fix
+@require_POST
 @login_required(login_url="/accounts/login")
 def dislike_issue(request, issue_pk):

95-96: Add @require_POST to restrict state-changing operations.

like_issue modifies database state (toggles upvotes) but accepts any HTTP method. State-changing endpoints should be restricted to POST requests for CSRF protection and REST compliance.

🔎 Proposed fix
+@require_POST
 @login_required(login_url="/accounts/login")
 def like_issue(request, issue_pk):
🧹 Nitpick comments (3)
website/views/user.py (1)

1908-1910: Consider sending email notifications for consistency.

The existing follow_user function (lines 1099-1108) sends email notifications when users are followed, but this new endpoint doesn't. This creates inconsistent user experience depending on which endpoint is used.

🔎 Add email notification when following
     else:
         follower_profile.follows.add(target_profile)
         is_following = True
         action = "followed"
+        
+        # Send email notification to the followed user
+        if target_user.email:
+            msg_plain = render_to_string(
+                "email/follow_user.html",
+                {"follower": request.user, "followed": target_user}
+            )
+            msg_html = render_to_string(
+                "email/follow_user.html",
+                {"follower": request.user, "followed": target_user}
+            )
+            send_mail(
+                "You got a new follower!!",
+                msg_plain,
+                settings.EMAIL_TO_STRING,
+                [target_user.email],
+                html_message=msg_html,
+            )
website/templates/includes/_like_dislike_share.html (1)

85-119: Consider preventing duplicate event listeners if template is reused.

If this template is included multiple times on a page (e.g., an issue list), the DOMContentLoaded handler adds duplicate click and keydown listeners to document. While functional, this causes handlers to execute multiple times per event.

🔎 Proposed fix using a guard flag
 document.addEventListener('DOMContentLoaded', function() {
+    // Prevent duplicate registration if template is included multiple times
+    if (window._likeDislikeModalListenersAttached) return;
+    window._likeDislikeModalListenersAttached = true;
+
     // Close modals when clicking outside
     document.addEventListener('click', function(event) {
website/views/issue.py (1)

2182-2183: Remove redundant import.

render_to_string is already imported at the top of the file (line 43). This inline import is unnecessary.

🔎 Proposed fix
     # Check for HTMX request
     if request.headers.get("HX-Request"):
-        from django.template.loader import render_to_string
-
         html = render_to_string(
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 826da88 and d69a3af.

📒 Files selected for processing (4)
  • blt/urls.py
  • website/templates/includes/_like_dislike_share.html
  • website/views/issue.py
  • website/views/user.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • blt/urls.py
🧰 Additional context used
🧬 Code graph analysis (1)
website/views/issue.py (1)
website/models.py (12)
  • UserProfile (896-1109)
  • Issue (590-739)
  • save (74-77)
  • save (277-289)
  • save (1400-1427)
  • save (1535-1538)
  • save (1728-1731)
  • save (1846-1860)
  • save (1951-1973)
  • save (2772-2775)
  • save (3304-3314)
  • save (3612-3621)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run Tests
  • GitHub Check: Run pre-commit
  • GitHub Check: docker-test
🔇 Additional comments (4)
website/templates/includes/_like_dislike_share.html (3)

5-6: LGTM! Context variable fallbacks correctly implemented.

The |default: filters properly handle both naming conventions, ensuring compatibility between HTMX responses (which use positive_votes, negative_votes, flags_count, user_has_flagged) and the initial page load context (which may use likes, dislikes, flags, isFlagged).


14-28: LGTM! View buttons correctly depend on context variables now provided by views.

The conditional rendering of "View Flags" and "View Likes" buttons relies on flagers and likers context variables, which are now correctly included in the HTMX view responses in issue.py.


30-33: Verify bookmark UI placement.

The placeholder div#bookmark-section-{{ object.id }} exists for HTMX updates, and save_issue view renders _bookmark_section.html, but no bookmark button or initial state is visible in this template. Is the bookmark control intentionally placed elsewhere, or should it be added here?

website/views/issue.py (1)

145-161: LGTM! HTMX context now includes all required variables.

The context correctly provides likers and flagers with select_related("user") for efficient queries, addressing the previous review about missing context for the "View Likes" and "View Flags" buttons.

@github-actions github-actions bot added files-changed: 10 PR changes 10 files and removed files-changed: 9 PR changes 9 files changes-requested PR has requested changes from a reviewer labels Dec 22, 2025
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: 2

🧹 Nitpick comments (4)
website/templates/includes/_bookmark_section.html (1)

1-16: Explicitly mark the HTMX bookmark button as a non‑submitting button

If this fragment is ever rendered inside a <form>, the default type="submit" can trigger an unwanted full form submit in addition to the HTMX request. Consider explicitly setting type="button" on the bookmark button.

Example change
-    <button hx-post="{% url 'save_issue' object.id %}"
+    <button type="button"
+            hx-post="{% url 'save_issue' object.id %}"
             hx-target="#bookmark-section-{{ object.id }}"
             hx-swap="outerHTML"
             hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
website/views/user.py (1)

1890-1907: Simplify profile retrieval; get_or_create won’t raise UserProfile.DoesNotExist

The try/except UserProfile.DoesNotExist around UserProfile.objects.get_or_create(...) is effectively dead: get_or_create returns (obj, created) or raises other exceptions (e.g., IntegrityError), but not DoesNotExist. This makes the error path misleading and unlikely to ever execute.

You can simplify by removing the try/except entirely or, if you want defensive handling, catching a broader exception and logging it.

Example simplification
-    try:
-        follower_profile, _ = UserProfile.objects.get_or_create(user=request.user)
-        target_profile, _ = UserProfile.objects.get_or_create(user=target_user)
-    except UserProfile.DoesNotExist:
-        if request.headers.get("HX-Request"):
-            return JsonResponse({"error": "User profile not found"}, status=404)
-        messages.error(request, "User profile not found")
-        return redirect("profile", slug=username)
+    follower_profile, _ = UserProfile.objects.get_or_create(user=request.user)
+    target_profile, _ = UserProfile.objects.get_or_create(user=target_user)
website/templates/includes/_like_dislike_share.html (1)

79-135: Consider hoisting modal JS to a shared script to avoid duplicate listeners

If this fragment is rendered multiple times on a page (e.g., in issue lists), each inclusion redefines openLikeModal/openFlagModal and re-attaches global click/keydown handlers. It works, but it’s not ideal.

Longer-term, consider moving the modal open/close logic and global listeners into a shared JS bundle or a base template block so they’re registered once per page, and keep this fragment purely declarative.

website/views/issue.py (1)

2163-2201: Minor clean-up: avoid re-importing render_to_string inside save_issue

render_to_string is already imported at the top of the file, so the inner import in the HTMX branch of save_issue is redundant and can be removed for clarity.

Example change
-    # Check for HTMX request
-    if request.headers.get("HX-Request"):
-        from django.template.loader import render_to_string
-
-        html = render_to_string(
+    # HTMX response
+    if request.headers.get("HX-Request"):
+        html = render_to_string(
             "includes/_bookmark_section.html",
             {
                 "object": issue,
                 "user_has_saved": is_saved,
             },
         )

You may also consider extracting a small helper to build the standard JSON payload for bookmark state if you expect to expand this API, but that’s optional.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between d69a3af and c629636.

📒 Files selected for processing (4)
  • website/templates/includes/_bookmark_section.html
  • website/templates/includes/_like_dislike_share.html
  • website/views/issue.py
  • website/views/user.py
🧰 Additional context used
🧬 Code graph analysis (2)
website/views/user.py (1)
website/models.py (1)
  • UserProfile (896-1109)
website/views/issue.py (2)
website/models.py (12)
  • Issue (590-739)
  • UserProfile (896-1109)
  • save (74-77)
  • save (277-289)
  • save (1400-1427)
  • save (1535-1538)
  • save (1728-1731)
  • save (1846-1860)
  • save (1951-1973)
  • save (2772-2775)
  • save (3304-3314)
  • save (3612-3621)
website/api/views.py (9)
  • get (282-287)
  • get (341-346)
  • get (365-368)
  • get (462-475)
  • get (488-494)
  • get (550-576)
  • get (609-633)
  • filter (375-414)
  • filter (844-911)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (2)
website/templates/includes/_like_dislike_share.html (1)

38-76: Clipboard helper now covers modern and legacy browsers

The updated copyClipboard implementation correctly feature-checks navigator.clipboard, falls back to a temporary <textarea> + execCommand('copy'), and only shows a success message when a copy attempt actually occurs. This looks robust and backwards compatible.

website/views/issue.py (1)

95-176: HTMX vote endpoints look consistent and safely constrained to POST/authorized users

The like_issue, dislike_issue, and issue_votes implementations correctly:

  • Enforce authentication (login_required) and POST-only semantics for state changes (@require_POST on like/dislike).
  • Maintain mutual exclusivity between upvote/downvote for a user.
  • Compute updated counts and per-user state and feed a single shared HTMX fragment (includes/_like_dislike_share.html) with the right context keys (object, user_vote, user_has_flagged, user_has_saved, positive_votes, negative_votes, flags_count, likers, flagers).
  • Provide JSON fallbacks with enough information for non-HTMX/API consumers.

This is a solid structure and should work well with the new template fragments and modals.

Also applies to: 177-226, 229-276

@github-actions github-actions bot added tests: passed Django tests passed and removed tests: failed Django tests failed labels Dec 22, 2025
@github-actions github-actions bot removed the last-active: 1d PR last updated 1 day ago label Jan 2, 2026
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 (1)
website/views/user.py (1)

1913-1913: Optimize the follow-state check for better performance

Using target_profile in follower_profile.follows.all() loads the entire follows queryset into memory before checking membership. For users with many follows, this is inefficient.

🔎 Proposed optimization using filter().exists()
-    if target_profile in follower_profile.follows.all():
+    if follower_profile.follows.filter(pk=target_profile.pk).exists():
         follower_profile.follows.remove(target_profile)
         is_following = False
         action = "unfollowed"

This performs the membership check at the database level without loading all follow relationships into memory.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between d17e50b and 228efee.

📒 Files selected for processing (1)
  • website/views/user.py
🧰 Additional context used
🧬 Code graph analysis (1)
website/views/user.py (1)
website/models.py (1)
  • UserProfile (896-1109)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run Tests
  • GitHub Check: Run pre-commit
  • GitHub Check: docker-test
🔇 Additional comments (4)
website/views/user.py (4)

30-30: LGTM - Import addition for POST enforcement

The addition of require_POST import is appropriate for the new toggle_follow endpoint, aligning with the PR's goal of using POST for state-changing operations.


1897-1911: LGTM - Robust function setup and guards

The function setup properly handles:

  • POST-only enforcement via decorator
  • Login requirement
  • Self-follow prevention with appropriate responses for both HTMX and non-HTMX clients
  • Profile existence via get_or_create (addressing previous concerns about AttributeError)

1931-1948: LGTM - Clean response handling for both HTMX and standard requests

The dual-path response logic properly serves:

  • HTMX requests with an HTML fragment swap
  • Standard form submissions with a user-facing message and redirect

The follower count is correctly computed and passed to the template context.


1935-1944: The _follow_button.html template exists and correctly uses all three context variables (user, is_following, follower_count) passed by the view. No action needed.

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 (3)
website/views/user.py (1)

1897-1948: Well-structured HTMX toggle implementation.

The function correctly implements the follow/unfollow toggle with proper handling for both HTMX and non-HTMX requests. The previous double-toggle bug has been fixed, and defensive get_or_create ensures UserProfile availability.

Verify the template exists before merging.

The HTMX response at lines 1935-1943 renders includes/_follow_button.html. Previous reviews flagged this template as missing, which would cause a TemplateDoesNotExist exception (500 error) for HTMX requests.

#!/bin/bash
# Verify the _follow_button.html template exists
echo "Searching for _follow_button.html template..."
fd "_follow_button.html" --type f

# Also check the includes directory structure
echo -e "\nListing templates in includes directory:"
find . -path "*/templates/includes/*" -name "*.html" -type f | head -20
website/views/issue.py (2)

95-158: Add complete context and non-HTMX fallback to like_issue

The HTMX response is missing context variables required for modals and user state consistency:

  1. Missing modal data: likers and flagers querysets are needed if the template includes "View Likes" and "View Flags" modals (as indicated in past review comments and PR objectives).

  2. Missing user state: user_has_flagged and user_has_saved should be included so the entire like/dislike/flag/save UI state stays in sync after a like action.

  3. No fallback for non-HTMX requests: The function only returns inside the if request.headers.get("HX-Request") block. Non-HTMX POST requests (e.g., from API clients, curl, or proxies that strip headers) will implicitly return None, causing a Django error.

🔎 Recommended fix
     total_upvotes = UserProfile.objects.filter(issue_upvoted=issue).count()
     total_downvotes = UserProfile.objects.filter(issue_downvoted=issue).count()
+    total_flags = UserProfile.objects.filter(issue_flaged=issue).count()
+    
+    user_has_flagged = userprof.issue_flaged.filter(pk=issue.pk).exists()
+    user_has_saved = userprof.issue_saved.filter(pk=issue.pk).exists()

     # Check for HTMX request
     if request.headers.get("HX-Request"):
         html = render_to_string(
             "includes/_like_section.html",
             {
                 "object": issue,
                 "positive_votes": total_upvotes,
                 "negative_votes": total_downvotes,
-                "flags_count": UserProfile.objects.filter(issue_flaged=issue).count(),
+                "flags_count": total_flags,
                 "user_vote": "upvote" if is_liked else None,
+                "user_has_flagged": user_has_flagged,
+                "user_has_saved": user_has_saved,
+                "likers": UserProfile.objects.filter(issue_upvoted=issue).select_related("user"),
+                "flagers": UserProfile.objects.filter(issue_flaged=issue).select_related("user"),
             },
             request=request,
         )
         return HttpResponse(html)
+    
+    # Fallback for non-HTMX POST requests
+    return JsonResponse(
+        {
+            "likes": total_upvotes,
+            "dislikes": total_downvotes,
+            "flags": total_flags,
+            "user_vote": "upvote" if is_liked else None,
+        }
+    )

2243-2283: Complete the flag_issue context and add fallback

The function computes user_vote and user_has_saved (lines 2263-2268) but doesn't pass them to the template. Additionally, it's missing likers/flagers for modals and lacks a non-HTMX fallback.

🔎 Recommended fix
     userprof.save()
     total_flag_votes = UserProfile.objects.filter(issue_flaged=issue).count()
     total_upvotes = UserProfile.objects.filter(issue_upvoted=issue).count()
     total_downvotes = UserProfile.objects.filter(issue_downvoted=issue).count()

     # Derive user vote/save state for consistency with other endpoints
     user_vote = None
     if userprof.issue_upvoted.filter(pk=issue.pk).exists():
         user_vote = "upvote"
     elif userprof.issue_downvoted.filter(pk=issue.pk).exists():
         user_vote = "downvote"
     user_has_saved = userprof.issue_saved.filter(pk=issue.pk).exists()

     # Check for HTMX request
     if request.headers.get("HX-Request"):
         html = render_to_string(
             "includes/_like_section.html",
             {
                 "object": issue,
                 "positive_votes": total_upvotes,
                 "negative_votes": total_downvotes,
-                "flags_count": UserProfile.objects.filter(issue_flaged=issue).count(),
+                "flags_count": total_flag_votes,
                 "user_vote": user_vote,
+                "user_has_flagged": is_flagged,
+                "user_has_saved": user_has_saved,
+                "likers": UserProfile.objects.filter(issue_upvoted=issue).select_related("user"),
+                "flagers": UserProfile.objects.filter(issue_flaged=issue).select_related("user"),
             },
             request=request,
         )
         return HttpResponse(html)
+    
+    # Fallback for non-HTMX POST requests
+    return JsonResponse(
+        {
+            "likes": total_upvotes,
+            "dislikes": total_downvotes,
+            "flags": total_flag_votes,
+            "user_vote": user_vote,
+            "user_has_flagged": is_flagged,
+            "user_has_saved": user_has_saved,
+        }
+    )

Minor efficiency note: Line 2278 re-queries flags_count instead of reusing total_flag_votes from line 2258.

🧹 Nitpick comments (2)
website/views/user.py (1)

1922-1929: Consider async email delivery for better UX.

The email notification is sent synchronously, which could delay the response if the mail server is slow. While fail_silently=True prevents crashes, failures are silently ignored without logging.

For improved responsiveness, consider using Django's send_mail with a task queue (e.g., Celery) or at minimum, log failures for debugging.

🔎 Optional: Add logging for email failures
         if target_user.email:
-            send_mail(
-                "You got a new follower!!",
-                f"{request.user.username} started following you.",
-                settings.EMAIL_TO_STRING,
-                [target_user.email],
-                fail_silently=True,
-            )
+            try:
+                send_mail(
+                    "You got a new follower!!",
+                    f"{request.user.username} started following you.",
+                    settings.EMAIL_TO_STRING,
+                    [target_user.email],
+                )
+            except Exception as e:
+                logger.warning(f"Failed to send follower notification email to {target_user.email}: {e}")
website/views/issue.py (1)

197-245: Well-implemented vote state endpoint

The issue_votes function correctly provides complete context for HTMX updates and includes a sensible JSON fallback for non-HTMX requests. The context includes all necessary data for modals (likers, flagers) and user state (user_vote, user_has_flagged, user_has_saved).

Optional consideration: Since this endpoint only reads state without modification, a GET method would be semantically appropriate. However, if it's exclusively called via HTMX hx-post attributes for consistency with the action endpoints, the current pattern (no explicit @require_POST) allows flexibility while the frontend controls the method.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 228efee and feae389.

📒 Files selected for processing (2)
  • website/views/issue.py
  • website/views/user.py
🧰 Additional context used
🧬 Code graph analysis (1)
website/views/user.py (1)
website/models.py (1)
  • UserProfile (896-1109)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run Tests
  • GitHub Check: Run pre-commit
  • GitHub Check: docker-test
🔇 Additional comments (3)
website/views/user.py (1)

30-30: LGTM!

The require_POST import is correctly added and appropriately used by the new toggle_follow endpoint to enforce POST-only access for state-changing operations.

website/views/issue.py (2)

1878-1900: LGTM: Context enrichment for HTMX consistency

The additions to IssueView.get_context_data correctly:

  • Map legacy keys to canonical names (positive_votes, negative_votes, flags_count)
  • Derive user-specific state (user_vote, user_has_flagged, user_has_saved)
  • Handle unauthenticated users gracefully with appropriate defaults

This ensures the initial page load has the same context structure as HTMX updates, preventing UI inconsistencies.


2155-2191: Template exists—no issues found

The _bookmark_section.html template is present at website/templates/includes/_bookmark_section.html. The implementation is correct and will not encounter runtime errors on the HTMX branch.

@github-actions github-actions bot added the changes-requested PR has requested changes from a reviewer label Jan 2, 2026
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 (1)
website/views/issue.py (1)

232-280: Remove stale development comment.

The issue_votes endpoint is correctly implemented as a read-only state fetcher (appropriately without @require_POST). It provides complete context for HTMX partial rendering and JSON fallback.

However, line 263's comment # REQUIRED: Add these context variables appears to be a leftover from development, as the variables are already present at lines 264-265. Please remove it.

🔎 Cleanup suggestion
                 "positive_votes": total_upvotes,
                 "negative_votes": total_downvotes,
                 "flags_count": total_flags,
-                # REQUIRED: Add these context variables
                 "likers": UserProfile.objects.filter(issue_upvoted=issue).select_related("user"),
                 "flagers": UserProfile.objects.filter(issue_flaged=issue).select_related("user"),
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between feae389 and db0c0be.

📒 Files selected for processing (1)
  • website/views/issue.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (5)
website/views/issue.py (5)

95-175: LGTM! Complete HTMX integration with proper fallbacks.

The like_issue endpoint correctly:

  • Enforces POST-only requests and authentication
  • Toggles like state and removes conflicting downvotes
  • Preserves existing email notification logic
  • Provides complete context for HTMX partial rendering (including likers/flagers querysets for modals)
  • Returns JSON fallback for non-HTMX clients

The use of select_related("user") on the likers/flagers querysets (lines 161-162) efficiently prevents N+1 queries when rendering the modal user lists.


178-229: LGTM! Consistent implementation with like_issue.

The dislike_issue endpoint correctly mirrors the like functionality with:

  • Proper toggle logic (removes upvote if present, toggles downvote)
  • Complete HTMX context including modal data (likers, flagers at lines 214-215)
  • JSON fallback for non-HTMX requests
  • Efficient queries with select_related("user")

All issues flagged in previous reviews (undefined user_vote, missing context, missing fallback) have been resolved.


2190-2226: LGTM! Bookmark toggle correctly implemented.

The save_issue endpoint now properly:

  • Restricts to POST requests (@require_POST added per previous feedback)
  • Toggles bookmark state with appropriate user feedback
  • Renders HTMX partial or JSON fallback

The implementation relies on includes/_bookmark_section.html existing (referenced in AI summary as present).


2278-2333: LGTM! Flag toggle correctly integrated with HTMX.

The flag_issue endpoint has been properly updated to address all previous feedback:

  • @require_POST decorator added (line 2278)
  • Renders the correct shared template _like_section.html (line 2308)
  • Computes complete vote state (lines 2293-2295) and user state (lines 2298-2303)
  • Provides full context including likers/flagers querysets (lines 2317-2318)
  • Returns JSON fallback for non-HTMX clients

The implementation correctly maintains UI consistency by using the same template and context structure as like_issue and dislike_issue.


1913-1935: LGTM! Context additions provide consistent state for HTMX updates.

This segment properly initializes the context for the issue detail page:

  • Maps legacy keys (likes, dislikes, flags) to canonical names (positive_votes, negative_votes, flags_count) for template consistency
  • Derives authenticated user's state (user_vote, user_has_flagged, user_has_saved) from their profile
  • Provides safe defaults for unauthenticated users

The context structure matches what the HTMX endpoints (like_issue, dislike_issue, etc.) return, ensuring seamless partial updates without full page reloads.

@github-actions github-actions bot removed the changes-requested PR has requested changes from a reviewer label Jan 2, 2026
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

🧹 Nitpick comments (1)
website/views/issue.py (1)

112-140: Wrap email notification in error handling to prevent blocking like action

The email notification is sent without error handling. If send_mail fails (SMTP issues, network errors, etc.), the entire like action could fail, preventing the user from liking the issue.

Wrap the email sending logic in a try-except block and log errors, or add fail_silently=True to the send_mail call to ensure email failures don't impact the core functionality.

🔎 Recommended fix
         is_liked = True
 
         # Send notification email (keep existing logic)
         if issue.user is not None:
+            try:
                 liked_user = issue.user
                 liker_user = request.user
                 issue_pk = issue.pk
                 msg_plain = render_to_string(
                     "email/issue_liked.html",
                     {
                         "liker_user": liker_user.username,
                         "liked_user": liked_user.username,
                         "issue_pk": issue_pk,
                     },
                 )
                 msg_html = render_to_string(
                     "email/issue_liked.html",
                     {
                         "liker_user": liker_user.username,
                         "liked_user": liked_user.username,
                         "issue_pk": issue_pk,
                     },
                 )
 
                 send_mail(
                     "Your issue got an upvote!!",
                     msg_plain,
                     settings.EMAIL_TO_STRING,
                     [liked_user.email],
                     html_message=msg_html,
                 )
+            except Exception as e:
+                logger.warning(f"Failed to send like notification email: {e}")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 70ee754 and 6af99e0.

📒 Files selected for processing (1)
  • website/views/issue.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run pre-commit
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (1)
website/views/issue.py (1)

1912-1937: Nice work on the context data improvements!

The additions to get_context_data properly address the missing context issues flagged in previous reviews. The implementation:

  • Maps existing vote/flag counts to canonical names for consistency
  • Computes user-specific states (vote, flag, save) needed by templates
  • Includes likers and flagers querysets with select_related("user") to prevent N+1 queries when rendering modals
  • Maintains backward compatibility by keeping both legacy and new key names

This ensures the issue detail page has all the data needed for both initial render and HTMX updates.

@github-actions github-actions bot added the changes-requested PR has requested changes from a reviewer label Jan 2, 2026
@Nachiket-Roy Nachiket-Roy marked this pull request as draft January 2, 2026 14:45
@Nachiket-Roy Nachiket-Roy marked this pull request as ready for review January 2, 2026 14:49
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 (1)
website/views/issue.py (1)

232-279: Consider adding rate limiting for the read-only issue_votes endpoint.

This endpoint correctly omits @require_POST since it's a read-only query for vote counts. However, since it performs multiple database queries (counts + user state checks), consider adding rate limiting to prevent abuse if it's exposed to frequent polling.

If the endpoint is only called on-demand (e.g., on page load or explicit user refresh), the current implementation is fine.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 6af99e0 and ea5a9e2.

📒 Files selected for processing (1)
  • website/views/issue.py
🧰 Additional context used
🧬 Code graph analysis (1)
website/views/issue.py (1)
website/models.py (12)
  • Issue (590-739)
  • UserProfile (896-1109)
  • save (74-77)
  • save (277-289)
  • save (1400-1427)
  • save (1535-1538)
  • save (1728-1731)
  • save (1846-1860)
  • save (1951-1973)
  • save (2772-2775)
  • save (3304-3314)
  • save (3612-3621)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run Tests
  • GitHub Check: Run pre-commit
  • GitHub Check: docker-test
🔇 Additional comments (5)
website/views/issue.py (5)

95-175: LGTM! Well-structured HTMX endpoint with proper fallbacks.

The function correctly:

  • Enforces POST-only access and authentication
  • Handles vote toggling logic (removing opposite vote before adding)
  • Preserves email notifications for issue authors
  • Provides complete context for HTMX (including modal data for likers/flagers)
  • Returns appropriate JSON fallback for non-HTMX clients

178-229: LGTM! Consistent with like_issue implementation.

The function properly mirrors the like endpoint with appropriate logic for downvotes. Context and response handling are complete and consistent.


2280-2335: LGTM! Complete implementation with proper context.

The flag_issue function correctly:

  • Enforces POST and authentication
  • Toggles flag state without conflicts with other vote types
  • Computes and provides complete vote/flag/save state for the UI
  • Renders the shared _like_dislike_share.html template (avoiding the non-existent template issue)
  • Returns comprehensive JSON for non-HTMX clients

1912-1937: LGTM! Context properly unified for both initial page load and HTMX updates.

The modifications correctly:

  • Map legacy context keys to canonical names for consistency across initial render and HTMX responses
  • Compute user-specific vote/flag/save state safely (checking authentication first)
  • Provide modal data (likers/flagers) with query optimization via select_related
  • Maintain backward compatibility with existing template code

2192-2228: No action required. The _bookmark_section.html template exists and is properly implemented with correct context variable handling (object and user_has_saved). The save_issue function logic is sound and will not encounter runtime errors.

@github-actions github-actions bot added last-active: 0d PR last updated 0 days ago last-active: 1d PR last updated 1 day ago and removed last-active: 0d PR last updated 0 days ago last-active: 1d PR last updated 1 day ago labels Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-requested PR has requested changes from a reviewer files-changed: 11 PR changes 11 files last-active: 1d PR last updated 1 day ago needs-peer-review PR needs peer review pre-commit: passed Pre-commit checks passed quality: high tests: passed Django tests passed

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

1 participant