Skip to content

Conversation

@mrkeshav-05
Copy link
Contributor

Proposed change

This PR modularises the badge update with BaseBadgeHandler to share logic and separate handler files for each badge type.

Resolves: #3019

Checklist

  • Required: I read and followed the contributing guidelines
  • Required: I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Summary by CodeRabbit

  • Refactor
    • Badge synchronization reworked: removed the old monolithic command and introduced a common base plus two focused commands for Staff and Project Leader.
  • Tests
    • Added unit tests for the new base and per-badge commands; removed the old monolithic command’s test suite.
  • Chores
    • Build targets updated to run the new per-badge synchronization commands.

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

Walkthrough

Replaces a single monolithic badge-sync command with a modular system: adds an abstract BaseBadgeCommand, two concrete commands (staff and project-leader), updates Makefile targets, removes the old combined command and its tests, and adds focused tests for the new components.

Changes

Cohort / File(s) Summary
Removed monolithic command
backend/apps/nest/management/commands/nest_update_badges.py
Deleted the original combined management command and all badge-sync logic, constants, and helper methods.
Base command
backend/apps/nest/management/commands/base_badge_command.py
Added BaseBadgeCommand (abstract) with class-level badge metadata, abstract get_eligible_users(), _log(), and concrete handle() implementing badge creation/update, idempotent assignment/reactivation, bulk deactivation, logging, and error handling.
Per-badge commands
backend/apps/nest/management/commands/nest_update_staff_badge.py, backend/apps/nest/management/commands/nest_update_project_leader_badge.py
Added two concrete commands extending BaseBadgeCommand, each setting badge metadata and implementing get_eligible_users() (staff flag and project-leader EntityMember query).
Makefile targets
backend/apps/nest/Makefile
Replaced single nest-update-badges invocation with nest-update-staff-badge and nest-update-project-leader-badge targets; added aggregator nest-update-badges depending on both.
Removed tests
backend/tests/apps/nest/management/commands/nest_update_badges_test.py
Deleted the comprehensive test suite for the old monolithic command.
New tests
backend/tests/apps/nest/management/commands/base_badge_command_test.py, backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py, backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
Added unit tests for BaseBadgeCommand validation/sync behavior and tests for staff and project-leader commands covering metadata, successful run, and error propagation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

level 2

Suggested reviewers

  • kasya
  • arkid15r

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.78% 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
Title check ✅ Passed The title accurately summarizes the main changes: refactoring badge updates, introducing a base class, and adding tests.
Description check ✅ Passed The description relates to the changeset by explaining the modularization approach and referencing issue #3019 which the PR is meant to resolve.
Linked Issues check ✅ Passed The PR meets all coding requirements from issue #3019: separate handler files for each badge type [nest_update_staff_badge.py, nest_update_project_leader_badge.py], BaseBadgeCommand base class, comprehensive tests for both base class and handlers, and clear code structure.
Out of Scope Changes check ✅ Passed All changes directly support the refactoring objectives: base class implementation, individual badge handlers, test suites, and Makefile updates to orchestrate separate commands are all aligned with issue #3019 requirements.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5699b90 and a5dffd7.

📒 Files selected for processing (1)
  • backend/apps/nest/management/commands/base_badge_command.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/nest/management/commands/base_badge_command.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/apps/nest/badges/project_leader_badge.py (1)

28-47: Consider caching the ContentType lookup.

The eligibility logic is correct and comprehensive. The filters appropriately identify active, reviewed project leaders with linked user accounts.

For improved efficiency, consider caching ContentType.objects.get_for_model(Project) as a class attribute, since it's called every time get_eligible_users() runs and always returns the same value.

🔎 Suggested optimization

At the class level, add:

class OWASPProjectLeaderBadgeHandler(BaseBadgeHandler):
    """Handler for managing the OWASP Project Leader badge.

    This badge is awarded to users who are active and reviewed leaders
    of OWASP projects. It uses the EntityMember model for users with the LEADER role.
    """

    name = "OWASP Project Leader"
    description = "Official OWASP Project Leader"
    css_class = "fa-user-shield"
    weight = 90
    
    # Cache the content type at class level
    _project_content_type = None
    
    @classmethod
    def get_project_content_type(cls):
        if cls._project_content_type is None:
            cls._project_content_type = ContentType.objects.get_for_model(Project)
        return cls._project_content_type

Then update the method:

     def get_eligible_users(self) -> QuerySet[User]:
         # Get IDs of users who are active and reviewed project leaders
         leader_ids = EntityMember.objects.filter(
-            entity_type=ContentType.objects.get_for_model(Project),
+            entity_type=self.get_project_content_type(),
             role=EntityMember.Role.LEADER,
             is_active=True,
             is_reviewed=True,
             member__isnull=False,
         ).values_list("member_id", flat=True)
         return User.objects.filter(id__in=leader_ids).distinct()
backend/apps/nest/badges/base.py (1)

75-81: Consider bulk operations for better performance.

The current implementation uses individual get_or_create and save calls in a loop. For large user bases, this could be optimized using bulk operations to reduce database round-trips.

💡 Potential optimization approach
# Get existing UserBadge records
existing_user_badges = UserBadge.objects.filter(
    user__in=users_to_add,
    badge=badge
).select_related('user')

existing_map = {ub.user_id: ub for ub in existing_user_badges}

# Prepare lists for bulk operations
to_create = []
to_update = []

for user in users_to_add:
    if user.id in existing_map:
        user_badge = existing_map[user.id]
        if not user_badge.is_active:
            user_badge.is_active = True
            to_update.append(user_badge)
    else:
        to_create.append(UserBadge(user=user, badge=badge, is_active=True))

# Bulk create and update
if to_create:
    UserBadge.objects.bulk_create(to_create)
if to_update:
    UserBadge.objects.bulk_update(to_update, ['is_active'])

added_count = len(to_create) + len(to_update)

Note: This optimization is optional and can be deferred if the current performance is acceptable.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e38d6a and 2a6afd6.

📒 Files selected for processing (5)
  • backend/apps/nest/badges/__init__.py
  • backend/apps/nest/badges/base.py
  • backend/apps/nest/badges/project_leader_badge.py
  • backend/apps/nest/badges/staff_badge.py
  • backend/apps/nest/management/commands/nest_update_badges.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-01T04:15:32.151Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 1823
File: frontend/__tests__/e2e/pages/Login.spec.ts:28-34
Timestamp: 2025-08-01T04:15:32.151Z
Learning: In the OWASP Nest project, the login page (/auth/login) handles only authentication (GitHub OAuth) and does not differentiate between OWASP staff and non-staff users. The role-based access control using the is_owasp_staff field happens after authentication in downstream components like DashboardWrapper and ProjectsWrapper, not during the login process itself.

Applied to files:

  • backend/apps/nest/badges/staff_badge.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (4)
backend/apps/nest/badges/__init__.py (3)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (16-47)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (13-28)
backend/apps/nest/management/commands/nest_update_badges.py (4)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (16-47)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (13-28)
backend/apps/nest/badges/base.py (1)
  • process (47-97)
backend/apps/nest/badges/project_leader_badge.py (2)
backend/apps/nest/badges/base.py (2)
  • BaseBadgeHandler (13-97)
  • get_eligible_users (26-30)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/badges/base.py (3)
backend/apps/nest/models/user_badge.py (1)
  • UserBadge (10-50)
