Skip to content

Conversation

@sidd190
Copy link
Contributor

@sidd190 sidd190 commented Dec 10, 2025

Summary

Implements a comprehensive reporting system that allows users to report suspicious bug reports and provides admins with efficient tools to review and manage these reports.

Problem

Fixes #2229
Previously, there was no mechanism for the community to flag potentially fraudulent, spam, or inappropriate bug reports. This made it difficult to maintain content quality and required manual monitoring by administrators.

Solution

  • User Reporting Interface: Added "Report Issue" button on issue pages for authenticated users (excluding issue owners)
  • Admin Review Dashboard: Created dedicated interfaces for administrators to review, manage, and track report status
  • Contextual Indicators: Added visual indicators on issue pages when reports exist, allowing admins to quickly identify flagged content
  • Structured Workflow: Implemented status tracking (Pending → Reviewed → Resolved/Dismissed) with admin notes and audit trail
Screenshot from 2025-12-10 22-39-05 Screenshot from 2025-12-10 22-40-58 Screenshot from 2025-12-10 22-41-29

Key Features

  • Report Submission: Users can report issues with categorized reasons (spam, duplicate, fake, inappropriate, other) and detailed descriptions
  • Duplicate Prevention: Users cannot report the same issue multiple times or report their own issues
  • Admin Dashboard: Centralized view of all reports with filtering and status management
  • Issue-Specific Reports: Direct access to reports for individual issues with contextual information
  • Visual Indicators: Alert badges on issues with pending reports for immediate admin attention
  • Navigation Integration: Added reports link to admin sidebar for easy access

Summary by CodeRabbit

Release Notes

  • New Features
    • Users can now report issues by selecting a reason and providing a description. The system prevents duplicate reports from the same user per issue.
    • Administrators can view all issue reports, manage status updates, add review notes, and track reports for specific issues through a new admin interface.
    • Issue detail pages now display report counts and pending indicators for admin visibility.

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

@github-actions
Copy link
Contributor

👋 Hi @sidd190!

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 migrations PR contains database migration files needs-peer-review PR needs peer review labels Dec 10, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Introduces an issue reporting system allowing authenticated users to report suspicious bug reports. Includes a new IssueReport model, database migration, four URL routes, six view functions for handling report submission and admin management, admin UI templates, user-facing report modal, and navigation updates.

Changes

Cohort / File(s) Summary
Data Layer
website/migrations/0261_add_issue_reporting_system.py, website/models.py
Added new IssueReport model with fields: reason (choices: spam, duplicate, fake, inappropriate, other), description, status (choices: pending, reviewed, resolved, dismissed; default pending), timestamps (created, reviewed_at), admin_notes, and foreign keys to User (reporter, reviewed_by) and Issue (reported_issue). Includes unique constraint on (reporter, reported_issue) and descending-created ordering.
URL Routing
blt/urls.py
Added four new URL patterns: report_issue/ for submitting reports, issue-reports/ for admin listing, issue-reports/<int:report_id>/update/ for status updates, and issue/<int:issue_id>/reports/ for viewing issue-specific reports.
View Functions
website/views/issue.py
Implemented report_issue() to handle report submissions with duplicate and ownership checks, view_issue_reports() for admin listing with pending counts, update_report_status() for updating report status and admin notes, view_issue_specific_reports() for filtering reports by issue, and helper functions get_issue_reports_count() and get_issue_pending_reports_count().
Admin UI Templates
website/templates/admin/issue_reports.html, website/templates/admin/issue_specific_reports.html, website/templates/includes/admin_sidenav.html
Added templates for listing all issue reports with status badges and actions, viewing reports for a specific issue with modal-based editing, and new "Issue Reports" navigation item in admin sidenav with conditional highlighting.
User Context & Indicators
website/templates/includes/issue_right_info.html
Added admin-only section displaying issue report count, pending badge, and link to view issue-specific reports.
User-Facing UI
website/templates/issue.html
Added "Report Issue" button and modal form for authenticated non-owner users to submit reports; includes reason dropdown, description textarea, client-side form validation, and CSRF-protected POST submission.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • View functions (report_issue, update_report_status, view_issue_specific_reports): Verify permission checks, validation logic, duplicate prevention, and CSRF handling in both view and template form submissions.
  • Model constraints and relationships: Confirm the unique constraint on (reporter, reported_issue) and foreign key cascade/set-null behaviors align with intended behavior.
  • Client-side JavaScript: Review form validation, CSRF token handling, and error/success response handling in both issue.html and admin templates.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add User Reporting System for Suspicious Bug Reports' directly summarizes the main change, accurately reflecting the primary objective of implementing a comprehensive reporting system for issues.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #2229: a Report Issue button for users, categorized reasons with free-text descriptions, duplicate/self-report prevention, admin dashboard, status workflow, and contextual alerts.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the reporting system scope: models, migrations, views, templates, and UI components for both user reporting and admin management are all core to the feature.
