Skip to content

Conversation

@Ankit-singh-dot
Copy link
Contributor

@Ankit-singh-dot Ankit-singh-dot commented Dec 11, 2025

adding feature issue number : #3767
Phase 1: Bounty Backend (Models + API Endpoints)
This PR introduces the foundational backend components required for the upcoming /bounty $X feature described in issue #3767.
It includes the Bounty model, serializer, and REST API endpoints that will be consumed by GitHub Actions and Slack commands in later phases.

What This PR Adds

  1. Bounty Model (models_bounty.py)

A new database model to store bounty contributions:

repo_full_name — GitHub repository path (org/repo)

issue_number — Issue number that received the bounty

issue_url — Full GitHub issue URL

sponsor_github — GitHub username of the sponsor

amount, currency — Monetary value of the bounty

origin_comment_id — GitHub comment ID for idempotent creation

paid, paid_at — Integration points with existing payout webhook

created_at — automatic timestamp

This model is structured to support both GitHub and Slack bounty commands.

  1. BountySerializer (serializers.py)

A simple DRF serializer exposing the full Bounty model.
Used by API endpoints and future ingestion scripts.

  1. Bounty API (api/views.py)
    POST /api/bounties/

Creates a new bounty record.
Includes safety check to prevent duplicate creation when GitHub sends repeated webhooks (origin_comment_id).

GET /api/bounties/issue-total/?repo=org/repo&issue_number=123

Returns the aggregated bounty total for a specific issue.
This will be used by Phase 2 GitHub Actions to update bounty labels dynamically.

  1. URL Routing

The new API endpoints are registered under:

/api/bounties/

These routes will be consumed by the Phase 2 automation.

Local Testing Completed

Migration successfully applied (0261_bounty)

Serializer imports verified

ViewSet import verified

API endpoints load without errors

=> Why This PR Is Phase 1 Only

To keep contributions reviewable and maintainable, the bounty feature is being delivered in stages:

Phase 1 — Backend (this PR)

Model + API

Phase 2 — GitHub Action Integration

Detect /bounty $X, update labels, post confirmation comment

Phase 3 — Slack /bounty Command

Slack support + GitHub updates

This PR creates the backend foundation required for future phases.

=>>Next Steps After Merge

Once this PR is approved, Phase 2 will introduce:

GitHub Action workflow (bounty-detection.yml)

Node parser for /bounty

Automatic label handling

Confirmation comments via GitHub API

REQUEST FOR REVIEW

Please review the model fields, serializer exposure, and API design.
Feedback is welcome before proceeding with Phase 2.

Summary by CodeRabbit

  • New Features

    • Introduces a bounty system for GitHub issues: create, view, and track sponsorships with amount, currency, sponsor, and payment status.
    • Public API endpoints to create bounties and to retrieve total bounty amounts for a specific issue.
  • Bug Fixes

    • Prevents duplicate bounty submissions and handles race conditions when creating bounties.
  • Tests

    • Adds serialization support for bounty-related tests.

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

@github-actions github-actions bot added the migrations PR contains database migration files label Dec 11, 2025
@github-actions
Copy link
Contributor

👋 Hi @Ankit-singh-dot!

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: 6 PR changes 6 files needs-peer-review PR needs peer review labels Dec 11, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Walkthrough

Adds a new Bounty feature: a Django Bounty model and migration, ModelSerializer(s), a BountyViewSet with create and issue-total endpoints, and DRF route registration for bounties. Test code gains a test-scoped serializer; views handle duplicate origin_comment_id and aggregate bounty totals.

Changes

Cohort / File(s) Summary
Model & Migration
website/views/models_bounty.py, website/migrations/0261_bounty.py
Introduces Bounty model with fields (repo_full_name, issue_number, issue_url, sponsor_github, sponsor_user FK, amount, currency, created_at, paid, paid_at, origin_comment_id); default ordering -created_at; indexes on (repo_full_name,issue_number) and sponsor_github; migration creates table.
Serializer(s)
website/serializers.py, website/test_feed.py
Adds BountySerializer (ModelSerializer) in main serializers and a test-scoped BountySerializer in tests; fields include id, repo_full_name, issue_number, issue_url, sponsor_github, sponsor_user, amount, currency, created_at, paid, paid_at, origin_comment_id with read-only fields for id, created_at, paid, paid_at.
API ViewSet
website/api/views.py
Adds BountyViewSet with create() that validates input and prevents duplicates by origin_comment_id (handles IntegrityError for races), and an issue_total endpoint that validates query params and returns aggregated sum (Sum) for a repo + issue_number.
Routing
blt/urls.py
Registers bounties on the DRF router via router.register(r"bounties", BountyViewSet, basename="bounties").

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as "BountyViewSet"
    participant S as "BountySerializer"
    participant M as "Bounty model"
    participant DB as "Database"

    rect rgb(235,245,255)
    Note over Client,DB: Create bounty (POST /api/bounties/)
    Client->>API: POST /api/bounties/ (payload)
    API->>S: S(data=payload)
    S->>S: serializer.is_valid()
    alt origin_comment_id provided
        API->>M: M.objects.filter(origin_comment_id=...).exists()
        M->>DB: SELECT ... WHERE origin_comment_id=?
        DB-->>M: result
        alt exists
            API-->>Client: 400/409 Duplicate response
        else
            API->>M: M.objects.create(**validated_data)
            M->>DB: INSERT bounty
            DB-->>M: created row
            API-->>Client: 201 Created + serialized bounty
        end
    else
        API->>M: M.objects.create(**validated_data)
        M->>DB: INSERT bounty
        DB-->>M: created row
        API-->>Client: 201 Created + serialized bounty
    end
    end
Loading
sequenceDiagram
    participant Client
    participant API as "BountyViewSet"
    participant M as "Bounty model"
    participant DB as "Database"

    rect rgb(250,245,235)
    Note over Client,DB: Issue total (GET /api/bounties/issue-total?repo=...&issue=...)
    Client->>API: GET /api/bounties/issue-total?repo=...&issue=...
    API->>M: M.objects.filter(repo_full_name=..., issue_number=...).aggregate(Sum('amount'))
    M->>DB: SELECT SUM(amount) ... WHERE repo_full_name=? AND issue_number=?
    DB-->>M: {total: X}
    API-->>Client: 200 OK {repo, issue_number, total: X}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect blt/urls.py registration for correct basename and router usage.
  • Verify create() duplicate-check, race handling (IntegrityError), and returned status codes.
  • Confirm migration field types/precision and indexes align with queries in issue_total.
  • Review serializer field exposure, validation, and permission implications in BountyViewSet.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% 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 captures the main change: adding a Bounty model, serializer, and API endpoints as Phase 1 of the bounty 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/blt/urls.py