backend/apps/nest/badges/project_leader_badge.py (1)
  • get_eligible_users (28-47)
backend/apps/nest/badges/staff_badge.py (1)
  • get_eligible_users (21-28)
🪛 Ruff (0.14.10)
backend/apps/nest/badges/__init__.py

6-9: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

backend/apps/nest/badges/staff_badge.py

22-27: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


25-25: Missing blank line after last section ("Returns")

Add blank line after "Returns"

(D413)

backend/apps/nest/badges/project_leader_badge.py

29-37: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


29-37: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


35-35: Missing blank line after last section ("Returns")

Add blank line after "Returns"

(D413)

backend/apps/nest/badges/base.py

1-1: Missing docstring in public module

(D100)


21-21: Missing docstring in __init__

(D107)


27-29: One-line docstring should fit on one line

Reformat to one line

(D200)


27-29: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


30-30: Unnecessary pass statement

Remove unnecessary pass

(PIE790)


32-32: Missing docstring in public method

(D102)


40-40: First line of docstring should be in imperative mood: "Helper to log to both file logger and stdout if available."

(D401)


48-53: 1 blank line required between summary line and description

(D205)


48-53: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


48-53: First line of docstring should be in imperative mood: "Main execution method to sync the badge."

(D401)


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

(TRY003)


55-55: Exception must not use a string literal, assign to variable first

Assign to variable; remove string literal

(EM101)


97-97: No newline at end of file

Add trailing newline

(W292)

🔇 Additional comments (5)
backend/apps/nest/badges/staff_badge.py (2)

13-19: LGTM!

The class definition and badge metadata are correctly structured. The weight of 100 appropriately positions the staff badge with high priority.


21-28: LGTM!

The eligibility logic correctly filters users by the is_owasp_staff field, which aligns with the project's role-based access control approach.

backend/apps/nest/badges/__init__.py (1)

1-9: LGTM!

The package initialization correctly exposes the two badge handlers, establishing a clean public API for the badges module.

backend/apps/nest/badges/project_leader_badge.py (1)

16-26: LGTM!

The class definition and badge metadata are well-structured. The weight of 90 appropriately positions the project leader badge below the staff badge.

backend/apps/nest/management/commands/nest_update_badges.py (1)

20-37: Excellent refactoring with proper error isolation!

The new pluggable handler architecture cleanly separates concerns and improves maintainability. The per-handler try/except ensures that a failure in one badge type doesn't prevent other badges from being processed. Error logging and styled output align with Django management command best practices.

Based on learnings, the use of self.stdout.write and self.style.SUCCESS/ERROR is appropriate for Django management commands.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 25, 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: 0

🧹 Nitpick comments (7)
backend/tests/apps/nest/badges/staff_badge_test.py (1)

10-18: Add trailing newline and consider asserting the return value.

The test verifies the filter is called correctly but doesn't assert that get_eligible_users() returns the expected queryset. Consider adding an assertion for completeness.

🔎 Proposed enhancement
     @patch("apps.nest.badges.staff_badge.User")
     def test_get_eligible_users_filters_staff(self, mock_user_model):
         """Test that get_eligible_users filters for is_owasp_staff=True."""
         handler = OWASPStaffBadgeHandler()
-        handler.get_eligible_users()
+        result = handler.get_eligible_users()

         mock_user_model.objects.filter.assert_called_once_with(is_owasp_staff=True)
+        assert result == mock_user_model.objects.filter.return_value
+
backend/tests/apps/nest/management/commands/nest_update_badges_test.py (1)

76-86: Add trailing newline.

The test correctly verifies exception logging with stack traces. Minor: add a trailing newline at end of file.

🔎 Proposed fix
         mock_handlers_dict.keys.return_value = ["staff"]
         call_command("nest_update_badges", stdout=StringIO())
         mock_logger.exception.assert_called_with("Error processing badge %s", "OWASP Staff")
+
backend/tests/apps/nest/badges/project_leader_badge_test.py (2)

7-8: Remove unused import.

EntityMember is imported but not used in the test. The assertion on line 38 uses mock_entity_member.Role.LEADER (the patched mock), not the actual import.

🔎 Proposed fix
 from apps.nest.badges.project_leader_badge import OWASPProjectLeaderBadgeHandler
-from apps.owasp.models.entity_member import EntityMember

43-44: Add trailing newline.

🔎 Proposed fix
         mock_user_model.objects.filter.assert_called_once_with(id__in=[101, 102])
         mock_user_model.objects.filter.return_value.distinct.assert_called_once()
+
backend/apps/nest/management/commands/nest_update_badges.py (1)

43-52: Consider differentiating success message when handlers fail.

The "User badges sync completed" message prints unconditionally, even if all handlers failed. This could be misleading in error scenarios.

🔎 Proposed enhancement
+        failed_count = 0
         for handler_class in handlers_to_run:
             try:
                 self.stdout.write(f"Processing badge: {handler_class.name}")
                 handler = handler_class(stdout=self.stdout, style=self.style)
                 handler.process()
             except Exception:
                 logger.exception("Error processing badge %s", handler_class.name)
                 self.stdout.write(self.style.ERROR(f"Failed to update {handler_class.name}"))
+                failed_count += 1

-        self.stdout.write(self.style.SUCCESS("User badges sync completed"))
+        if failed_count:
+            self.stdout.write(
+                self.style.WARNING(f"User badges sync completed with {failed_count} failure(s)")
+            )
+        else:
+            self.stdout.write(self.style.SUCCESS("User badges sync completed"))
backend/tests/apps/nest/badges/base_test.py (2)

11-19: Add blank line after class docstring.

Per PEP 257 convention (flagged by static analysis D204).

🔎 Proposed fix
 class ConcreteTestHandler(BaseBadgeHandler):
     """Concrete implementation of BaseBadgeHandler for testing purposes."""
+
     name = "Test Badge"
     description = "Test Description"

75-95: Add trailing newline; tests look good.

The revocation test correctly verifies the bulk update flow. Add trailing newline at end of file.

🔎 Proposed fix
         mock_revocation_qs.update.assert_called_with(is_active=False)
         self.assertIn("Removed 'Test Badge' badge from 5 users", self.out.getvalue())
+
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a6afd6 and 1e54fb8.

📒 Files selected for processing (10)
  • backend/apps/nest/Makefile
  • backend/apps/nest/badges/__init__.py
  • backend/apps/nest/badges/base.py
  • backend/apps/nest/badges/project_leader_badge.py
  • backend/apps/nest/badges/staff_badge.py
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/tests/apps/nest/badges/base_test.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
  • backend/tests/apps/nest/badges/staff_badge_test.py
  • backend/tests/apps/nest/management/commands/nest_update_badges_test.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/apps/nest/badges/staff_badge.py
  • backend/apps/nest/badges/init.py
  • backend/apps/nest/badges/base.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-06T19:03:01.985Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2223
File: backend/apps/owasp/models/common.py:213-232
Timestamp: 2025-09-06T19:03:01.985Z
Learning: In the OWASP Nest project, the get_leaders_emails() method in RepositoryBasedEntityModel is designed to only capture leaders with mailto: links from leaders.md files, intentionally ignoring plain text names without email addresses. The current regex implementation works correctly for the intended behavior as validated by comprehensive test cases.

Applied to files:

  • backend/apps/nest/badges/project_leader_badge.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/nest/management/commands/nest_update_badges_test.py
  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (5)