✨ 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

❌ Pre-commit checks failed

The pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them:

# Install pre-commit if you haven't already
pip install pre-commit

# Run pre-commit on all files
pre-commit run --all-files

# Or run pre-commit on staged files only
pre-commit run

After running these commands, the pre-commit hooks will automatically fix most issues.
Please review the changes, commit them, and push to your branch.

💡 Tip: You can set up pre-commit to run automatically on every commit by running:

pre-commit install
Pre-commit output
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[WARNING] repo `https://github.com/pre-commit/pre-commit-hooks` uses deprecated stage names (commit, push) which will be removed in a future version.  Hint: often `pre-commit autoupdate --repo https://github.com/pre-commit/pre-commit-hooks` will fix this.  if it does not -- consider reporting an issue to that repo.
[INFO] Initializing environment for https://github.com/pycqa/isort.
[WARNING] repo `https://github.com/pycqa/isort` uses deprecated stage names (commit, merge-commit, push) which will be removed in a future version.  Hint: often `pre-commit autoupdate --repo https://github.com/pycqa/isort` will fix this.  if it does not -- consider reporting an issue to that repo.
[INFO] Initializing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Initializing environment for https://github.com/djlint/djLint.
[INFO] Initializing environment for local.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pycqa/isort.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/djlint/djLint.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for local.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
check python ast.........................................................Passed
check builtin type constructor use.......................................Passed
check yaml...............................................................Passed
fix python encoding pragma...............................................Passed
mixed line ending........................................................Passed
isort....................................................................Failed
- hook id: isort
- files were modified by this hook

Fixing /home/runner/work/BLT/BLT/website/migrations/0261_add_issue_reporting_system.py


For more information, see the pre-commit documentation.

@github-actions github-actions bot added the pre-commit: failed Pre-commit checks failed label Dec 10, 2025
Comment on lines +290 to +300

reason = request.POST.get("reason", "other")
description = request.POST.get("description", "")

if not description.strip():
return JsonResponse({"status": "error", "message": "Please provide a description"}, status=400)

# Create the report
report = IssueReport.objects.create(
reporter=request.user, reported_issue=issue, reason=reason, description=description
)
Copy link

Choose a reason for hiding this comment

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

Bug: The report_issue function has a race condition where concurrent requests can bypass the duplicate check, causing an unhandled IntegrityError when creating a report.
Severity: HIGH | Confidence: High

🔍 Detailed Analysis

A race condition exists in the report_issue function. The code first checks if an IssueReport from a user on a specific issue already exists, and if not, it proceeds to create one. However, this check and creation are not atomic. If two concurrent requests from the same user for the same issue are processed, both can pass the initial existence check. The first request will successfully create the report. The second request's attempt to create a duplicate report will violate the unique_together database constraint on the IssueReport model, raising an unhandled IntegrityError. This causes the server to return a 500 error page instead of the expected JSON error response, leading to a poor user experience and breaking frontend error handling.

💡 Suggested Fix

