-
-
Notifications
You must be signed in to change notification settings - Fork 323
Replace full page reloads with HTMX for like/dislike actions #5359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Replace full page reloads with HTMX for like/dislike actions #5359
Conversation
|
👋 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:
Once a valid peer review is submitted, this check will pass automatically. Thank you! |
WalkthroughAdds 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
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)
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📊 Monthly LeaderboardHi @Nachiket-Roy! Here's how you rank for December 2025:
Leaderboard based on contributions in December 2025. Keep up the great work! 🚀 |
There was a problem hiding this 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: Fixupvote_issue/downvote_issueURL kwarg mismatch and clean up duplicate vote route patterns
Bug: URL kwarg names don't match view signatures (will crash)
upvote_issueanddownvote_issueroutes use<int:issue_id>butlike_issue()anddislike_issue()expectissue_pkparameter. Django will raiseTypeError: 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"),Duplicate URL patterns (recommended cleanup)
like_issue,dislike_issue,flag_issue,save_issue, andunsave_issuehave both oldre_pathentries (lines 588–612) and newpathentries (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-headersor 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 forhtmx:afterRequestmay cause issues.There are two
htmx:afterRequestlisteners (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-statesextension 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-statesextension 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.htmland_flag_modal.htmlshare 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 tweaksThe new toggle implementations for bookmarking and flagging are clear and HTMX‑friendly:
- They toggle the
issue_saved/issue_flagedrelations.- 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
successflag 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 computetotal_flag_votes; if you later render_like_dislike_share.htmlfrom any shared endpoint, consider also exposing aflags_countalias so all consumers can use a single naming convention (flags_countvsflags).- Similar to
like_issue/dislike_issue, you might want to restrictsave_issueandflag_issueto POST only using@require_POSTto 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
📒 Files selected for processing (9)
blt/urls.pywebsite/templates/base.htmlwebsite/templates/includes/_flag_modal.htmlwebsite/templates/includes/_like_dislike_share.htmlwebsite/templates/includes/_like_modal.htmlwebsite/templates/includes/_like_section.htmlwebsite/templates/profile.htmlwebsite/views/issue.pywebsite/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.createMessagefor form submission feedback provides consistent UX across the application.website/templates/includes/_like_modal.html (1)
49-53: No action needed. ThecloseLikeModal()function is defined in_like_dislike_share.htmland 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 variableflagerscorrectly 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: ThecloseFlagModal()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.
There was a problem hiding this 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 templateincludes/_follow_button.htmlexists.Past review comments flagged that this template doesn't exist in the codebase, which would cause
TemplateDoesNotExisterrors 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 fwebsite/views/issue.py (3)
227-248: Remove redundantis_authenticatedcheck.The
@login_requireddecorator on line 227 already ensuresrequest.user.is_authenticatedisTrue, 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_POSTtodislike_issueas 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_POSTto restrict state-changing operations.
like_issuemodifies 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_userfunction (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
DOMContentLoadedhandler adds duplicateclickandkeydownlisteners todocument. 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_stringis 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
📒 Files selected for processing (4)
blt/urls.pywebsite/templates/includes/_like_dislike_share.htmlwebsite/views/issue.pywebsite/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 usepositive_votes,negative_votes,flags_count,user_has_flagged) and the initial page load context (which may uselikes,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
flagersandlikerscontext variables, which are now correctly included in the HTMX view responses inissue.py.
30-33: Verify bookmark UI placement.The placeholder
div#bookmark-section-{{ object.id }}exists for HTMX updates, andsave_issueview 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
likersandflagerswithselect_related("user")for efficient queries, addressing the previous review about missing context for the "View Likes" and "View Flags" buttons.
There was a problem hiding this 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 buttonIf this fragment is ever rendered inside a
<form>, the defaulttype="submit"can trigger an unwanted full form submit in addition to the HTMX request. Consider explicitly settingtype="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_createwon’t raiseUserProfile.DoesNotExistThe
try/except UserProfile.DoesNotExistaroundUserProfile.objects.get_or_create(...)is effectively dead:get_or_createreturns(obj, created)or raises other exceptions (e.g.,IntegrityError), but notDoesNotExist. This makes the error path misleading and unlikely to ever execute.You can simplify by removing the
try/exceptentirely 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 listenersIf this fragment is rendered multiple times on a page (e.g., in issue lists), each inclusion redefines
openLikeModal/openFlagModaland 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-importingrender_to_stringinsidesave_issue
render_to_stringis already imported at the top of the file, so the inner import in the HTMX branch ofsave_issueis 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
📒 Files selected for processing (4)
website/templates/includes/_bookmark_section.htmlwebsite/templates/includes/_like_dislike_share.htmlwebsite/views/issue.pywebsite/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 browsersThe updated
copyClipboardimplementation correctly feature-checksnavigator.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 usersThe
like_issue,dislike_issue, andissue_votesimplementations correctly:
- Enforce authentication (
login_required) and POST-only semantics for state changes (@require_POSTon 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
There was a problem hiding this 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 performanceUsing
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
📒 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 enforcementThe addition of
require_POSTimport is appropriate for the newtoggle_followendpoint, aligning with the PR's goal of using POST for state-changing operations.
1897-1911: LGTM - Robust function setup and guardsThe 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 requestsThe 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.htmltemplate exists and correctly uses all three context variables (user,is_following,follower_count) passed by the view. No action needed.
There was a problem hiding this 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_createensures 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 aTemplateDoesNotExistexception (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 -20website/views/issue.py (2)
95-158: Add complete context and non-HTMX fallback tolike_issueThe HTMX response is missing context variables required for modals and user state consistency:
Missing modal data:
likersandflagersquerysets are needed if the template includes "View Likes" and "View Flags" modals (as indicated in past review comments and PR objectives).Missing user state:
user_has_flaggedanduser_has_savedshould be included so the entire like/dislike/flag/save UI state stays in sync after a like action.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 returnNone, 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 theflag_issuecontext and add fallbackThe function computes
user_voteanduser_has_saved(lines 2263-2268) but doesn't pass them to the template. Additionally, it's missinglikers/flagersfor 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_countinstead of reusingtotal_flag_votesfrom 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=Trueprevents crashes, failures are silently ignored without logging.For improved responsiveness, consider using Django's
send_mailwith 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 endpointThe
issue_votesfunction 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
GETmethod would be semantically appropriate. However, if it's exclusively called via HTMXhx-postattributes 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
📒 Files selected for processing (2)
website/views/issue.pywebsite/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_POSTimport is correctly added and appropriately used by the newtoggle_followendpoint to enforce POST-only access for state-changing operations.website/views/issue.py (2)
1878-1900: LGTM: Context enrichment for HTMX consistencyThe additions to
IssueView.get_context_datacorrectly:
- 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 foundThe
_bookmark_section.htmltemplate is present atwebsite/templates/includes/_bookmark_section.html. The implementation is correct and will not encounter runtime errors on the HTMX branch.
There was a problem hiding this 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_votesendpoint 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 variablesappears 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
📒 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_issueendpoint 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/flagersquerysets for modals)- Returns JSON fallback for non-HTMX clients
The use of
select_related("user")on thelikers/flagersquerysets (lines 161-162) efficiently prevents N+1 queries when rendering the modal user lists.
178-229: LGTM! Consistent implementation withlike_issue.The
dislike_issueendpoint correctly mirrors the like functionality with:
- Proper toggle logic (removes upvote if present, toggles downvote)
- Complete HTMX context including modal data (
likers,flagersat 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_issueendpoint now properly:
- Restricts to POST requests (
@require_POSTadded per previous feedback)- Toggles bookmark state with appropriate user feedback
- Renders HTMX partial or JSON fallback
The implementation relies on
includes/_bookmark_section.htmlexisting (referenced in AI summary as present).
2278-2333: LGTM! Flag toggle correctly integrated with HTMX.The
flag_issueendpoint has been properly updated to address all previous feedback:
@require_POSTdecorator 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/flagersquerysets (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_issueanddislike_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.
There was a problem hiding this 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 actionThe email notification is sent without error handling. If
send_mailfails (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=Trueto thesend_mailcall 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
📒 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_dataproperly 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
likersandflagersquerysets withselect_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.
There was a problem hiding this 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-onlyissue_votesendpoint.This endpoint correctly omits
@require_POSTsince 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
📒 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 withlike_issueimplementation.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_issuefunction 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.htmltemplate (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.htmltemplate exists and is properly implemented with correct context variable handling (objectanduser_has_saved). Thesave_issuefunction logic is sound and will not encounter runtime errors.
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:
Summary by CodeRabbit
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.