backend/apps/nest/badges/project_leader_badge.py (3)
backend/apps/nest/badges/base.py (2)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/badges/staff_badge.py (1)
  • get_eligible_users (17-24)
backend/tests/apps/nest/management/commands/nest_update_badges_test.py (3)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/github/management/commands/github_update_related_organizations_test.py (1)
  • mock_logger (18-22)
backend/tests/apps/nest/badges/base_test.py (1)
backend/apps/nest/badges/base.py (3)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
  • process (48-86)
backend/apps/nest/management/commands/nest_update_badges.py (5)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (12-34)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (9-24)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/nest/badges/project_leader_badge_test.py (4)
backend/apps/nest/badges/project_leader_badge.py (1)
  • get_eligible_users (20-34)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/badges/base.py (1)
  • get_eligible_users (29-30)
backend/tests/apps/nest/badges/base_test.py (1)
  • get_eligible_users (18-19)
🪛 Ruff (0.14.10)
backend/tests/apps/nest/badges/staff_badge_test.py

1-1: File backend/tests/apps/nest/badges/staff_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


18-18: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/nest/badges/base_test.py

1-1: File backend/tests/apps/nest/badges/base_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


12-12: 1 blank line required after class docstring

Insert 1 blank line after class docstring

(D204)


50-50: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


73-73: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


95-95: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


95-95: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/nest/badges/project_leader_badge_test.py

1-1: File backend/tests/apps/nest/badges/project_leader_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


44-44: No newline at end of file

Add trailing newline

(W292)

🔇 Additional comments (7)
backend/apps/nest/Makefile (1)

1-3: LGTM!

The addition of $(ARGS) enables passing the new --handler argument to the management command, aligning with the refactored handler-driven architecture.

backend/tests/apps/nest/management/commands/nest_update_badges_test.py (2)

13-34: LGTM!

Good test coverage for the all-handlers scenario. The mocking approach correctly isolates the handler instantiation and process calls.


52-74: LGTM!

Excellent test for error resilience - verifies that a failing handler doesn't prevent subsequent handlers from executing, and that appropriate error messages are written to stdout.

backend/apps/nest/badges/project_leader_badge.py (1)

12-34: LGTM!

The implementation correctly queries for active, reviewed project leaders with linked user accounts. The distinct() is a good defensive measure.

One minor consideration: ContentType.objects.get_for_model(Project) is called on every invocation. Django caches ContentType lookups internally, so this is fine for the current use case.

backend/apps/nest/management/commands/nest_update_badges.py (1)

17-20: LGTM!

Clean handler registry pattern that makes adding new badge handlers straightforward. The keys provide intuitive CLI identifiers.

backend/tests/apps/nest/badges/base_test.py (2)

28-50: LGTM!

Good coverage of badge creation flow. Verifies get_or_create is called with correct defaults and that the creation message is logged.


52-73: LGTM!

Thorough test of the badge assignment flow - correctly verifies UserBadge creation, activation toggle, and save with update_fields.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/tests/apps/nest/badges/staff_badge_test.py (1)

1-19: Consider adding __init__.py for explicit test package structure.

The test directory backend/tests/apps/nest/badges/ would benefit from an explicit __init__.py file to ensure consistent test discovery across different test runners and Python environments, though implicit namespace packages work in Python 3.3+.

📝 Create `__init__.py` in test directories

Create an empty backend/tests/apps/nest/badges/__init__.py:

"""Tests for nest badge handlers."""

Apply this to all intermediate test directories if not already present:

  • backend/tests/apps/nest/badges/__init__.py

Test implementation looks solid.

The test correctly verifies that get_eligible_users calls User.objects.filter with the expected parameter and returns the filtered queryset.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e54fb8 and 4bab2d4.

📒 Files selected for processing (4)
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/tests/apps/nest/badges/base_test.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
  • backend/tests/apps/nest/badges/staff_badge_test.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (3)
backend/apps/nest/management/commands/nest_update_badges.py (3)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (12-34)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (9-24)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/nest/badges/base_test.py (3)
backend/apps/github/api/internal/nodes/user.py (1)
  • badges (37-51)
backend/apps/nest/badges/base.py (3)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
  • process (48-86)
backend/apps/nest/badges/staff_badge.py (1)
  • get_eligible_users (17-24)
backend/tests/apps/nest/badges/project_leader_badge_test.py (2)
backend/apps/nest/badges/project_leader_badge.py (2)
  • OWASPProjectLeaderBadgeHandler (12-34)
  • get_eligible_users (20-34)
backend/apps/owasp/models/entity_member.py (1)
  • Role (13-16)
🪛 Ruff (0.14.10)
backend/tests/apps/nest/badges/base_test.py

1-1: File backend/tests/apps/nest/badges/base_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


51-51: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


74-74: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)


96-96: Use a regular assert instead of unittest-style assertIn

Replace assertIn(...) with assert ...

(PT009)

backend/tests/apps/nest/badges/project_leader_badge_test.py

1-1: File backend/tests/apps/nest/badges/project_leader_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/nest/badges/staff_badge_test.py

1-1: File backend/tests/apps/nest/badges/staff_badge_test.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

🔇 Additional comments (7)
backend/tests/apps/nest/badges/project_leader_badge_test.py (1)

1-43: Comprehensive test coverage for project leader badge logic.

The test thoroughly validates the multi-step query process:

  • ContentType resolution for the Project model
  • EntityMember filtering with correct role, status, and relationship checks
  • User filtering by leader IDs with distinct() applied

The mock setup and assertions align well with the handler implementation.

backend/apps/nest/management/commands/nest_update_badges.py (2)

17-30: Well-designed pluggable handler registry.

The BADGE_HANDLERS dictionary provides a clean extension point for adding new badge types. The --handler argument with choices validation ensures only valid handlers can be selected, while allowing all handlers to run when omitted.


32-61: Robust handler execution with proper error handling.

The implementation correctly:

  • Selects handlers based on CLI argument
  • Instantiates each handler with stdout and style for output formatting
  • Isolates failures with per-handler try/except blocks so one failure doesn't abort the entire run
  • Uses logger.exception to capture full tracebacks
  • Reports final status with failure count

The use of self.stdout.write() and self.style methods aligns with Django management command conventions. Based on learnings, this is the preferred approach over print().

backend/tests/apps/nest/badges/base_test.py (4)

11-27: Solid test infrastructure for abstract base class.

The ConcreteTestHandler provides a minimal concrete implementation suitable for testing the base class logic, and the setUp method correctly initializes output capture for verifying _log calls.


29-51: Badge creation test correctly validates core behavior.

The test verifies that process() calls Badge.objects.get_or_create with the correct badge name and defaults (description, css_class, weight), and confirms the creation message is logged.


53-74: Badge assignment test validates activation logic.

The test correctly verifies that process():

  • Creates or retrieves UserBadge for eligible users
  • Activates inactive badges by setting is_active=True
  • Saves with update_fields for efficiency
  • Logs the count of added badges

76-96: Badge revocation test validates cleanup logic.

The test correctly verifies that process():

  • Identifies UserBadge records for users no longer eligible
  • Bulk updates them with is_active=False
  • Logs the count of revoked badges

The use of assertIn is appropriate for SimpleTestCase (unittest-based tests). The static analysis suggestion to use assert instead applies to pytest-style tests, not Django's unittest framework.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/tests/apps/nest/badges/base_test.py (1)