Wrap the IssueReport.objects.create() call in a try...except IntegrityError block. In the except block, return a JsonResponse with an appropriate error message and a 400 status code, similar to the check for an existing report. This ensures that the race condition is handled gracefully without causing a server error.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: website/views/issue.py#L287-L300

Potential issue: A race condition exists in the `report_issue` function. The code first
checks if an `IssueReport` from a user on a specific issue already exists, and if not,
it proceeds to create one. However, this check and creation are not atomic. If two
concurrent requests from the same user for the same issue are processed, both can pass
the initial existence check. The first request will successfully create the report. The
second request's attempt to create a duplicate report will violate the `unique_together`
database constraint on the `IssueReport` model, raising an unhandled `IntegrityError`.
This causes the server to return a 500 error page instead of the expected JSON error
response, leading to a poor user experience and breaking frontend error handling.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 6874317

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 (8)
website/templates/issue.html (1)

285-317: Consider using the existing notification system instead of alert().

The rest of the codebase uses $.notify() for user feedback (see deleteIssue function at line 268). Using alert() here creates an inconsistent UX.

 function submitReport() {
     const reason = document.getElementById('reportReason').value;
     const description = document.getElementById('reportDescription').value;

     if (!description.trim()) {
-        alert('Please provide a description for your report.');
+        $.notify('Please provide a description for your report.', {
+            style: "custom",
+            className: "danger"
+        });
         return;
     }

     const formData = new FormData();
     formData.append('reason', reason);
     formData.append('description', description);
     formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');

     fetch(`/report_issue/{{ object.id }}/`, {
         method: 'POST',
         body: formData,
         credentials: 'same-origin'
     })
     .then(response => response.json())
     .then(data => {
         if (data.status === 'success') {
-            alert(data.message);
+            $.notify(data.message, {
+                style: "custom",
+                className: "success"
+            });
             closeReportModal();
         } else {
-            alert(data.message || 'Failed to submit report');
+            $.notify(data.message || 'Failed to submit report', {
+                style: "custom",
+                className: "danger"
+            });
         }
     })
     .catch(error => {
         console.error('Error:', error);
-        alert('Failed to submit report. Please try again.');
+        $.notify('Failed to submit report. Please try again.', {
+            style: "custom",
+            className: "danger"
+        });
     });
 }
website/templates/admin/issue_reports.html (2)

74-75: Potential XSS vector with inline JavaScript onclick handlers.

While escapejs escapes for JavaScript strings, passing user content directly to inline handlers can be risky. Consider using data-* attributes and attaching event listeners in JavaScript instead.

-<button onclick="showReportDetails({{ report.id }}, '{{ report.description|escapejs }}', '{{ report.admin_notes|escapejs }}', '{{ report.status }}')" 
+<button data-report-id="{{ report.id }}" 
+        data-description="{{ report.description|escapejs }}"
+        data-admin-notes="{{ report.admin_notes|escapejs }}"
+        data-status="{{ report.status }}"
+        onclick="showReportDetails(this)"
         class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-3">
     View
 </button>

Then update the JavaScript function:

function showReportDetails(element) {
    currentReportId = element.dataset.reportId;
    document.getElementById('reportDetailsDescription').textContent = element.dataset.description;
    document.getElementById('adminNotes').value = element.dataset.adminNotes;
    document.getElementById('reportStatus').value = element.dataset.status;
    document.getElementById('reportDetailsModal').classList.remove('hidden');
}

1-6: Missing admin access check in template.

While the view enforces admin permissions, adding a defensive check in the template provides defense-in-depth. This template extends base.html but doesn't verify admin access at the template level.

Consider wrapping the content in an admin check:

{% if request.user.is_superuser or request.user.is_staff %}
    <!-- existing content -->
{% else %}
    <p>Access denied</p>
{% endif %}
website/templates/admin/issue_specific_reports.html (2)

23-24: Add rel attributes to external link to avoid reverse‑tabnabbing

The issue URL is opened in a new tab with target="_blank" but without rel="noopener noreferrer", which can allow the target page to control the admin window. Add rel="noopener noreferrer" on this anchor.