Fixing /home/runner/work/BLT/BLT/website/api/views.py
Fixing /home/runner/work/BLT/BLT/website/serializers.py
Fixing /home/runner/work/BLT/BLT/website/test_feed.py
Fixing /home/runner/work/BLT/BLT/website/views/models_bounty.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 11, 2025
blt/urls.py Outdated
Comment on lines 402 to 403
router = routers.DefaultRouter()
router.register(r"bounties", BountyViewSet, basename="bounty")

This comment was marked as outdated.

@github-actions
Copy link
Contributor

❌ Tests failed

The Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests.

How to run tests locally

# Install dependencies
poetry install --with dev

# Run all tests
poetry run python manage.py test

# Run tests with verbose output
poetry run python manage.py test -v 3

# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_method
Test output (last 100 lines)
Adding permission 'Permission object (386)'
Adding permission 'Permission object (387)'
Adding permission 'Permission object (388)'
Running post-migrate handlers for application allauth
Running post-migrate handlers for application account
Adding content type 'account | emailaddress'
Adding content type 'account | emailconfirmation'
Adding permission 'Permission object (389)'
Adding permission 'Permission object (390)'
Adding permission 'Permission object (391)'
Adding permission 'Permission object (392)'
Adding permission 'Permission object (393)'
Adding permission 'Permission object (394)'
Adding permission 'Permission object (395)'
Adding permission 'Permission object (396)'
Running post-migrate handlers for application socialaccount
Adding content type 'socialaccount | socialaccount'
Adding content type 'socialaccount | socialapp'
Adding content type 'socialaccount | socialtoken'
Adding permission 'Permission object (397)'
Adding permission 'Permission object (398)'
Adding permission 'Permission object (399)'
Adding permission 'Permission object (400)'
Adding permission 'Permission object (401)'
Adding permission 'Permission object (402)'
Adding permission 'Permission object (403)'
Adding permission 'Permission object (404)'
Adding permission 'Permission object (405)'
Adding permission 'Permission object (406)'
Adding permission 'Permission object (407)'
Adding permission 'Permission object (408)'
Running post-migrate handlers for application django_gravatar
Running post-migrate handlers for application comments
Adding content type 'comments | comment'
Adding permission 'Permission object (409)'
Adding permission 'Permission object (410)'
Adding permission 'Permission object (411)'
Adding permission 'Permission object (412)'
Running post-migrate handlers for application authtoken
Adding content type 'authtoken | token'
Adding content type 'authtoken | tokenproxy'
Adding permission 'Permission object (413)'
Adding permission 'Permission object (414)'
Adding permission 'Permission object (415)'
Adding permission 'Permission object (416)'
Adding permission 'Permission object (417)'
Adding permission 'Permission object (418)'
Adding permission 'Permission object (419)'
Adding permission 'Permission object (420)'
Running post-migrate handlers for application tz_detect
Running post-migrate handlers for application star_ratings
Adding content type 'star_ratings | rating'
Adding content type 'star_ratings | userrating'
Adding permission 'Permission object (421)'
Adding permission 'Permission object (422)'
Adding permission 'Permission object (423)'
Adding permission 'Permission object (424)'
Adding permission 'Permission object (425)'
Adding permission 'Permission object (426)'
Adding permission 'Permission object (427)'
Adding permission 'Permission object (428)'
Running post-migrate handlers for application captcha
Adding content type 'captcha | captchastore'
Adding permission 'Permission object (429)'
Adding permission 'Permission object (430)'
Adding permission 'Permission object (431)'
Adding permission 'Permission object (432)'
Running post-migrate handlers for application dj_rest_auth
WARNING Slack environment not set. Slack integration disabled.
WARNING Slack environment not set. Slack integration disabled.
System check identified some issues:

WARNINGS:
?: settings.ACCOUNT_EMAIL_REQUIRED is deprecated, use: settings.ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*']
?: settings.ACCOUNT_USERNAME_REQUIRED is deprecated, use: settings.ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*']

System check identified 2 issues (0 silenced).
website.test_feed (unittest.loader._FailedTest.website.test_feed) ... ERROR

======================================================================
ERROR: website.test_feed (unittest.loader._FailedTest.website.test_feed)
----------------------------------------------------------------------
ImportError: Failed to import test module: website.test_feed
Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/unittest/loader.py", line 407, in _find_test_path
    module = self._get_module_from_name(name)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/unittest/loader.py", line 350, in _get_module_from_name
    __import__(name)
  File "/home/runner/work/BLT/BLT/website/test_feed.py", line 6, in <module>
    from .models import Bounty
ImportError: cannot import name 'Bounty' from 'website.models' (/home/runner/work/BLT/BLT/website/models.py)


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...

For more information, see the Django testing documentation.

@github-actions github-actions bot added the tests: failed Django tests failed label Dec 11, 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: 4

🧹 Nitpick comments (9)
blt/urls.py (1)

373-374: Remove duplicate import.

The routers import on line 373 duplicates line 19. The BountyViewSet import on line 374 is correctly placed.

Apply this diff:

-from rest_framework import routers
 from website.api.views import BountyViewSet
website/serializers.py (1)

25-25: Remove duplicate import.

The serializers import duplicates line 2.

Apply this diff:

-from rest_framework import serializers
 from website.views.models_bounty import Bounty
website/test_feed.py (1)

128-145: Remove unused nested serializer definition.

This BountySerializer class is defined inside the test method but never used. The test test_rss_feed_link_on_page (lines 117-124) doesn't reference it. Additionally, BountySerializer is already defined in website/serializers.py (lines 312-315). Remove this unused code.

Apply this diff:

         rss_url = reverse("activity_feed_rss")
         self.assertContains(response, rss_url)
         self.assertContains(response, "Subscribe to RSS Feed")