54-77: Consider mocking the revocation branch for better test isolation.

The test correctly validates the badge addition flow. However, the revocation branch (UserBadge.objects.filter(...).exclude(...)) is not explicitly mocked. Due to MagicMock's default behavior, this branch will execute with a truthy count() value, causing unintended log output during the test.

While the test passes because the assertion only checks for the "Added" message, explicitly mocking the revocation path would make the test more precise.

🔎 Optional: Add explicit mock for revocation path
         mock_user_badge = MagicMock()
         mock_user_badge.is_active = False
         mock_user_badge_model.objects.get_or_create.return_value = (mock_user_badge, False)
+
+        # Mock revocation path to return no users to revoke
+        mock_revocation_qs = MagicMock()
+        mock_revocation_qs.count.return_value = 0
+        mock_user_badge_model.objects.filter.return_value.exclude.return_value = mock_revocation_qs
+
         self.handler.process()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bab2d4 and e7132f6.

📒 Files selected for processing (5)
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/tests/apps/nest/badges/__init__.py
  • backend/tests/apps/nest/badges/base_test.py
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
  • backend/tests/apps/nest/badges/staff_badge_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/nest/badges/project_leader_badge_test.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_badges.py
🧬 Code graph analysis (3)
backend/apps/nest/management/commands/nest_update_badges.py (3)
backend/apps/nest/badges/project_leader_badge.py (1)
  • OWASPProjectLeaderBadgeHandler (12-34)
backend/apps/nest/badges/staff_badge.py (1)
  • OWASPStaffBadgeHandler (9-24)
backend/apps/nest/badges/base.py (1)
  • process (48-86)
backend/tests/apps/nest/badges/base_test.py (1)
backend/apps/nest/badges/base.py (3)
  • BaseBadgeHandler (15-86)
  • get_eligible_users (29-30)
  • process (48-86)
backend/tests/apps/nest/badges/staff_badge_test.py (4)
backend/apps/nest/badges/staff_badge.py (2)
  • OWASPStaffBadgeHandler (9-24)
  • get_eligible_users (17-24)
backend/tests/apps/nest/badges/base_test.py (1)
  • get_eligible_users (19-20)
backend/apps/nest/badges/base.py (1)
  • get_eligible_users (29-30)
backend/apps/nest/api/internal/nodes/user.py (1)
  • is_owasp_staff (14-16)
🔇 Additional comments (6)
backend/tests/apps/nest/badges/staff_badge_test.py (1)

10-18: LGTM!

Clean and focused test that correctly validates the core filtering logic. The patch target path is correct (patching where the User is imported in the module under test), and the assertion properly verifies both the filter call arguments and the return value.

backend/apps/nest/management/commands/nest_update_badges.py (2)

44-52: Robust error handling with continuation.

Good pattern: catching exceptions per-handler allows other handlers to proceed even if one fails. Using logger.exception captures the full traceback for debugging.

One minor consideration: if a handler class doesn't define name (violating the base class contract), accessing handler_class.name on line 46 could raise AttributeError before process() is called. However, since the handlers are well-defined in this PR and the base class enforces name via its contract, this is acceptable.


32-41: LGTM!

Clean implementation of the handler selection logic. Using self.stdout.write aligns with Django management command conventions. Based on learnings, this approach improves testability compared to using print().

backend/tests/apps/nest/badges/base_test.py (3)

30-52: LGTM!

Good test coverage for the badge creation path. Correctly verifies:

  • get_or_create is called with the expected name and defaults
  • The "Created badge" log message appears when a new badge is created

79-101: LGTM!

Good test coverage for the revocation path. The mock setup correctly chains filter().exclude() and verifies the update(is_active=False) call.


11-20: LGTM!

Clean concrete test handler implementation that satisfies the abstract base class contract. Defining all required attributes (name, description, css_class, weight) ensures the process() method can execute without raising validation errors.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 25, 2025
@mrkeshav-05 mrkeshav-05 marked this pull request as ready for review December 25, 2025 22:14
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

Actually, I expected it to have a management command base and derived per badge management commands. What do you think about this refactoring?

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 (6)
backend/apps/nest/management/commands/base_badge_command.py (2)

35-36: Consider defining the error message as a constant.

The static analysis tool flags the inline string literal in the exception. While this is functional, defining it as a module-level constant improves consistency.

🔎 Optional refactor
+BADGE_NAME_REQUIRED_ERROR = "badge_name must be set"
+
 class BaseBadgeCommand(BaseCommand, ABC):
     """Base class for badge sync commands.
     
     Subclasses must define badge metadata and implement get_eligible_users().
     """

     badge_name: str = None
     badge_description: str = None
     badge_css_class: str = None
     badge_weight: int = None

     @abstractmethod
     def get_eligible_users(self) -> QuerySet[User]:
         """Return users who should have this badge."""

     def _log(self, message):
         logger.info(message)
         self.stdout.write(message)

     def handle(self, *args, **options):
         if not self.badge_name:
-            raise ValueError("badge_name must be set")
+            raise ValueError(BADGE_NAME_REQUIRED_ERROR)

59-66: Consider bulk operations for improved performance.

The current implementation loops through users_to_add and performs individual get_or_create and save operations. For large user sets, this results in N database queries.

Consider using bulk_create with ignore_conflicts=True for new UserBadge records and bulk_update for reactivations. This would reduce database round-trips significantly.

🔎 Suggested optimization
         users_to_add = eligible_users.exclude(
             user_badges__badge=badge,
             user_badges__is_active=True,
         )

-        added_count = 0
-        for user in users_to_add:
-            user_badge, _ = UserBadge.objects.get_or_create(user=user, badge=badge)
-            if not user_badge.is_active:
-                user_badge.is_active = True
-                user_badge.save(update_fields=["is_active"])
-                added_count += 1
+        # Get existing inactive badges that need reactivation
+        existing_inactive = UserBadge.objects.filter(
+            user__in=users_to_add,
+            badge=badge,
+            is_active=False
+        )
+        reactivated_count = existing_inactive.update(is_active=True)
+        
+        # Create new badges for users without any existing UserBadge
+        existing_user_ids = set(existing_inactive.values_list("user_id", flat=True))
+        new_badges = [
+            UserBadge(user=user, badge=badge, is_active=True)
+            for user in users_to_add
+            if user.id not in existing_user_ids
+        ]
+        UserBadge.objects.bulk_create(new_badges, ignore_conflicts=True)
+        added_count = reactivated_count + len(new_badges)
backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (1)

34-40: Consider being more specific about the expected exception.

The test currently catches any Exception, which is too broad. Since the base command re-raises exceptions, this test should verify that the specific exception from the mock is properly propagated.

🔎 Suggested improvement
     @patch(
         "apps.nest.management.commands.nest_update_staff_badge.User.objects.filter",
-        side_effect=Exception("error"),
+        side_effect=RuntimeError("Database error"),
     )
     def test_handles_errors(self, mock_filter):
-        with self.assertRaises(Exception):
+        with self.assertRaises(RuntimeError) as context:
             call_command("nest_update_staff_badge", stdout=StringIO())
+        self.assertIn("Database error", str(context.exception))
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)

20-28: Consider caching the ContentType lookup for better performance.

The query logic correctly identifies project leaders through EntityMember filtering. However, ContentType.objects.get_for_model(Project) is evaluated every time the command runs, which involves a database query.