71-82: JS modal wiring is generally safe; consider decoupling hard‑coded path and aligning CSRF with backend

The modal plumbing and use of escapejs + .textContent look good from an XSS perspective. Two improvements to consider:

  • The update URL is hard‑coded as /issue-reports/${currentReportId}/update/. If the URL pattern or prefix changes, this breaks silently. Prefer deriving it from {% url %} or putting the base path in a data-* attribute.
  • You append csrfmiddlewaretoken to FormData, which is correct for Django, but the backend view (update_report_status) is currently marked @csrf_exempt. Either remove csrf_exempt and rely on CSRF protection or drop the unused token; keeping both aligned will avoid confusion.

Also applies to: 107-112, 161-203

website/views/issue.py (2)

307-323: Admin reports overview looks fine; consider pagination and filtering later

The admin overview correctly restricts access to staff/superusers and uses select_related for related objects. For large numbers of reports, loading IssueReport.objects.all() without pagination or basic filtering (e.g., status) may become slow and hard to use, but that can be addressed incrementally if needed.


368-385: Per‑issue admin reports view is correct; consider pagination if counts grow

view_issue_specific_reports correctly:

  • Enforces staff/superuser access.
  • Uses get_object_or_404(Issue, id=issue_id).
  • Filters IssueReport by reported_issue and select_related for the fields used in the template.
  • Provides pending_count consistent with the main reports view.

For very report-heavy issues you may want pagination or ordering tweaks later, but functionally this is fine.

website/models.py (1)

1592-1625: IssueReport model aligns well with usage; consider adding indexes for common queries

The model design matches how it’s used:

  • reporter / reported_issue FKs and unique_together enforce “one report per user per issue”.
  • REPORT_REASONS and STATUS_CHOICES cover the flows implemented in the views and templates.
  • reviewed_by / reviewed_at / admin_notes support the admin audit workflow.
  • related_name="reports" on reported_issue is consistent with filters in the views.

Given you frequently filter by reported_issue and sometimes by (reported_issue, status), you might want to add an index (or composite index) on these fields to keep queries fast as the table grows:

 class IssueReport(models.Model):
@@
     admin_notes = models.TextField(blank=True, help_text="Admin notes about this report")
 
     class Meta:
         unique_together = ("reporter", "reported_issue")  # Prevent duplicate reports from same user
-        ordering = ["-created"]
+        ordering = ["-created"]
+        indexes = [
+            models.Index(fields=["reported_issue"], name="issuereport_issue_idx"),
+            models.Index(fields=["reported_issue", "status"], name="issuereport_issue_status_idx"),
+        ]
📜 Review details

Configuration used: CodeRabbit 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 ebbc77c and 77caf07.

📒 Files selected for processing (9)
  • blt/urls.py (3 hunks)
  • website/migrations/0261_add_issue_reporting_system.py (1 hunks)
  • website/models.py (1 hunks)
  • website/templates/admin/issue_reports.html (1 hunks)
  • website/templates/admin/issue_specific_reports.html (1 hunks)
  • website/templates/includes/admin_sidenav.html (1 hunks)
  • website/templates/includes/issue_right_info.html (1 hunks)
  • website/templates/issue.html (2 hunks)
  • website/views/issue.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
website/views/issue.py (1)
website/models.py (11)
  • Issue (590-739)
  • IssueReport (1592-1625)
  • save (74-77)
  • save (277-289)
  • save (1400-1427)
  • save (1535-1538)
  • save (1763-1766)
  • save (1881-1895)
  • save (1986-2008)
  • save (2763-2766)
  • save (3280-3290)
blt/urls.py (1)
website/views/issue.py (4)
  • report_issue (275-304)
  • update_report_status (327-351)
  • view_issue_reports (308-322)
  • view_issue_specific_reports (369-385)
⏰ 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 (10)
website/templates/includes/admin_sidenav.html (1)

10-16: LGTM!