-    
-
-
-    class BountySerializer(serializers.ModelSerializer):
-        class Meta:
-            model = Bounty
-            fields = [
-                "id",
-            "repo_full_name",
-            "issue_number",
-            "issue_url",
-            "sponsor_github",
-            "sponsor_user",
-            "amount",
-            "currency",
-            "created_at",
-            "paid",
-            "paid_at",
-            "origin_comment_id",
-            ]
-            read_only_fields = ["id", "created_at", "paid", "paid_at"]
website/views/models_bounty.py (3)

17-17: Clarify the comment for the amount field.

The comment "currency in USD (or store cents)" is misleading. Since amount is a DecimalField with decimal_places=2, it stores dollars (e.g., 50.00 for $50), not cents (5000 for $50). Update the comment to reflect the actual storage format.

Apply this diff:

-    amount = models.DecimalField(max_digits=12, decimal_places=2)  # currency in USD (or store cents)
+    amount = models.DecimalField(max_digits=12, decimal_places=2)  # e.g., 50.00 for $50 USD

26-26: Consider adding a unique constraint on origin_comment_id.

Since origin_comment_id is used for duplicate prevention in the API (website/api/views.py line 1983), adding a database-level unique constraint would provide an additional safety layer. However, because the field is nullable and GitHub might send events without comment IDs, you'd need a partial unique constraint.

Apply this diff to add a partial unique constraint (requires Django 2.2+):

     class Meta:
         indexes = [
             models.Index(fields=["repo_full_name", "issue_number"]),
             models.Index(fields=["sponsor_github"]),
         ]
+        constraints = [
+            models.UniqueConstraint(
+                fields=["origin_comment_id"],
+                condition=models.Q(origin_comment_id__isnull=False),
+                name="unique_origin_comment_id"
+            ),
+        ]
         ordering = ["-created_at"]

Note: This requires a new migration. Verify this aligns with your GitHub webhook behavior.


17-18: Add validation for amount and currency fields.

Consider adding model-level validation to ensure amount is positive and currency is a valid currency code. This prevents data integrity issues at the database layer.

Add validators to the model:

from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError

class Bounty(models.Model):
    # ... existing fields ...
    amount = models.DecimalField(
        max_digits=12, 
        decimal_places=2,
        validators=[MinValueValidator(0.01)]
    )
    currency = models.CharField(max_length=10, default="USD")
    
    def clean(self):
        super().clean()
        # Validate currency is a supported code
        SUPPORTED_CURRENCIES = ["USD", "EUR", "GBP"]  # extend as needed
        if self.currency not in SUPPORTED_CURRENCIES:
            raise ValidationError(f"Currency {self.currency} is not supported")

Alternatively, use choices for the currency field if the list is fixed.

website/api/views.py (3)

35-36: Remove duplicate imports.

Both viewsets (line 35, already on line 27) and Sum (line 36, already on line 22) are duplicate imports.

Apply this diff:

-from rest_framework import viewsets, status
-from django.db.models import Sum
 from website.duplicate_checker import check_for_duplicates, find_similar_bugs, format_similar_bug

1983-1987: Return appropriate HTTP status for duplicate bounties.

Returning HTTP_200_OK for an existing bounty is semantically incorrect. Consider using HTTP_409_CONFLICT to indicate the resource already exists, or return a consistent 200 with a created: false flag.

Apply this diff:

         # Prevent duplicate entries
         if origin_comment_id and Bounty.objects.filter(origin_comment_id=origin_comment_id).exists():
+            existing = Bounty.objects.get(origin_comment_id=origin_comment_id)
+            serializer = self.get_serializer(existing)
             return Response(
-                {"detail": "Bounty for this GitHub comment already exists."},
-                status=status.HTTP_200_OK,
+                {"detail": "Bounty for this GitHub comment already exists.", "bounty": serializer.data},
+                status=status.HTTP_409_CONFLICT,
             )

Alternatively, for idempotent behavior, return 200 with the existing bounty data and a created: false field.


2008-2019: Preserve decimal precision in the total field.

Converting total from Decimal to float on line 2019 can introduce floating-point precision errors. For financial data, preserve precision by returning the Decimal as a string or keeping it as Decimal (DRF serializes Decimal correctly).

Apply this diff:

         total = (
             Bounty.objects
             .filter(repo_full_name=repo, issue_number=issue_number)
             .aggregate(total=Sum("amount"))
             .get("total") or 0
         )
 
         return Response(
             {
                 "repo": repo,
-                "issue_number": int(issue_number),
-                "total": float(total)
+                "issue_number": issue_number,
+                "total": str(total) if total else "0.00"
             }
         )

If issue_number is validated as int earlier (per previous comment), no need to cast it again.

📜 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 385df26 and 626ddac.

📒 Files selected for processing (6)
  • blt/urls.py (2 hunks)
  • website/api/views.py (3 hunks)
  • website/migrations/0261_bounty.py (1 hunks)
  • website/serializers.py (2 hunks)
  • website/test_feed.py (2 hunks)
  • website/views/models_bounty.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
website/views/models_bounty.py (1)
website/serializers.py (16)
  • Meta (34-36)
  • Meta (58-74)
  • Meta (78-80)
  • Meta (84-86)
  • Meta (97-99)
  • Meta (107-109)
  • Meta (113-115)
  • Meta (119-121)
  • Meta (125-127)
  • Meta (139-142)
  • Meta (149-151)
  • Meta (155-173)
  • Meta (177-185)
  • Meta (198-200)
  • Meta (212-237)
  • Meta (248-266)
website/serializers.py (2)
website/views/models_bounty.py (1)
  • Bounty (6-36)
website/test_feed.py (1)
  • BountySerializer (128-145)
blt/urls.py (1)
website/api/views.py (1)
  • BountyViewSet (1960-2021)
website/api/views.py (3)
website/views/models_bounty.py (1)
  • Bounty (6-36)
website/serializers.py (1)
  • BountySerializer (312-315)
website/test_feed.py (1)
  • BountySerializer (128-145)
website/test_feed.py (2)
website/views/models_bounty.py (1)
  • Bounty (6-36)