🔎 Performance optimization

Define the ContentType as a cached class attribute to avoid repeated database lookups:

 class Command(BaseBadgeCommand):
     help = "Sync OWASP Project Leader badges"
     
     badge_name = "OWASP Project Leader"
     badge_description = "Official OWASP Project Leader"
     badge_css_class = "fa-user-shield"
     badge_weight = 90
+    
+    _project_content_type = None
+    
+    @classmethod
+    def get_project_content_type(cls):
+        if cls._project_content_type is None:
+            cls._project_content_type = ContentType.objects.get_for_model(Project)
+        return cls._project_content_type

     def get_eligible_users(self) -> QuerySet[User]:
         leader_ids = EntityMember.objects.filter(
-            entity_type=ContentType.objects.get_for_model(Project),
+            entity_type=self.get_project_content_type(),
             role=EntityMember.Role.LEADER,
             is_active=True,
             is_reviewed=True,
             member__isnull=False,
         ).values_list("member_id", flat=True)
         return User.objects.filter(id__in=leader_ids).distinct()
backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (2)

17-43: Test coverage looks good, but consider a more precise assertion.

The mocking comprehensively covers all database interactions and correctly simulates the command's execution flow. However, line 43 checks for "Project Leader" in the output, while the equivalent staff badge test verifies the full badge name "OWASP Staff".

🔎 Suggested refinement for consistency
-        assert "Project Leader" in out.getvalue()
+        assert "OWASP Project Leader" in out.getvalue()

This makes the assertion more precise and consistent with the staff badge test pattern.


45-51: Error handling test works, but consider using a specific exception type.

The test correctly verifies that exceptions during badge synchronization are propagated. Using assertRaises is appropriate here since the test inherits from SimpleTestCase (Django's unittest-based class), so the static analysis suggestion to use pytest.raises doesn't apply.

However, the static analysis hint about catching a blind Exception is valid. Consider using a more specific exception type (e.g., ValueError, DatabaseError) to avoid catching unexpected errors. That said, this pattern matches the staff badge test, suggesting it's an intentional design choice for consistency across badge command tests.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7132f6 and 7cd1fdc.

📒 Files selected for processing (9)
  • backend/apps/nest/Makefile
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
  • backend/tests/apps/nest/management/commands/nest_update_badges_test.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
💤 Files with no reviewable changes (2)
  • backend/apps/nest/management/commands/nest_update_badges.py
  • backend/tests/apps/nest/management/commands/nest_update_badges_test.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
📚 Learning: 2025-11-23T11:37:26.253Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2606
File: backend/apps/api/rest/v0/project.py:43-48
Timestamp: 2025-11-23T11:37:26.253Z
Learning: In the OWASP Nest backend, `entity_leaders` is a `property` method defined in `RepositoryBasedEntityModel` (backend/apps/owasp/models/common.py) that returns a dynamically constructed QuerySet. It cannot be prefetched using standard `prefetch_related()` because Django's prefetch mechanism only works on model fields and relations, not property methods.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
🧬 Code graph analysis (5)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (1)
backend/apps/nest/management/commands/base_badge_command.py (3)
  • BaseBadgeCommand (15-83)
  • get_eligible_users (27-28)
  • handle (34-83)
backend/apps/nest/management/commands/base_badge_command.py (4)
backend/apps/nest/models/user_badge.py (1)
  • UserBadge (10-50)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • get_eligible_users (20-28)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • get_eligible_users (17-18)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (2)
  • get_eligible_users (17-18)
  • get_eligible_users (24-25)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (3)
backend/apps/nest/management/commands/base_badge_command.py (2)
  • BaseBadgeCommand (15-83)
  • get_eligible_users (27-28)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/management/commands/nest_update_staff_badge.py (2)
  • Command (9-18)
  • get_eligible_users (17-18)
backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (3)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • Command (12-28)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • Command (9-18)
backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (3)
  • test_has_correct_metadata (13-15)
  • test_command_runs (20-32)
  • test_handles_errors (38-40)
backend/apps/nest/management/commands/nest_update_staff_badge.py (4)
backend/apps/nest/management/commands/base_badge_command.py (2)
  • BaseBadgeCommand (15-83)
  • get_eligible_users (27-28)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (2)
  • Command (12-28)
  • get_eligible_users (20-28)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (2)
  • get_eligible_users (17-18)
  • get_eligible_users (24-25)
backend/apps/nest/api/internal/nodes/user.py (1)
  • is_owasp_staff (14-16)
🪛 checkmake (0.2.2)
backend/apps/nest/Makefile

[warning] 1-1: Missing required phony target "all"

(minphony)


[warning] 1-1: Missing required phony target "clean"

(minphony)


[warning] 1-1: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.10)
backend/tests/apps/nest/management/commands/base_badge_command_test.py

27-27: Use pytest.raises instead of unittest-style assertRaises

Replace assertRaises with pytest.raises

(PT027)

backend/apps/nest/management/commands/base_badge_command.py

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

(TRY003)


36-36: Exception must not use a string literal, assign to variable first

Assign to variable; remove string literal

(EM101)

backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py

39-39: Use pytest.raises instead of unittest-style assertRaises

Replace assertRaises with pytest.raises

(PT027)


39-39: Do not assert blind exception: Exception

(B017)

backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py

50-50: Use pytest.raises instead of unittest-style assertRaises

Replace assertRaises with pytest.raises

(PT027)


50-50: Do not assert blind exception: Exception

(B017)

🔇 Additional comments (9)
backend/apps/nest/management/commands/base_badge_command.py (2)

1-32: LGTM! Clean structure and proper use of Django conventions.

The base class setup is well-designed:

  • Proper use of ABC with abstract method decorator
  • _log method correctly uses both logger and stdout per Django best practices
  • Clear documentation and metadata attributes

68-83: LGTM! Efficient removal logic and appropriate error handling.

The badge removal implementation correctly uses bulk update() for deactivation, avoiding N+1 queries. The broad exception catch with logging and re-raise is appropriate for a management command.

backend/tests/apps/nest/management/commands/base_badge_command_test.py (2)

21-29: LGTM! Effective validation test.

The test correctly verifies that a ValueError is raised when badge_name is not set. The logic is sound.

Note: Ruff suggests using pytest.raises instead of assertRaises for consistency with pytest conventions, but this is a stylistic preference rather than a functional issue.


31-51: LGTM! Well-structured integration test.

The test effectively validates the complete sync flow with appropriate mocking of database operations. Output assertions confirm the command executes successfully.

backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (1)

12-32: LGTM! Comprehensive test coverage.

The tests effectively validate:

  • Correct badge metadata configuration
  • Successful execution path with proper mocking
  • Output contains expected badge name
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)

1-18: LGTM! Clean and straightforward implementation.

This command correctly extends BaseBadgeCommand with:

  • Appropriate metadata for the "OWASP Staff" badge
  • Simple and efficient get_eligible_users() implementation that filters users by the is_owasp_staff flag

The implementation is idiomatic and follows the established pattern.

backend/apps/nest/Makefile (1)

1-11: LGTM! Clean modular Makefile structure.

The refactored targets properly separate per-badge updates and provide a convenient aggregate target. The command invocations correctly reference the new per-badge management commands, and the echo messages provide clear user feedback.

Note: The static analysis warnings about missing phony targets (all, clean, test) are false positives—this appears to be a partial Makefile snippet likely included by a parent Makefile.

backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (2)

1-12: LGTM! Appropriate test structure.

The imports are correct and SimpleTestCase is the right choice since all database interactions are mocked in the tests.


13-15: LGTM! Metadata assertions are correct.

The test correctly verifies the command's badge name and weight match the expected values defined in the Command class.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 1, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (2)

18-33: Mock setup could more accurately reflect queryset behavior.

Line 27 sets qs.exclude.return_value = [], which works but returns a plain list instead of a queryset-like object. While this is functional for the test, consider returning a MagicMock that better mimics a QuerySet's iteration behavior for consistency with the actual command's expectations.

🔎 Suggested improvement
     qs = MagicMock()
-    qs.exclude.return_value = []
+    qs.exclude.return_value = MagicMock()
+    qs.exclude.return_value.__iter__ = lambda self: iter([])
     mock_user.objects.filter.return_value = qs

35-46: Consider verifying error message output in error handling test.

The test confirms that the exception propagates (line 45), but the base command's exception handler (lines 79-82 in base_badge_command.py) also writes an error message to stdout via self.stdout.write(self.style.ERROR(...)). For more thorough coverage, consider asserting that the error output contains the expected failure message.

Example enhancement
out = StringIO()
with pytest.raises(Exception, match="error"):
    call_command("nest_update_staff_badge", stdout=out)
assert "sync failed" in out.getvalue()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cd1fdc and 3edb353.

📒 Files selected for processing (6)
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
🧬 Code graph analysis (1)
backend/apps/nest/management/commands/base_badge_command.py (4)
backend/apps/nest/models/user_badge.py (1)
  • UserBadge (10-50)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • get_eligible_users (20-28)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • get_eligible_users (17-18)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (2)
  • get_eligible_users (18-19)
  • get_eligible_users (25-26)
🔇 Additional comments (2)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)

9-18: LGTM! Clean command implementation.

The command correctly extends BaseBadgeCommand, defines all required metadata attributes, and implements get_eligible_users with a straightforward filter on is_owasp_staff. The implementation follows Django conventions and aligns well with the PR's modularization objective.

backend/apps/nest/management/commands/base_badge_command.py (1)

16-82: Well-structured base class with clear separation of concerns.

The BaseBadgeCommand effectively encapsulates the shared badge synchronization flow, allowing subclasses to provide only badge-specific metadata and eligibility logic via get_eligible_users. The idempotent assignment and cleanup logic (lines 52-76) correctly handles both adding and removing badges, and the exception handling (lines 79-82) ensures errors are logged and propagated. This design aligns perfectly with the PR's goal to reduce duplication and simplify adding new badge types.

@mrkeshav-05 mrkeshav-05 force-pushed the refactor/nest-badges branch from 3edb353 to 363c4a2 Compare January 1, 2026 21:54
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (1)

18-33: Test mocking prevents validation of actual filter logic.

The test completely patches the User model, so the actual filter User.objects.filter(is_owasp_staff=True) from the command never executes. This means the test doesn't validate whether is_owasp_staff is a valid database field that can be used in a filter.

Consider adding an integration test that exercises the actual database query without mocking User, to ensure the filter logic works correctly.

backend/tests/apps/nest/management/commands/base_badge_command_test.py (1)

31-51: Consider instance-level mocking instead of class-level.

Line 40 modifies MockCommand.get_eligible_users at the class level, which could affect other tests if they run in parallel or if the modification persists. Consider patching at the instance level instead:

🔎 Suggested refactor
     @patch("apps.nest.management.commands.base_badge_command.UserBadge")
     @patch("apps.nest.management.commands.base_badge_command.Badge")
     def test_syncs_badge(self, mock_badge, mock_user_badge):
         badge = MagicMock()
         badge.name = "Test Badge"
         mock_badge.objects.get_or_create.return_value = (badge, False)
 
         qs = MagicMock()
         qs.exclude.return_value = []
-        MockCommand.get_eligible_users = MagicMock(return_value=qs)
 
         mock_user_badge.objects.filter.return_value.exclude.return_value.count.return_value = 0
 
         out = StringIO()
         cmd = MockCommand()
+        cmd.get_eligible_users = MagicMock(return_value=qs)
         cmd.stdout = out
         cmd.handle()
 
         output = out.getvalue()
         assert "Test Badge" in output
         assert "synced successfully" in output
backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (1)

18-44: Test mocking prevents validation of actual query logic.

Similar to the staff badge tests, the extensive mocking of EntityMember, ContentType, Project, and User means the actual query chain in get_eligible_users() never executes. The test validates command execution flow but not the correctness of the database queries.

Consider adding an integration test that uses real models to verify:

  • ContentType.objects.get_for_model(Project) returns the correct content type
  • The EntityMember filter correctly identifies project leaders
  • The User query returns the expected distinct set of users
backend/apps/nest/management/commands/base_badge_command.py (1)

32-84: Consider wrapping the sync operation in a transaction.

While the current implementation works correctly, wrapping the badge add and remove operations in transaction.atomic() would ensure that the sync operation is fully atomic—either all changes succeed or all are rolled back. This prevents partial synchronization states if an error occurs mid-execution.

🔎 Proposed enhancement
+from django.db import transaction
+
 def handle(self, *args, **options):
     if not self.badge_name:
         msg = "badge_name must be set"
         raise ValueError(msg)
 
     self.stdout.write(f"Syncing {self.badge_name}...")
 
     try:
+        with transaction.atomic():
-        badge, created = Badge.objects.get_or_create(
+            badge, created = Badge.objects.get_or_create(
                 name=self.badge_name,
                 defaults={
                     "description": self.badge_description,

And indent the rest of the badge sync logic inside the transaction block.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3edb353 and 363c4a2.

📒 Files selected for processing (6)
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
🧬 Code graph analysis (5)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (4)
backend/apps/nest/management/commands/base_badge_command.py (2)
  • BaseBadgeCommand (16-84)
  • get_eligible_users (25-26)
backend/apps/owasp/models/entity_member.py (2)
  • EntityMember (10-112)
  • Role (13-16)
backend/apps/nest/management/commands/nest_update_staff_badge.py (2)
  • Command (9-18)
  • get_eligible_users (17-18)
backend/apps/nest/management/commands/nest_update_badges.py (1)
  • Command (20-153)
backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (1)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • Command (9-18)
backend/apps/nest/management/commands/nest_update_staff_badge.py (3)
backend/apps/nest/management/commands/base_badge_command.py (2)
  • BaseBadgeCommand (16-84)
  • get_eligible_users (25-26)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (2)
  • Command (12-28)
  • get_eligible_users (20-28)
backend/apps/nest/api/internal/nodes/user.py (1)
  • is_owasp_staff (14-16)
backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (2)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • Command (12-28)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • Command (9-18)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (1)
backend/apps/nest/management/commands/base_badge_command.py (3)
  • BaseBadgeCommand (16-84)
  • get_eligible_users (25-26)
  • handle (32-84)
🔇 Additional comments (8)
backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (1)

35-46: Good error handling coverage.

The test correctly verifies that exceptions during user filtering propagate up through the command execution flow.

backend/tests/apps/nest/management/commands/base_badge_command_test.py (1)

22-29: Good validation of required badge_name.

The test correctly verifies that the base command raises ValueError when badge_name is not set.

backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)

20-28: Clean implementation aligned with BaseBadgeCommand pattern.

The get_eligible_users method correctly identifies project leaders by:

  1. Filtering EntityMember for active, reviewed project leaders
  2. Extracting member IDs
  3. Returning distinct users matching those IDs

The logic matches the original implementation from nest_update_badges.py and properly leverages the base class synchronization workflow.

backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (1)

46-59: Good error propagation test.

The test correctly verifies that exceptions during the EntityMember query propagate through the command execution.

backend/apps/nest/management/commands/nest_update_staff_badge.py (1)

17-18: No action needed — is_owasp_staff is a database field.

The filter User.objects.filter(is_owasp_staff=True) correctly uses a database field. The is_owasp_staff field is defined as a BooleanField on the GitHub User model (backend/apps/github/models/user.py:47) and was added via migration. This will not raise a FieldError.

backend/apps/nest/management/commands/base_badge_command.py (3)

1-23: Clean implementation of the base class structure.

The imports, type annotations, and class design are well-structured. Using ABC with BaseCommand appropriately enforces that subclasses must implement get_eligible_users, and the class-level metadata attributes provide clear extension points.


24-30: Good adherence to Django conventions.

The abstract method signature correctly defines the contract for subclasses, and the _log helper appropriately uses self.stdout.write alongside logging, following Django management command best practices.

Based on learnings, using self.stdout.write for command output is the correct approach.


59-66: Counting logic correctly fixed.

The corrected implementation now properly tracks both newly created badges (line 61-62) and reactivated badges (lines 63-66), ensuring accurate reporting. This addresses the past review comment.

@mrkeshav-05 mrkeshav-05 force-pushed the refactor/nest-badges branch from 363c4a2 to 21e2374 Compare January 1, 2026 22:05
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (1)

18-44: Test covers the happy path with appropriate mocks.

The mock setup correctly patches dependencies at their usage locations. The test verifies that the command executes without errors and produces expected output.

One minor note: The qs.distinct.return_value = qs (line 38) creates a self-referential mock, which works but could be simplified to just return an empty iterable since distinct() result is only iterated in the base command.

🔎 Optional simplification
         qs = MagicMock()
         qs.exclude.return_value = []
-        qs.distinct.return_value = qs
+        qs.distinct.return_value = MagicMock(exclude=MagicMock(return_value=[]))
         mock_user.objects.filter.return_value = qs

Or even simpler, since the chain filter().distinct().exclude() is what's called:

mock_user.objects.filter.return_value.distinct.return_value.exclude.return_value = []
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 363c4a2 and 21e2374.

📒 Files selected for processing (6)
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py
  • backend/apps/nest/management/commands/nest_update_project_leader_badge.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/management/commands/nest_update_staff_badge.py
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
🧬 Code graph analysis (3)
backend/apps/nest/management/commands/nest_update_staff_badge.py (4)
backend/apps/nest/management/commands/base_badge_command.py (2)
  • BaseBadgeCommand (16-89)
  • get_eligible_users (25-26)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (2)
  • Command (12-28)
  • get_eligible_users (20-28)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (2)
  • get_eligible_users (18-19)
  • get_eligible_users (25-26)
backend/apps/nest/api/internal/nodes/user.py (1)
  • is_owasp_staff (14-16)
backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (2)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • Command (12-28)
backend/tests/apps/nest/management/commands/nest_update_staff_badge_test.py (3)
  • test_has_correct_metadata (14-16)
  • test_command_runs (21-33)
  • test_handles_errors (40-46)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (4)
backend/apps/nest/management/commands/base_badge_command.py (3)
  • BaseBadgeCommand (16-89)
  • get_eligible_users (25-26)
  • handle (32-89)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • get_eligible_users (20-28)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • get_eligible_users (17-18)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
🔇 Additional comments (11)
backend/apps/nest/management/commands/base_badge_command.py (5)

1-14: LGTM! Clean imports and module setup.

The imports are well-organized, and the logger is properly initialized using __name__. Good use of ABC and abstractmethod for establishing the base class contract.


16-30: Good base class design with proper Django conventions.

The abstract base class structure is clean. The _log method correctly uses self.stdout.write for command output alongside the logger, which aligns with Django management command best practices and improves testability. Based on learnings, this is the preferred approach over using print().


32-55: Badge creation and metadata sync logic is well-implemented.

The handle method properly validates badge_name, creates badges with defaults, and syncs metadata on existing badges. The fix for ensuring metadata updates on existing badges (lines 51-55) correctly addresses the prior review feedback.


57-73: Badge assignment logic correctly counts both new and reactivated badges.

The logic properly handles three cases:

  1. New user without any badge → creates UserBadge (counted)
  2. User with inactive badge → reactivates (counted)
  3. User with active badge → excluded by the query (not in loop)

This addresses the prior review feedback about counting newly created badges.


75-89: Consider documenting the bulk update behavior.

The badge removal logic uses update(is_active=False) which is efficient for bulk operations. The exception handling properly logs, outputs an error message, and re-raises to allow the calling infrastructure to handle failures appropriately.

One minor observation: the error handling catches all exceptions broadly. This is acceptable for a management command where you want visibility into any failure, but consider whether specific exception types might warrant different handling in the future.

backend/apps/nest/management/commands/nest_update_staff_badge.py (1)

1-18: Clean, minimal implementation following the base class contract.

The command correctly extends BaseBadgeCommand and implements only the badge-specific logic as intended by the PR objectives. The get_eligible_users method is simple and efficient.

Note: Both this badge and the Project Leader badge use fa-user-shield as the CSS class. If visual differentiation is desired, consider using distinct icons (e.g., fa-id-badge for staff). If the same icon is intentional, this is fine as-is.

backend/tests/apps/nest/management/commands/nest_update_project_leader_badge_test.py (2)

13-16: LGTM! Metadata test is clear and effective.

This test ensures the command class attributes are correctly defined, which helps catch accidental changes to badge configuration.


46-59: Error handling test correctly verifies exception propagation.

The test confirms that exceptions from the get_eligible_users flow are not swallowed and propagate correctly, which aligns with the base command's error handling design.

backend/tests/apps/nest/management/commands/base_badge_command_test.py (3)

12-19: LGTM! MockCommand provides a valid test double.

The MockCommand class correctly implements the abstract get_eligible_users method and provides realistic badge metadata for testing.


22-29: Good validation test for missing badge_name.

This test correctly verifies that the base command raises ValueError when badge_name is not set, ensuring subclasses must define this required attribute.


49-51: LGTM! Assertions verify expected output.

The assertions correctly check that the badge name and success message appear in the command output.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 1, 2026
@mrkeshav-05 mrkeshav-05 marked this pull request as ready for review January 1, 2026 22:09
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

This is cleaner structurally now. Let's optimize the DB operations a bit.


added_count = 0
for user in users_to_add:
user_badge, created = UserBadge.objects.get_or_create(user=user, badge=badge)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use bulk save operations here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we can use bulk_create / bulk_update, but there is a trade-off that model signals are not triggered in bulk operations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we don’t rely on signals here, so using bulk_create / bulk_update should work fine.

removed_count = users_to_remove.count()
if removed_count:
users_to_remove.update(is_active=False)
self._log(f"Removed '{self.badge_name}' badge from {removed_count} users")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please pluralize log messages where applicable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I will do that.

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)
backend/apps/nest/management/commands/base_badge_command.py (1)

83-83: Fix pluralization in log message.

Same pluralization issue: when removed_count is 1, the message will display "1 users" instead of "1 user".

🔎 Proposed fix
-                self._log(f"Removed '{self.badge_name}' badge from {removed_count} users")
+                user_word = "user" if removed_count == 1 else "users"
+                self._log(f"Removed '{self.badge_name}' badge from {removed_count} {user_word}")
🧹 Nitpick comments (3)
backend/apps/nest/management/commands/base_badge_command.py (3)

28-30: Consider adding a type hint for consistency.

The logging helper correctly uses self.stdout.write() per Django conventions. Consider adding a type hint for the message parameter for consistency with the rest of the codebase.

🔎 Suggested enhancement
-    def _log(self, message):
+    def _log(self, message: str) -> None:
         logger.info(message)
         self.stdout.write(message)

52-55: Consider optimizing to save only when fields change.

The current implementation always saves existing badges even if metadata hasn't changed. Since badge sync commands run infrequently, this is not critical, but you could optimize by checking if any field actually changed before calling save().

🔎 Optional optimization
             else:
-                badge.description = self.badge_description
-                badge.css_class = self.badge_css_class
-                badge.weight = self.badge_weight
-                badge.save(update_fields=["css_class", "description", "weight"])
+                updated = False
+                if badge.description != self.badge_description:
+                    badge.description = self.badge_description
+                    updated = True
+                if badge.css_class != self.badge_css_class:
+                    badge.css_class = self.badge_css_class
+                    updated = True
+                if badge.weight != self.badge_weight:
+                    badge.weight = self.badge_weight
+                    updated = True
+                if updated:
+                    badge.save(update_fields=["css_class", "description", "weight"])

64-71: Optimize with bulk operations for better performance.

The current loop creates or updates UserBadge entries one at a time, resulting in N database queries when processing many users. Since UserBadge extends BulkSaveModel, consider using bulk operations to improve performance.

🔎 Proposed bulk operations approach
             added_count = 0
+            to_create = []
+            to_reactivate = []
+            
             for user in users_to_add:
-                user_badge, created = UserBadge.objects.get_or_create(user=user, badge=badge)
-                if created:
-                    added_count += 1
-                elif not user_badge.is_active:
-                    user_badge.is_active = True
-                    user_badge.save(update_fields=["is_active"])
-                    added_count += 1
+                try:
+                    user_badge = UserBadge.objects.get(user=user, badge=badge)
+                    if not user_badge.is_active:
+                        user_badge.is_active = True
+                        to_reactivate.append(user_badge)
+                except UserBadge.DoesNotExist:
+                    to_create.append(UserBadge(user=user, badge=badge))
+            
+            if to_create:
+                UserBadge.objects.bulk_create(to_create)
+                added_count += len(to_create)
+            
+            if to_reactivate:
+                UserBadge.objects.bulk_update(to_reactivate, ["is_active"])
+                added_count += len(to_reactivate)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21e2374 and 53d4443.

📒 Files selected for processing (2)
  • backend/apps/nest/management/commands/base_badge_command.py
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/nest/management/commands/base_badge_command_test.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
🧬 Code graph analysis (1)
backend/apps/nest/management/commands/base_badge_command.py (4)
backend/apps/nest/models/user_badge.py (1)
  • UserBadge (10-50)
backend/tests/apps/nest/management/commands/base_badge_command_test.py (2)
  • get_eligible_users (18-19)
  • get_eligible_users (25-26)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • get_eligible_users (17-18)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • get_eligible_users (20-28)
🔇 Additional comments (3)
backend/apps/nest/management/commands/base_badge_command.py (3)

1-23: LGTM: Clean structure and proper abstractions.

The imports, class definition, and attribute declarations are well-structured. The use of ABC with class-level badge metadata attributes provides a clean template pattern for concrete badge commands.


24-26: LGTM: Clear abstract method contract.

The abstract method properly defines the interface that subclasses must implement, with appropriate type hints and documentation.


85-89: LGTM: Appropriate exception handling.

The exception handling correctly logs errors, provides user-friendly output via self.stdout.write(), and re-raises to ensure proper command failure. This follows Django management command best practices.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 2, 2026
@mrkeshav-05 mrkeshav-05 requested a review from arkid15r January 2, 2026 15:51
]