The new navigation item follows the existing sidenav pattern correctly. The conditional highlighting and URL linking are properly implemented.

website/templates/issue.html (2)

188-199: LGTM!

The Report Issue button is correctly shown only to authenticated users who are not the issue owner. This prevents self-reporting while allowing legitimate reports.


207-243: LGTM!

The modal structure is clean with appropriate form elements. The reason dropdown matches the model's REASON_CHOICES from the migration.

website/migrations/0261_add_issue_reporting_system.py (1)

15-37: LGTM!

The migration is well-structured with appropriate:

  • CASCADE delete on reporter and reported_issue (reports should be removed when source entities are deleted)
  • SET_NULL on reviewed_by (preserves audit trail even if admin account is removed)
  • unique_together constraint preventing duplicate reports
  • Sensible default status of 'pending'
website/templates/includes/issue_right_info.html (1)

28-57: Verify context variables are passed from IssueView.

The template uses reports_count and pending_reports_count, but these must be passed from the view's context. Ensure IssueView.get_context_data() includes these variables for the template to render correctly.

blt/urls.py (1)

593-596: Verify the ID type and authentication requirements for the report_issue view.

Unable to determine whether the ID parameter should be <int:id> or \w+ without confirming the actual data type used by the Issue model. If Issue IDs are indeed integers, converting to <int:id> would improve type safety and consistency with the other admin URLs. Additionally, confirm whether the report_issue view requires explicit @login_required decorator or if the request.user check provides sufficient authentication protection for your use case.

website/views/issue.py (4)

354-366: Helper functions for report counts are straightforward and consistent with model

get_issue_reports_count and get_issue_pending_reports_count are simple, readable helpers and use the reported_issue FK consistently with the IssueReport model. No issues here; any heavier usage patterns can be optimized later if needed.


1888-1892: Admin‑only report counters in IssueView context are appropriate

The new reports_count and pending_reports_count are only computed for authenticated staff/superusers, avoiding overhead for regular users. Using the helper functions keeps the logic centralized and consistent with the admin views.


273-305: Verify CSRF and reason validation in report_issue view

report_issue is marked @csrf_exempt while performing a state-changing action for authenticated users. Remove @csrf_exempt and either use a standard POST form with {% csrf_token %} or ensure AJAX requests send the CSRF token in the header.

Additionally, reason is taken directly from request.POST without validating against IssueReport.REPORT_REASONS choices. Validate the reason value against the model's choices to reject unexpected values.

Consider adding @require_POST to explicitly enforce POST-only access.


325-352: Remove @csrf_exempt from update_report_status and restrict to POST

This endpoint mutates report state (calls report.save()) and is only guarded by session authentication plus a staff/superuser check. The @csrf_exempt decorator disables Django's built-in CSRF protection, creating a vulnerability where a CSRF attack against a logged-in staff member could silently modify report statuses and notes without their knowledge.

Recommended:

  • Remove @csrf_exempt and rely on Django's CSRF middleware protection
  • Add @require_POST decorator to make the HTTP contract explicit and prevent accidental state mutations from GET requests
  • Ensure the calling template includes {% csrf_token %} in the form or X-CSRFToken header in AJAX requests

The status validation and audit-field updates (reviewed_by, reviewed_at) are appropriately implemented.

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Dec 10, 2025
@github-actions github-actions bot added tests: passed Django tests passed last-active: 0d PR last updated 0 days ago labels Dec 10, 2025
@github-actions
Copy link
Contributor

💬 Reminder: Unresolved Conversations

Hi @sidd190!

This pull request has 1 unresolved conversation that need to be addressed.

Please review and resolve the pending discussions so we can move forward with merging this PR.

Thank you! 🙏

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

Labels

files-changed: 9 PR changes 9 files last-active: 1d PR last updated 1 day ago migrations PR contains database migration files needs-peer-review PR needs peer review pre-commit: failed Pre-commit checks failed tests: passed Django tests passed

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

Allow users to report suspicious bug reports.

1 participant