website/serializers.py (17)
  • BountySerializer (312-315)
  • Meta (34-36)
  • Meta (58-74)
  • Meta (78-80)
  • Meta (84-86)
  • Meta (97-99)
  • Meta (107-109)
  • Meta (113-115)
  • Meta (119-121)
  • Meta (125-127)
  • Meta (139-142)
  • Meta (149-151)
  • Meta (155-173)
  • Meta (177-185)
  • Meta (198-200)
  • Meta (212-237)
  • Meta (248-266)
⏰ 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/migrations/0261_bounty.py (1)

1-45: LGTM!

The migration correctly creates the Bounty model with appropriate fields, indexes, and ordering. The schema aligns with the model definition in website/views/models_bounty.py.

website/test_feed.py (1)

5-6: Verify the Bounty import path.

The import from .models import Bounty may fail because Bounty is defined in website.views.models_bounty, not in website.models. If you need to import Bounty here, use from website.views.models_bounty import Bounty or add it to website.models.__init__.py.

#!/bin/bash
# Verify Bounty model import paths and check if it's exported from website.models
rg -n "class Bounty" --type=py
rg -n "from.*Bounty" --type=py | head -20
# Check if Bounty is in website/models/__init__.py or website/models.py
cat website/models.py 2>/dev/null | grep -i bounty || echo "Bounty not found in models.py"
cat website/models/__init__.py 2>/dev/null | grep -i bounty || echo "Bounty not found in __init__.py"

blt/urls.py Outdated
Comment on lines 402 to 403
router = routers.DefaultRouter()
router.register(r"bounties", BountyViewSet, basename="bounty")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Router re-instantiation overwrites existing routes.

Re-instantiating the router on line 402 discards all previously registered routes (lines 394-401: issues, userissues, profile, domain, timelogs, activitylogs, organizations, jobs). Only the bounty route will be accessible when router.urls is included on line 797.

Apply this diff to register the bounty route on the existing router:

 router.register(r"organizations", OrganizationViewSet, basename="organizations")
 router.register(r"jobs", JobViewSet, basename="jobs")
-router = routers.DefaultRouter()
 router.register(r"bounties", BountyViewSet, basename="bounty")
 handler404 = "website.views.core.handler404"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In blt/urls.py around lines 402-403, the code re-instantiates router (router =
routers.DefaultRouter()) which discards routes registered on lines ~394-401;
remove the new instantiation and call router.register(r"bounties",
BountyViewSet, basename="bounty") on the existing router instance so the bounty
route is added without overwriting previously registered routes.

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Dec 11, 2025
@github-actions github-actions bot added the changes-requested PR has requested changes from a reviewer label Dec 11, 2025
@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....................................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook

1 file reformatted, 466 files left unchanged


For more information, see the pre-commit documentation.

Comment on lines 399 to 407
router.register(r"activitylogs", ActivityLogViewSet, basename="activitylogs")
router.register(r"organizations", OrganizationViewSet, basename="organizations")
router.register(r"jobs", JobViewSet, basename="jobs")

router = routers.DefaultRouter()
router.register(r"issues", IssueViewSet, basename="issues")
router.register(r"userissues", UserIssueViewSet, basename="userissues")
handler404 = "website.views.core.handler404"
handler500 = "website.views.core.handler500"

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
website/serializers.py (1)

311-314: Explicitly declare serializer fields and mark read-only fields.

This concern was already raised in a previous review. Using fields = "__all__" exposes all model fields without granular control. For security and API clarity, explicitly list fields and mark system-managed fields (id, created_at, paid, paid_at) as read-only. The test file already shows the recommended pattern.

🧹 Nitpick comments (3)
website/views/models_bounty.py (2)

1-5: Consider relocating the model to website/models.py or website/models/ directory.

Placing a Django model in website/views/ is unconventional. Models are typically in models.py or a models/ package for better discoverability and alignment with Django conventions.


26-27: Consider adding a unique constraint on origin_comment_id.

The API view checks for duplicate origin_comment_id before creation, but this check is susceptible to race conditions. Adding a database-level unique constraint (excluding null values) would provide stronger guarantees for idempotent bounty creation.

     # Optional: reference to GitHub comment that created this bounty (if from GH comment)
-    origin_comment_id = models.BigIntegerField(null=True, blank=True)
+    origin_comment_id = models.BigIntegerField(null=True, blank=True, unique=True)

Note: unique=True with null=True allows multiple NULL values in most databases (PostgreSQL, SQLite), which is the desired behavior here.

website/api/views.py (1)

1957-1966: Consider restricting allowed HTTP methods.

ModelViewSet exposes list, create, retrieve, update, partial_update, and destroy by default. If only create and the custom issue_total action are needed for Phase 1, restrict the methods to prevent unintended data modification:

 class BountyViewSet(viewsets.ModelViewSet):
     queryset = Bounty.objects.all()
     serializer_class = BountySerializer
     permission_classes = [IsAuthenticated]
+    http_method_names = ["get", "post", "head", "options"]

Or use viewsets.GenericViewSet with explicit mixins for finer control.

📜 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 626ddac and b94758f.

📒 Files selected for processing (6)
  • blt/urls.py (3 hunks)
  • website/api/views.py (3 hunks)
  • website/migrations/0261_bounty.py (1 hunks)
  • website/serializers.py (2 hunks)
  • website/test_feed.py (2 hunks)
  • website/views/models_bounty.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • website/test_feed.py
  • blt/urls.py
🧰 Additional context used
🧬 Code graph analysis (2)
website/serializers.py (2)
website/views/models_bounty.py (2)
  • Bounty (7-37)
  • Meta (29-34)
website/test_feed.py (2)
  • BountySerializer (127-144)
  • Meta (128-144)
website/api/views.py (2)
website/serializers.py (1)
  • BountySerializer (311-314)
website/views/models_bounty.py (1)
  • Bounty (7-37)
⏰ 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: check_peer_review
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (7)
website/serializers.py (1)

25-25: LGTM!

The import follows the established pattern in this file.

website/views/models_bounty.py (1)

7-37: Model design looks solid.

Good choices:

  • DecimalField for monetary amounts prevents floating-point precision issues
  • Appropriate indexes for expected query patterns
  • get_user_model() for proper user model reference
  • SET_NULL on FK deletion preserves bounty history
website/api/views.py (4)

60-60: LGTM!