if new_badges:
UserBadge.objects.bulk_create(
Copy link
Collaborator

Choose a reason for hiding this comment

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

This class has BulkSaveModel functionality.

added_count = len(new_badges)

if added_count > 0:
user_word = "user" if added_count == 1 else "users"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not consistent with existing cases.


added_count = len(new_badges)

if added_count > 0:
Copy link
Collaborator

Choose a reason for hiding this comment

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

You didn't learn from previous review rounds.

Suggested change
if added_count > 0:
if added_count:

unique_fields=["user", "badge"],
)

added_count = len(new_badges)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can combine those ifs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26d10e9 and 5699b90.

📒 Files selected for processing (1)
  • backend/apps/nest/management/commands/base_badge_command.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/nest/management/commands/base_badge_command.py
🧬 Code graph analysis (1)
backend/apps/nest/management/commands/base_badge_command.py (3)
backend/apps/nest/models/user_badge.py (1)
  • UserBadge (10-50)
backend/apps/nest/management/commands/nest_update_project_leader_badge.py (1)
  • get_eligible_users (20-28)
backend/apps/nest/management/commands/nest_update_staff_badge.py (1)
  • get_eligible_users (17-18)
🔇 Additional comments (8)
backend/apps/nest/management/commands/base_badge_command.py (8)

1-14: LGTM: Clean imports and setup.

The imports and logger configuration follow Django best practices. The use of ABC for the abstract base class is appropriate for this modular design.


16-23: LGTM: Well-structured base class design.

The class-level attributes provide a clean interface for subclasses to declare badge metadata. The use of optional types (str | None) correctly indicates these must be overridden by concrete implementations.


24-26: LGTM: Abstract method enforces the template pattern.

The abstract get_eligible_users() method correctly forces subclasses to implement badge-specific eligibility logic, which is the core goal of this refactoring.


28-30: LGTM: Logging follows Django conventions.

The dual logging to both logger.info and self.stdout.write is appropriate for Django management commands, aligning with retrieved learnings about command output patterns.


32-37: LGTM: Defensive validation.

The validation ensures subclasses properly configure badge_name, providing a clear error message if not. The initial sync message provides good user feedback.


39-56: LGTM: Metadata syncing correctly implemented.

The code now properly updates existing badge metadata (lines 52-55) in addition to setting defaults on creation. This ensures the "sync" command truly synchronizes badge properties on every run. The use of update_fields is efficient. ✅ Addresses past review feedback.


78-88: LGTM: Badge removal logic is correct.

The query correctly identifies badges to deactivate, and the bulk update with is_active=False is efficient. Pluralization is handled properly for user feedback.


89-93: LGTM: Proper exception handling and success messaging.

The exception handling logs full details (with traceback via logger.exception) while providing styled output to users. Re-raising the exception allows calling code to handle failures appropriately.

@mrkeshav-05 mrkeshav-05 force-pushed the refactor/nest-badges branch from 5699b90 to a5dffd7 Compare January 2, 2026 20:54
@mrkeshav-05 mrkeshav-05 requested a review from arkid15r January 2, 2026 21:00
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 2, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor badges update job: modularize handlers, introduce base class, and add comprehensive tests

2 participants