Import additions are correct and follow the established patterns in this file.

Also applies to: 77-77


1957-1966: Permission classes are now set - previous concern addressed.

The IsAuthenticated permission is now in place. However, the docstring mentions "GitHub Action & Slack integration" - verify that these integrations can authenticate properly with the current permission setup.


1995-2001: Previous concern addressed.

The issue_number parameter validation is now properly implemented with a try/except block.


2003-2016: LGTM!

The aggregation query correctly uses the composite index on (repo_full_name, issue_number). The response properly converts Decimal to float for JSON serialization.

website/migrations/0261_bounty.py (1)

1-44: LGTM!

The migration correctly reflects the Bounty model definition with:

  • All fields properly typed
  • Composite index on (repo_full_name, issue_number) for efficient lookups
  • Index on sponsor_github for sponsor queries
  • Proper FK reference using settings.AUTH_USER_MODEL
  • Correct ordering configuration

@github-actions
Copy link
Contributor

❌ Tests failed

The Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests.

How to run tests locally

# Install dependencies
poetry install --with dev

# Run all tests
poetry run python manage.py test

# Run tests with verbose output
poetry run python manage.py test -v 3

# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_method
Test output (last 100 lines)
Adding permission 'Permission object (413)'
Adding permission 'Permission object (414)'
Adding permission 'Permission object (415)'
Adding permission 'Permission object (416)'
Adding permission 'Permission object (417)'
Adding permission 'Permission object (418)'
Adding permission 'Permission object (419)'
Adding permission 'Permission object (420)'
Running post-migrate handlers for application tz_detect
Running post-migrate handlers for application star_ratings
Adding content type 'star_ratings | rating'
Adding content type 'star_ratings | userrating'
Adding permission 'Permission object (421)'
Adding permission 'Permission object (422)'
Adding permission 'Permission object (423)'
Adding permission 'Permission object (424)'
Adding permission 'Permission object (425)'
Adding permission 'Permission object (426)'
Adding permission 'Permission object (427)'
Adding permission 'Permission object (428)'
Running post-migrate handlers for application captcha
Adding content type 'captcha | captchastore'
Adding permission 'Permission object (429)'
Adding permission 'Permission object (430)'
Adding permission 'Permission object (431)'
Adding permission 'Permission object (432)'
Running post-migrate handlers for application dj_rest_auth
WARNING Slack environment not set. Slack integration disabled.
WARNING Slack environment not set. Slack integration disabled.
Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
Traceback (most recent call last):
  File "/home/runner/work/BLT/BLT/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/test.py", line 24, in run_from_argv
    super().run_from_argv(argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 416, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/test.py", line 63, in handle
    failures = test_runner.run_tests(test_labels)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/test/runner.py", line 1098, in run_tests
    self.run_checks(databases)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/test/runner.py", line 1020, in run_checks
    call_command("check", verbosity=self.verbosity, databases=databases)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 194, in call_command
    return command.execute(*args, **defaults)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/check.py", line 81, in handle
    self.check(
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 492, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/registry.py", line 89, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/urls.py", line 16, in check_url_config
    return check_resolver(resolver)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/urls.py", line 26, in check_resolver
    return check_method()
           ^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 531, in check
    for pattern in self.url_patterns:
                   ^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/utils/functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 718, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/utils/functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 711, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/runner/work/BLT/BLT/blt/urls.py", line 393, in <module>
    router.register(r"bounties", BountyViewSet, basename="bounty")
    ^^^^^^
NameError: name 'router' is not defined. Did you mean: 'routers'?

For more information, see the Django testing documentation.

@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....................................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook

1 file reformatted, 466 files left unchanged


For more information, see the pre-commit documentation.

blt/urls.py Outdated
router.register(r"organizations", OrganizationViewSet, basename="organizations")
router.register(r"jobs", JobViewSet, basename="jobs")

router = routers.DefaultRouter()

This comment was marked as outdated.

Comment on lines +318 to +328
"issue_url",
"sponsor_github",
"sponsor_user",
"amount",
"currency",
"created_at",
"paid",
"paid_at",
"origin_comment_id",
]
read_only_fields = ["id", "created_at", "paid", "paid_at"]

This comment was marked as outdated.

Comment on lines 1972 to 1978
origin_comment_id = serializer.validated_data.get("origin_comment_id")

if origin_comment_id and Bounty.objects.filter(origin_comment_id=origin_comment_id).exists():
return Response(
{"detail": "Bounty for this GitHub comment already exists."},
status=status.HTTP_200_OK,
)

This comment was marked as outdated.

@github-actions
Copy link
Contributor

❌ Tests failed

The Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests.

How to run tests locally

# Install dependencies
poetry install --with dev

# Run all tests
poetry run python manage.py test

# Run tests with verbose output
poetry run python manage.py test -v 3

# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_method
Test output (last 100 lines)
Adding permission 'Permission object (413)'
Adding permission 'Permission object (414)'
Adding permission 'Permission object (415)'
Adding permission 'Permission object (416)'
Adding permission 'Permission object (417)'
Adding permission 'Permission object (418)'
Adding permission 'Permission object (419)'
Adding permission 'Permission object (420)'
Running post-migrate handlers for application tz_detect
Running post-migrate handlers for application star_ratings
Adding content type 'star_ratings | rating'
Adding content type 'star_ratings | userrating'
Adding permission 'Permission object (421)'
Adding permission 'Permission object (422)'
Adding permission 'Permission object (423)'
Adding permission 'Permission object (424)'
Adding permission 'Permission object (425)'
Adding permission 'Permission object (426)'
Adding permission 'Permission object (427)'
Adding permission 'Permission object (428)'
Running post-migrate handlers for application captcha
Adding content type 'captcha | captchastore'
Adding permission 'Permission object (429)'
Adding permission 'Permission object (430)'
Adding permission 'Permission object (431)'
Adding permission 'Permission object (432)'
Running post-migrate handlers for application dj_rest_auth
WARNING Slack environment not set. Slack integration disabled.
WARNING Slack environment not set. Slack integration disabled.
Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
Traceback (most recent call last):
  File "/home/runner/work/BLT/BLT/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/test.py", line 24, in run_from_argv
    super().run_from_argv(argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 416, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/test.py", line 63, in handle
    failures = test_runner.run_tests(test_labels)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/test/runner.py", line 1098, in run_tests
    self.run_checks(databases)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/test/runner.py", line 1020, in run_checks
    call_command("check", verbosity=self.verbosity, databases=databases)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 194, in call_command
    return command.execute(*args, **defaults)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/check.py", line 81, in handle
    self.check(
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 492, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/registry.py", line 89, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/urls.py", line 16, in check_url_config
    return check_resolver(resolver)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/urls.py", line 26, in check_resolver
    return check_method()
           ^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 531, in check
    for pattern in self.url_patterns:
                   ^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/utils/functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 718, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/utils/functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 711, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/runner/work/BLT/BLT/blt/urls.py", line 393, in <module>
    router.register(r"bounties", BountyViewSet, basename="bounty")
    ^^^^^^
NameError: name 'router' is not defined. Did you mean: 'routers'?

For more information, see the Django testing documentation.

@github-actions github-actions bot added pre-commit: passed Pre-commit checks passed and removed pre-commit: failed Pre-commit checks failed labels Dec 11, 2025
Comment on lines +725 to +735
.get("total")
or 0
)

return Response(
{
"repo": repo,
"issue_number": issue_number,
"total": float(total),
}
)
Copy link

Choose a reason for hiding this comment

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

Bug: The @action decorator and issue_total function are defined at the module level, not as methods within a ViewSet class, which will cause a server startup crash.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

The @action decorator from Django REST Framework is applied to the issue_total function at the module level in website/api/views.py. This decorator is designed to work exclusively on methods within a ViewSet class. Because the function is not a method of a class, the decorator will fail to apply correctly when the module is imported. This will raise an AttributeError or a similar exception during the application's initialization phase, preventing the Django server from starting up successfully.

💡 Suggested Fix

Move the issue_total function and its @action decorator inside the OrganizationViewSet class by indenting them correctly. This will register the function as a valid action on the viewset.

🤖 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/api/views.py#L703-L735

Potential issue: The `@action` decorator from Django REST Framework is applied to the
`issue_total` function at the module level in `website/api/views.py`. This decorator is
designed to work exclusively on methods within a `ViewSet` class. Because the function
is not a method of a class, the decorator will fail to apply correctly when the module
is imported. This will raise an `AttributeError` or a similar exception during the
application's initialization phase, preventing the Django server from starting up
successfully.

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

@github-actions
Copy link
Contributor

❌ Tests failed

The Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests.

How to run tests locally

# Install dependencies
poetry install --with dev

# Run all tests
poetry run python manage.py test

# Run tests with verbose output
poetry run python manage.py test -v 3

# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_method
Test output (last 100 lines)
Adding permission 'Permission object (413)'
Adding permission 'Permission object (414)'
Adding permission 'Permission object (415)'
Adding permission 'Permission object (416)'
Adding permission 'Permission object (417)'
Adding permission 'Permission object (418)'
Adding permission 'Permission object (419)'
Adding permission 'Permission object (420)'
Running post-migrate handlers for application tz_detect
Running post-migrate handlers for application star_ratings
Adding content type 'star_ratings | rating'
Adding content type 'star_ratings | userrating'
Adding permission 'Permission object (421)'
Adding permission 'Permission object (422)'
Adding permission 'Permission object (423)'
Adding permission 'Permission object (424)'
Adding permission 'Permission object (425)'
Adding permission 'Permission object (426)'
Adding permission 'Permission object (427)'
Adding permission 'Permission object (428)'
Running post-migrate handlers for application captcha
Adding content type 'captcha | captchastore'
Adding permission 'Permission object (429)'
Adding permission 'Permission object (430)'
Adding permission 'Permission object (431)'
Adding permission 'Permission object (432)'
Running post-migrate handlers for application dj_rest_auth
WARNING Slack environment not set. Slack integration disabled.
WARNING Slack environment not set. Slack integration disabled.
Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
Traceback (most recent call last):
  File "/home/runner/work/BLT/BLT/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/test.py", line 24, in run_from_argv
    super().run_from_argv(argv)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 416, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/test.py", line 63, in handle
    failures = test_runner.run_tests(test_labels)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/test/runner.py", line 1098, in run_tests
    self.run_checks(databases)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/test/runner.py", line 1020, in run_checks
    call_command("check", verbosity=self.verbosity, databases=databases)
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/__init__.py", line 194, in call_command
    return command.execute(*args, **defaults)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 460, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/commands/check.py", line 81, in handle
    self.check(
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/management/base.py", line 492, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/registry.py", line 89, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/urls.py", line 16, in check_url_config
    return check_resolver(resolver)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/core/checks/urls.py", line 26, in check_resolver
    return check_method()
           ^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 531, in check
    for pattern in self.url_patterns:
                   ^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/utils/functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 718, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/utils/functional.py", line 47, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/blt-yuw0N2NF-py3.11/lib/python3.11/site-packages/django/urls/resolvers.py", line 711, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/runner/work/BLT/BLT/blt/urls.py", line 401, in <module>
    router.register(r"bounties", BountyViewSet, basename="bounties")
                                 ^^^^^^^^^^^^^
NameError: name 'BountyViewSet' is not defined

For more information, see the Django testing documentation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

1982-2004: Duplicate bounty prevention only works with origin_comment_id.

The duplicate check on lines 1988-1993 only executes when origin_comment_id is provided. If this field is omitted or null, multiple identical bounties can be created for the same issue (same repo_full_name, issue_number, sponsor_github).

While the origin_comment_id approach works for GitHub Action integration (Phase 2), the API should also prevent duplicates from other sources (e.g., manual creation, Slack commands in Phase 3).

Consider adding fallback duplicate detection:

     def create(self, request, *args, **kwargs):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
 
         origin_comment_id = serializer.validated_data.get("origin_comment_id")
+        repo_full_name = serializer.validated_data.get("repo_full_name")
+        issue_number = serializer.validated_data.get("issue_number")
+        sponsor_github = serializer.validated_data.get("sponsor_github")
 
         # Early duplicate check
         if origin_comment_id and Bounty.objects.filter(origin_comment_id=origin_comment_id).exists():
             return Response(
                 {"detail": "Bounty for this GitHub comment already exists."},
                 status=status.HTTP_200_OK,
             )
+
+        # Fallback duplicate check: same sponsor, same issue
+        if sponsor_github and Bounty.objects.filter(
+            repo_full_name=repo_full_name,
+            issue_number=issue_number,
+            sponsor_github=sponsor_github,
+            paid=False
+        ).exists():
+            return Response(
+                {"detail": "You already have an unpaid bounty for this issue."},
+                status=status.HTTP_200_OK,
+            )
 
         try:
             self.perform_create(serializer)

Alternatively, add a database-level unique constraint on (repo_full_name, issue_number, sponsor_github, paid) where paid=False to prevent duplicate active bounties from the same sponsor.

📜 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 bc5d9b1 and 99803a8.

📒 Files selected for processing (2)
  • blt/urls.py (1 hunks)
  • website/api/views.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
website/api/views.py (3)
website/serializers.py (1)
  • BountySerializer (311-328)
website/test_feed.py (1)
  • BountySerializer (127-144)
website/views/models_bounty.py (1)
  • Bounty (7-37)
blt/urls.py (1)
website/api/views.py (1)
  • BountyViewSet (1977-2004)
⏰ 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 (3)
blt/urls.py (1)

401-401: LGTM! Router re-initialization issue resolved.

The bounty route registration is correctly added to the existing router instance. The critical bug flagged in previous reviews (router re-initialization that discarded all prior registrations) has been fixed.

website/api/views.py (2)

21-21: LGTM! Imports correctly added.

The new imports (IntegrityError, BountySerializer, Bounty) are properly placed and necessary for the new bounty functionality.

Also applies to: 59-59, 75-75


1977-1980: LGTM! Permission classes properly configured.

The BountyViewSet now includes permission_classes = [IsAuthenticated] as recommended in previous reviews. The queryset exposes all bounties to authenticated users, which is appropriate for Phase 1.

Comment on lines +702 to +735

@action(detail=False, methods=["get"], url_path="issue-total")
def issue_total(self, request):
repo = request.query_params.get("repo")
issue_number = request.query_params.get("issue_number")

if not repo or not issue_number:
return Response(
{"detail": "Parameters 'repo' and 'issue_number' are required"},
status=status.HTTP_400_BAD_REQUEST,
)

try:
issue_number = int(issue_number)
except (ValueError, TypeError):
return Response(
{"detail": "Parameter 'issue_number' must be a valid integer"},
status=status.HTTP_400_BAD_REQUEST,
)

total = (
Bounty.objects.filter(repo_full_name=repo, issue_number=issue_number)
.aggregate(total=Sum("amount"))
.get("total")
or 0
)

return Response(
{
"repo": repo,
"issue_number": issue_number,
"total": float(total),
}
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move issue_total action to BountyViewSet.

The issue_total action is currently added to OrganizationViewSet (line 694), but this is bounty-specific functionality. This creates an endpoint at /api/v1/organizations/issue-total/ instead of the more logical /api/v1/bounties/issue-total/.

The implementation itself is correct (proper validation, aggregation logic), but the architectural placement is wrong.

Apply this diff to move the action to the correct ViewSet:

 class OrganizationViewSet(viewsets.ModelViewSet):
     queryset = Organization.objects.all()
     serializer_class = OrganizationSerializer
     permission_classes = (IsAuthenticatedOrReadOnly,)
     filter_backends = (filters.SearchFilter,)
     search_fields = ("id", "name")
     http_method_names = ("get", "post", "put")
-
-@action(detail=False, methods=["get"], url_path="issue-total")
-def issue_total(self, request):
-    repo = request.query_params.get("repo")
-    issue_number = request.query_params.get("issue_number")
-
-    if not repo or not issue_number:
-        return Response(
-            {"detail": "Parameters 'repo' and 'issue_number' are required"},
-            status=status.HTTP_400_BAD_REQUEST,
-        )
-
-    try:
-        issue_number = int(issue_number)
-    except (ValueError, TypeError):
-        return Response(
-            {"detail": "Parameter 'issue_number' must be a valid integer"},
-            status=status.HTTP_400_BAD_REQUEST,
-        )
-
-    total = (
-        Bounty.objects.filter(repo_full_name=repo, issue_number=issue_number)
-        .aggregate(total=Sum("amount"))
-        .get("total")
-        or 0
-    )
-
-    return Response(
-        {
-            "repo": repo,
-            "issue_number": issue_number,
-            "total": float(total),
-        }
-    )

Then add it to BountyViewSet (after line 2004):

 class BountyViewSet(viewsets.ModelViewSet):
     queryset = Bounty.objects.all()
     serializer_class = BountySerializer
     permission_classes = [IsAuthenticated]
 
     def create(self, request, *args, **kwargs):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
 
         origin_comment_id = serializer.validated_data.get("origin_comment_id")
 
         # Early duplicate check
         if origin_comment_id and Bounty.objects.filter(origin_comment_id=origin_comment_id).exists():
             return Response(
                 {"detail": "Bounty for this GitHub comment already exists."},
                 status=status.HTTP_200_OK,
             )
 
         try:
             self.perform_create(serializer)
             return Response(serializer.data, status=status.HTTP_201_CREATED)
 
         except IntegrityError:
             # If two requests arrive at the same time
             return Response(
                 {"detail": "Bounty for this GitHub comment already exists."},
                 status=status.HTTP_200_OK,
             )
+
+    @action(detail=False, methods=["get"], url_path="issue-total")
+    def issue_total(self, request):
+        repo = request.query_params.get("repo")
+        issue_number = request.query_params.get("issue_number")
+
+        if not repo or not issue_number:
+            return Response(
+                {"detail": "Parameters 'repo' and 'issue_number' are required"},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+
+        try:
+            issue_number = int(issue_number)
+        except (ValueError, TypeError):
+            return Response(
+                {"detail": "Parameter 'issue_number' must be a valid integer"},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+
+        total = (
+            Bounty.objects.filter(repo_full_name=repo, issue_number=issue_number)
+            .aggregate(total=Sum("amount"))
+            .get("total")
+            or 0
+        )
+
+        return Response(
+            {
+                "repo": repo,
+                "issue_number": issue_number,
+                "total": float(total),
+            }
+        )
📝 Committable suggestion

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

Suggested change
@action(detail=False, methods=["get"], url_path="issue-total")
def issue_total(self, request):
repo = request.query_params.get("repo")
issue_number = request.query_params.get("issue_number")
if not repo or not issue_number:
return Response(
{"detail": "Parameters 'repo' and 'issue_number' are required"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
issue_number = int(issue_number)
except (ValueError, TypeError):
return Response(
{"detail": "Parameter 'issue_number' must be a valid integer"},
status=status.HTTP_400_BAD_REQUEST,
)
total = (
Bounty.objects.filter(repo_full_name=repo, issue_number=issue_number)
.aggregate(total=Sum("amount"))
.get("total")
or 0
)
return Response(
{
"repo": repo,
"issue_number": issue_number,
"total": float(total),
}
)
@action(detail=False, methods=["get"], url_path="issue-total")
def issue_total(self, request):
repo = request.query_params.get("repo")
issue_number = request.query_params.get("issue_number")
if not repo or not issue_number:
return Response(
{"detail": "Parameters 'repo' and 'issue_number' are required"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
issue_number = int(issue_number)
except (ValueError, TypeError):
return Response(
{"detail": "Parameter 'issue_number' must be a valid integer"},
status=status.HTTP_400_BAD_REQUEST,
)
total = (
Bounty.objects.filter(repo_full_name=repo, issue_number=issue_number)
.aggregate(total=Sum("amount"))
.get("total")
or 0
)
return Response(
{
"repo": repo,
"issue_number": issue_number,
"total": float(total),
}
)
🤖 Prompt for AI Agents
In website/api/views.py around lines 702-735 the issue_total action is placed on
OrganizationViewSet but belongs on BountyViewSet; remove the entire method from
OrganizationViewSet and insert the same @action(detail=False, methods=["get"],
url_path="issue-total") def issue_total(self, request): ... implementation into
BountyViewSet after line 2004, preserving the validation, casting, aggregation
and Response logic; ensure proper indentation and imports remain, then run
tests/route checks to confirm the endpoint is now exposed at
/api/v1/bounties/issue-total/.

@Ankit-singh-dot
Copy link
Contributor Author

@CodeRabbit what i have to resolve now what should be next step and how to tackle form there ?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get the PR status and check runs
echo "=== PR Status and Checks ==="
gh pr view 5268 --json statusCheckRollup,labels,commits | jq '.'

echo ""
echo "=== Recent Check Runs Details ==="
# Get more detailed information about failed checks
gh pr checks 5268

echo ""
echo "=== Latest Commit Info ==="
gh pr view 5268 --json commits | jq '.commits[-1]'

Length of output: 16624


🏁 Script executed:

#!/bin/bash

# Get detailed logs from the failed test run
echo "=== Failed Test Run Details ==="
gh run view 20135773969 --log-failed | head -200

echo ""
echo "=== PR Description Check ==="
# Get PR body to see if issue number is missing
gh pr view 5268 --json body,title | jq '.'

Length of output: 8400


🏁 Script executed:

#!/bin/bash

# Get the actual test output showing what failed
echo "=== Test Failures Detail ==="
gh run view 20135773969 --log | grep -A 50 "FAILED\|ERROR\|Traceback" | head -150

echo ""
echo "=== Check validate_pr_closing_issues failure ==="
gh run view 20135828140 --log

Length of output: 28318


🏁 Script executed:

#!/bin/bash

# Check the imports in blt/urls.py to see if BountyViewSet is correctly imported
echo "=== blt/urls.py imports section ==="
cat blt/urls.py | head -50

echo ""
echo "=== Check where BountyViewSet is imported from ==="
rg -n "BountyViewSet" blt/urls.py

echo ""
echo "=== Check if BountyViewSet exists in api/views.py ==="
rg -n "class BountyViewSet" website/api/views.py

echo ""
echo "=== Check the actual import statement in api/views.py ==="
cat website/api/views.py | head -100

Length of output: 4916


🏁 Script executed:

#!/bin/bash

# Check the full import list from website.api.views in blt/urls.py
echo "=== Full import from website.api.views in blt/urls.py ==="
rg -A 30 "from website.api.views import" blt/urls.py

echo ""
echo "=== Verify BountyViewSet is exported from api/views.py ==="
rg -n "^class BountyViewSet" website/api/views.py -A 5

Length of output: 1134


@Ankit-singh-dot I've identified the two issues causing the CI failures. Here's exactly what you need to fix:

🔴 Issue 1: Missing Import (Test Failure)

Problem: You're using BountyViewSet in blt/urls.py at line 401, but it's not imported. This causes Django to crash when loading URLs.

Fix: Add BountyViewSet to the import list in blt/urls.py:

from website.api.views import (
    ActivityLogViewSet,
    AuthApiViewset,
    BountyViewSet,  # ← ADD THIS LINE
    BugHuntApiViewset,
    BugHuntApiViewsetV2,
    CheckDuplicateBugApiView,
    # ... rest of imports

Location: Around line 23-51 in blt/urls.py


🔴 Issue 2: PR Description Format (Workflow Failure)

Problem: Your PR description says "adding feature issue number : #3767", but GitHub requires specific keywords to link issues.

Fix: Edit your PR description and change the first line from:

adding feature issue number  : #3767

To:

Closes #3767

You can use any of these keywords: Closes, Fixes, Resolves, Implements


✅ Next Steps

  1. Add the missing import to blt/urls.py
  2. Edit the PR description to use "Closes GitHub Action & Slackbot: /bounty $X Command #3767"
  3. Commit and push your changes

Once you push these fixes, the CI checks should pass! The pre-commit check is already passing ✅, so you're almost there.

Would you like me to help with anything else once these are fixed?

@github-actions github-actions bot added the last-active: 0d PR last updated 0 days ago label Dec 12, 2025
@github-actions
Copy link
Contributor

💬 Reminder: Unresolved Conversations

Hi @Ankit-singh-dot!

This pull request has 5 unresolved conversations that need to be addressed.

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

Thank you! 🙏

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: 6 PR changes 6 files last-active: 0d PR last updated 0 days ago migrations PR contains database migration files needs-peer-review PR needs peer review pre-commit: passed Pre-commit checks passed tests: failed Django tests failed

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

1 participant