Skip to content

Conversation

@aaditya8979
Copy link

@aaditya8979 aaditya8979 commented Dec 22, 2025

This pull request improves BLT’s trademark matching workflow and aligns the touched code with the project’s existing tooling and test expectations.
Summary
• Implements and refines reusable trademark matching logic in the services layer, so all consumers (APIs, commands, and tests) share a single, consistent implementation.
• Integrates this logic into the relevant API views and management commands that need to compute or display trademark matches.
• Updates and extends tests to cover successful matches, no‑match situations, and error‑handling paths (including mocked SMTP failures in weekly bug digests), while keeping the overall suite green.
• Applies automatic import sorting and formatting to all modified Python files so that style checks no longer fail on this code after a full pre‑commit run.
Key changes
• Centralized trademark matching behavior in dedicated service functions/classes, reducing duplication and making the matching rules easier to maintain.
• Ensured that trademark‑related endpoints and background jobs call the new services and correctly handle returned match data and edge cases.
• Clarified the behavior of weekly bug digest tests that intentionally raise SMTP exceptions via mocks, confirming that these logged errors are expected and fully caught inside the command.
• Cleaned up import order, unused imports, and formatting issues flagged by the project’s linters/formatters, and removed OS‑generated files from version control so they no longer interfere with checks.
Impact
• Functional behavior for end users remains compatible, but trademark processing is now more robust, test‑backed, and maintainable.
• Future contributors working in these areas can rely on consistent services, clearer tests, and stable automated checks when running the validation pipeline.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added trademark conflict detection and reporting capabilities with dedicated API endpoints for checking trademark risks.
    • Introduced admin interface for managing trademark matches with bulk action support (mark as reviewed, false positive, or acknowledged).
    • Automatic trademark verification now triggers when organizations and websites are created or updated.
  • Tests

    • Added comprehensive test coverage for trademark matching and related functionality.
  • Chores

    • Updated system configuration files.

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

@github-actions github-actions bot added migrations PR contains database migration files files-changed: 17 PR changes 17 files labels Dec 22, 2025
@github-actions
Copy link
Contributor

👋 Hi @aaditya8979!

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

  • The PR author
  • DonnieBLT
  • coderabbitai
  • copilot

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

@github-actions github-actions bot added the needs-peer-review PR needs peer review label Dec 22, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

Walkthrough

This PR introduces a comprehensive trademark matching system including a fuzzy-matching service, database models for storing trademark conflicts, automated signal-based checking when organizations and websites are created/updated, Django admin integration with bulk actions, API endpoints, and management commands for trademark verification and reporting.

Changes

Cohort / File(s) Summary
Configuration and Routing
.gitignore, blt/urls.py
Added .DS_Store ignore rule; registered three new URL patterns for trademark reporting and organization/website trademark checks (note: two routes share path api/trademark-report/ with different views).
Core Data Models
website/models.py, website/migrations/0264_alter_activity_id_alter_activitylog_id_and_more.py
Introduced TrademarkMatch model with fields for organization, similarity scoring, risk levels, timestamps, and status tracking; migration creates model, updates id fields across models to BigAutoField, and defines composite indexes.
Application Configuration
website/apps.py
Added default_auto_field setting and website.signals import in ready() method.
Admin Interface
website/admin.py
Registered TrademarkMatchAdmin with custom display methods (risk_level_badge, similarity_score_display, status_badge) and three bulk actions (mark_as_reviewed, mark_as_false_positive, mark_as_acknowledged).
Trademark Matching Services
website/services/__init__.py, website/services/trademark_matching.py, website/services/trademark_integration.py
Created services package with fuzzy-matching engine (TrademarkMatcher using Levenshtein distance), TrademarkCandidate dataclass, and Django-aware integration functions (get_matches_for_website, check_trademark_conflict, get_trademark_report).
Automatic Checking
website/signals.py
Implemented signal handlers for post_save on Organization and Website models to automatically trigger trademark checks, create TrademarkMatch records, and determine risk levels.
API Endpoints
website/views/trademark.py, website/views/api/trademark.py
Added trademark_report_view for basic GET endpoint and three API methods: trademark_report_api (public), organization_trademark_check (authenticated), website_trademark_check (authenticated).
Management Commands
website/management/commands/check_all_trademarks.py, website/management/commands/trademark_demo.py
Created check_all_trademarks command with org/website filtering flags and trademark_demo command to demonstrate matching with configurable company and threshold parameters.
Test Coverage
website/tests/test_main.py, website/tests/test_trademark_matching.py, website/tests/test_trademark_models.py
Added Selenium test skip conditional for missing Chrome, comprehensive unit tests for matching logic (normalization, Levenshtein distance, similarity scoring, filtering), and TrademarkMatch model tests.

Sequence Diagram

sequenceDiagram
    actor Admin
    participant Django as Django ORM
    participant Signal as post_save Signal
    participant Checker as _perform_trademark_check
    participant Matcher as TrademarkMatcher
    participant DB as Database
    
    Admin->>Django: Create/Update Organization
    Django->>Signal: Trigger post_save
    Signal->>Checker: call _perform_trademark_check(name, org)
    Checker->>Matcher: get_matches_for_website(name, threshold=85)
    Matcher->>Matcher: normalize, compute similarity scores
    Matcher-->>Checker: List[TrademarkCandidate]
    Checker->>DB: Delete previous TrademarkMatch records
    loop For each match
        Checker->>Checker: Determine risk_level (high/medium/low)
        Checker->>DB: Create TrademarkMatch record
    end
    DB-->>Checker: TrademarkMatch instances created
    Checker-->>Signal: Complete (with logging)
    Signal-->>Django: Return
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Duplicate URL patterns: blt/urls.py routes api/trademark-report/ to two different views; requires verification that routing behavior is intentional and correctly prioritized.
  • Missing imports: website/views/api/trademark.py references Website model without explicit import statement (line pulls organization-associated website matches but Website class not imported).
  • Signal design: Broad exception handling in website/signals.py with logging may mask issues; threshold and risk-level thresholds (90 for high, 80–89.999 for medium) should be validated against business requirements.
  • Migration complexity: Large migration file touches multiple models' id fields across the app; validate that all related models are covered and that BigAutoField migration is backward-compatible.
  • API endpoint logic consistency: organization_trademark_check and website_trademark_check in website/views/api/trademark.py have similar logic—consider extracting common serialization patterns.
  • Unused parameter: threshold argument in website/management/commands/trademark_demo.py is parsed but never used; clarify whether this is intentional or should be integrated.

Suggested labels

quality: high, feature: trademark-checking, database: schema-change, api: new-endpoints

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: complete trademark matching for #1926' directly aligns with the PR's main objective of completing trademark matching functionality. It is concise, specific, and clearly summarizes the primary change.
Docstring Coverage ✅ Passed Docstring coverage is 91.07% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

@github-actions
Copy link
Contributor

📊 Monthly Leaderboard

Hi @aaditya8979! Here's how you rank for December 2025:

Rank User PRs Reviews Comments Total
#13 @c0d3h01 3 3 0 42
#14 @aaditya8979 3 2 0 38
#15 @nitinawari 1 6 0 34

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

@github-actions github-actions bot added the pre-commit: passed Pre-commit checks passed label Dec 22, 2025
Comment on lines +75 to +77
website = get_object_or_404(Website, id=website_id)

matches = TrademarkMatch.objects.filter(website=website).order_by("-similarity_score")
Copy link

Choose a reason for hiding this comment

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

Bug: The website_trademark_check view will crash due to using the Website model without importing it and referencing a non-existent website field on the TrademarkMatch model.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

The website_trademark_check API view will crash when accessed. The function attempts to retrieve a Website object on line 75 using get_object_or_404(Website, id=website_id), but the Website model is never imported, which will raise a NameError. Even if the import were fixed, the code would still fail on line 77. The query TrademarkMatch.objects.filter(website=website) references the website field on the TrademarkMatch model, but this field has been explicitly commented out in the model's definition, which would cause a FieldError.

💡 Suggested Fix

Either remove the website_trademark_check view and its corresponding URL pattern if it is unused, or complete the implementation. This would involve importing the Website model and uncommenting or re-implementing the website foreign key field on the TrademarkMatch model.

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

Location: website/views/api/trademark.py#L75-L77

Potential issue: The `website_trademark_check` API view will crash when accessed. The
function attempts to retrieve a `Website` object on line 75 using
`get_object_or_404(Website, id=website_id)`, but the `Website` model is never imported,
which will raise a `NameError`. Even if the import were fixed, the code would still fail
on line 77. The query `TrademarkMatch.objects.filter(website=website)` references the
`website` field on the `TrademarkMatch` model, but this field has been explicitly
commented out in the model's definition, which would cause a `FieldError`.

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

@github-actions github-actions bot added the tests: passed Django tests passed label Dec 22, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (5)
website/models.py (1)

52-53: Remove commented-out code.

Dead code comments like this add noise and reduce maintainability. If the website field was intentionally removed, this comment should be deleted. If it's planned for future work, consider tracking it in an issue instead.

🔎 Suggested fix
-    # Remove the website field entirely for now
-    # website = models.ForeignKey(...)
-
website/signals.py (1)

81-85: Refactor: Move logger initialization to module level.

The logger is imported and initialized inside the exception handler. While this works, it's inefficient if exceptions occur frequently and goes against Python conventions.

🔎 Proposed refactor

Move the import and logger initialization to the top of the file:

 """
 Django signals for automatic trademark checking when organizations/websites are created/updated.
 """
+import logging
 
 from django.db.models.signals import post_save
 from django.dispatch import receiver
 from django.utils import timezone
 
 from website.models import Organization, TrademarkMatch
 from website.services.trademark_integration import get_matches_for_website
 
+logger = logging.getLogger(__name__)

Then in the exception handler:

     except Exception as e:
-        import logging
-
-        logger = logging.getLogger(__name__)
         logger.error(f"Trademark check failed for {name}: {str(e)}")
website/services/trademark_integration.py (1)

11-33: Refactor: Move SAMPLE_TRADEMARKS import to module level.

Line 31 imports SAMPLE_TRADEMARKS inside the function. While this works, it's unconventional and makes the dependency less obvious. Module-level imports are preferred in Python for clarity and to follow PEP 8 guidelines.

The TODO comment on line 29 is noted and reasonable for the current implementation stage.

🔎 Proposed refactor

Move the import to the top of the file:

 """
 Integration module for trademark matching with BLT models.
 This provides Django-aware wrapper functions for the core matching service.
 """
 
 from typing import List
 
-from website.services.trademark_matching import TrademarkCandidate, TrademarkMatcher
+from website.services.trademark_matching import (
+    SAMPLE_TRADEMARKS,
+    TrademarkCandidate,
+    TrademarkMatcher,
+)

Then simplify the function:

 def get_matches_for_website(
     website_name: str,
     threshold: float = 85.0,
 ) -> List[TrademarkCandidate]:
     """
     Get trademark matches for a website/company.
 
     This is the primary API for integration with BLT models.
 
     Args:
         website_name: Name of the website or company to check.
         threshold: Match threshold (0-100). Default 85.0.
 
     Returns:
         List of TrademarkCandidate objects representing potential matches.
     """
     matcher = TrademarkMatcher(threshold=threshold)
 
     # TODO: In production, fetch trademarks from USPTO API or database
-    # For now, using sample data
-    from website.services.trademark_matching import SAMPLE_TRADEMARKS
 
     return matcher.match(website_name, SAMPLE_TRADEMARKS)
website/services/trademark_matching.py (2)

31-42: Consider: Aggressive normalization may over-match.

The normalize method removes all non-alphanumeric characters, which means "Bug-Hunt" and "BugHunt" become identical. While this enables fuzzy matching, it might create false positives for trademarks that specifically include punctuation or spacing as distinguishing features.

This is likely intentional for catching similar names, but be aware that:

  • "O'Reilly" and "OReilly" would match 100%
  • "Inc." and "INC" would be identical
  • Spacing differences are completely ignored

If this behavior causes issues in production, consider a less aggressive normalization that preserves some punctuation or word boundaries.


95-126: LGTM with scalability consideration.

The matching logic is well-implemented with proper edge case handling (empty strings, normalization) and clear result processing (sorting, limiting, rounding).

For future reference: The current O(n × m) complexity (where n = number of trademarks, m = average string length) works well for the sample dataset but may need optimization if the trademark database grows significantly. Consider indexing strategies or approximate nearest neighbor algorithms if performance becomes an issue with thousands of trademarks.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1c3b84d and 4770589.

📒 Files selected for processing (17)
  • .gitignore
  • blt/urls.py
  • website/admin.py
  • website/apps.py
  • website/management/commands/check_all_trademarks.py
  • website/management/commands/trademark_demo.py
  • website/migrations/0264_alter_activity_id_alter_activitylog_id_and_more.py
  • website/models.py
  • website/services/__init__.py
  • website/services/trademark_integration.py
  • website/services/trademark_matching.py
  • website/signals.py
  • website/tests/test_main.py
  • website/tests/test_trademark_matching.py
  • website/tests/test_trademark_models.py
  • website/views/api/trademark.py
  • website/views/trademark.py
🧰 Additional context used
🧬 Code graph analysis (9)
website/views/trademark.py (1)
website/services/trademark_integration.py (1)
  • get_trademark_report (54-86)
website/signals.py (3)
website/models.py (2)
  • Organization (251-359)
  • TrademarkMatch (38-105)
website/services/trademark_integration.py (1)
  • get_matches_for_website (11-33)
website/services/trademark_matching.py (1)
  • match (95-126)
website/admin.py (1)
website/models.py (1)
  • TrademarkMatch (38-105)
website/services/trademark_integration.py (1)
website/services/trademark_matching.py (3)
  • TrademarkCandidate (11-15)
  • TrademarkMatcher (18-126)
  • match (95-126)
website/management/commands/trademark_demo.py (1)
website/services/trademark_matching.py (2)
  • get_trademark_matches (144-155)
  • match (95-126)
website/tests/test_trademark_matching.py (1)
website/services/trademark_matching.py (7)
  • TrademarkCandidate (11-15)
  • TrademarkMatcher (18-126)
  • get_trademark_matches (144-155)
  • normalize (31-42)
  • _levenshtein_distance (44-73)
  • _similarity_score (75-93)
  • match (95-126)
website/tests/test_trademark_models.py (1)
website/models.py (3)
  • Organization (251-359)
  • TrademarkMatch (38-105)
  • is_high_risk (100-101)
website/management/commands/check_all_trademarks.py (2)
website/models.py (1)
  • Organization (251-359)
website/signals.py (1)
  • _perform_trademark_check (55-85)
blt/urls.py (2)
website/views/api/trademark.py (2)
  • organization_trademark_check (35-64)
  • trademark_report_api (15-30)
website/views/trademark.py (1)
  • trademark_report_view (8-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (19)
.gitignore (1)

28-28: Good housekeeping: macOS system files excluded.

Adding .DS_Store to .gitignore prevents macOS system metadata files from being accidentally committed, which is a standard practice. No concerns.

website/services/__init__.py (1)

1-4: LGTM!

Clean package initialization with a clear docstring documenting the purpose. This properly establishes the services package for reusable business logic modules.

website/models.py (1)

38-106: Well-structured trademark match model.

Good design choices:

  • Appropriate field types and constraints
  • Composite indexes align with expected query patterns (filtering by organization/score, status/date)
  • The __str__ fallback logic handles the nullable organization FK gracefully
  • Properties for risk level thresholds provide clean abstractions

The model provides a solid foundation for tracking trademark conflicts.

website/apps.py (1)

7-12: LGTM!

Good additions:

  • default_auto_field = "django.db.models.BigAutoField" aligns with Django 3.2+ best practices and ensures consistent primary key types across models
  • Importing website.signals in ready() follows the established pattern in this file and ensures trademark-related signal handlers are registered at app startup
website/views/trademark.py (1)

7-22: Clean implementation of the trademark report endpoint.

Good practices observed:

  • Input validation with whitespace stripping
  • Appropriate HTTP 400 response for missing required parameter
  • Clear docstring with endpoint path

One consideration: This endpoint is publicly accessible without authentication. If trademark analysis is intended to be a protected feature, you may want to add @login_required or similar access control.

Verify if this endpoint should require authentication based on your security requirements.

website/tests/test_trademark_models.py (1)

1-86: LGTM! Well-structured model tests.

The tests provide good coverage of the TrademarkMatch model, including creation defaults, the is_high_risk property, string representation, and ordering behavior. The test cases align with the model definition and verify expected defaults correctly.

website/admin.py (1)

182-245: LGTM! Well-implemented admin display methods and actions.

The badge display methods use format_html() correctly for XSS safety, color coding is appropriate for risk levels and statuses, and the batch actions properly update querysets with informative user feedback.

website/tests/test_trademark_matching.py (1)

1-147: LGTM! Comprehensive test coverage for trademark matching.

Excellent test suite covering:

  • Normalization (special chars, lowercasing, empty strings)
  • Levenshtein distance algorithm edge cases
  • Similarity scoring
  • Matching behavior with various thresholds
  • Result limits and sorting
  • Edge cases (empty inputs, empty trademark lists)

The tests are well-organized and provide good confidence in the matching logic.

website/migrations/0264_alter_activity_id_alter_activitylog_id_and_more.py (2)

468-537: LGTM! TrademarkMatch model migration is correct.

The CreateModel operation properly defines all fields matching the model definition, with appropriate indexes on (organization, -similarity_score) and (status, -checked_at) for efficient querying by organization and status filtering.


12-467: BigAutoField migration is appropriate.

The bulk AlterField operations converting id fields to BigAutoField follow Django 3.2+ best practices and future-proof the database for high-volume tables.

website/signals.py (1)

13-21: LGTM!

The organization trademark checking signal handler is properly registered and correctly validates the organization name before performing the check.

website/views/api/trademark.py (2)

14-30: LGTM!

The API endpoint correctly validates the required name parameter and returns appropriate error responses. The implementation is clean and follows REST best practices.


33-64: LGTM!

The organization trademark endpoint is properly secured with authentication, handles missing organizations gracefully with 404 responses, and provides a well-structured JSON response with useful metrics.

website/services/trademark_integration.py (2)

36-51: LGTM!

The conflict checking function is straightforward and correctly returns a boolean indicating whether any matches exceed the threshold. The default threshold of 90.0 is appropriately conservative for high-confidence conflicts.


54-86: LGTM!

The report generation function provides a well-structured response with useful metrics (highest score, total matches) and actionable recommendations. The data structure is clean and suitable for both API responses and internal use.

website/services/trademark_matching.py (4)

10-15: LGTM!

The TrademarkCandidate dataclass is a clean, type-annotated data structure that clearly represents a trademark match result.


44-73: LGTM!

The Levenshtein distance implementation is correct and uses the classic dynamic programming approach with space optimization (storing only the previous row). The recursion on line 57 to swap strings when s1 is shorter is a nice optimization for the algorithm's efficiency.


75-93: LGTM!

The similarity score calculation correctly converts Levenshtein distance to a percentage. The edge case handling for empty strings (returning 100.0) is appropriate since two empty strings are indeed identical.


129-155: LGTM!

The sample trademark dataset provides reasonable test data, and the convenience function get_trademark_matches offers a simple interface for common use cases with sensible defaults.

Comment on lines +1256 to +1257
path("api/trademark-report/", trademark_report_view, name="trademark_report"),
path("api/trademark-report/", trademark_report_api, name="trademark_report_api"),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Duplicate URL paths cause unreachable route.

Both lines define path("api/trademark-report/", ...) with different views. Django's URL resolver uses first-match-wins, so trademark_report_api on line 1257 will never be reached.

Looking at the view implementations in website/views/trademark.py and website/views/api/trademark.py, both trademark_report_view and trademark_report_api appear functionally identical (both return JSON trademark reports for a name query parameter).

Either:

  1. Remove one of the duplicate routes if they serve the same purpose
  2. Use distinct paths if both endpoints are needed (e.g., api/v1/trademark-report/ vs api/v2/trademark-report/)
🔎 Suggested fix: Remove the duplicate route
     path("api/trademark-report/", trademark_report_view, name="trademark_report"),
-    path("api/trademark-report/", trademark_report_api, name="trademark_report_api"),
     path(
         "api/organization/<int:org_id>/trademarks/", organization_trademark_check, name="organization_trademark_check"
     ),
📝 Committable suggestion

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

Suggested change
path("api/trademark-report/", trademark_report_view, name="trademark_report"),
path("api/trademark-report/", trademark_report_api, name="trademark_report_api"),
path("api/trademark-report/", trademark_report_view, name="trademark_report"),
🤖 Prompt for AI Agents
In blt/urls.py around lines 1256-1257 there are duplicate routes both using
path("api/trademark-report/", ...) which makes the second route unreachable;
remove the duplicate by either deleting the redundant entry (keep the correct
view) or, if both handlers are required, change one route to a distinct path
(e.g., "api/v2/trademark-report/" or "api/trademark-report/api/") and update any
callers/tests accordingly so each view has a unique URL.

Comment on lines +126 to +131
search_fields = [
"checked_name",
"matched_trademark_name",
"organization__name",
"website__name",
]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

search_fields references non-existent website field.

The TrademarkMatch model does not have a website field (it's explicitly commented out in the model: "Remove the website field entirely for now"). This will cause a FieldError when searching in the admin.

🔎 Proposed fix
     search_fields = [
         "checked_name",
         "matched_trademark_name",
         "organization__name",
-        "website__name",
     ]
📝 Committable suggestion

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

Suggested change
search_fields = [
"checked_name",
"matched_trademark_name",
"organization__name",
"website__name",
]
search_fields = [
"checked_name",
"matched_trademark_name",
"organization__name",
]
🤖 Prompt for AI Agents
In website/admin.py around lines 126 to 131, the admin search_fields includes
"website__name" which no longer exists on the TrademarkMatch model and will
raise a FieldError; remove "website__name" from the search_fields list (or
replace it with the correct existing relation/field if there is an alternative),
ensure the list only references valid model fields or related lookups, and run
the Django dev server/admin to verify the FieldError is resolved.

Comment on lines +155 to +160
(
"Related Objects",
{
"fields": ("organization", "website"),
},
),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fieldset references non-existent website field.

The TrademarkMatch model has no website field, so including it in the fieldset will cause an admin error when viewing/editing records.

🔎 Proposed fix
         (
             "Related Objects",
             {
-                "fields": ("organization", "website"),
+                "fields": ("organization",),
             },
         ),
🤖 Prompt for AI Agents
In website/admin.py around lines 155 to 160, the fieldset includes a
non-existent "website" field for the TrademarkMatch model which will raise an
admin error; remove "website" from the ("organization", "website") tuple or
replace it with the actual field name defined on TrademarkMatch (e.g., "site" or
"url") if intended—update the fieldset to only reference valid model fields and
run the Django admin to verify no errors.

Comment on lines +53 to +63
websites = Website.objects.filter(name__isnull=False).exclude(name="")
for website in websites:
try:
org = None
if hasattr(website, "organization"):
org = website.organization
_perform_trademark_check(name=website.name, website=website, organization=org)
website_count += 1
self.stdout.write(f" ✓ {website.name}")
except Exception as e:
self.stdout.write(self.style.ERROR(f" ✗ {website.name}: {str(e)}"))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing import and invalid function parameter cause runtime errors.

Two issues will crash the command when processing websites:

  1. Line 53: Website is referenced but never imported - this will raise a NameError.

  2. Line 59: _perform_trademark_check() is called with website=website, but according to website/signals.py (lines 54-84), the function signature only accepts name and organization parameters.

🔎 Proposed fix
 from django.core.management.base import BaseCommand

-from website.models import Organization
+from website.models import Organization, Website
 from website.signals import _perform_trademark_check

And fix the function call (assuming website parameter support is added to _perform_trademark_check, or remove the website parameter):

-                    _perform_trademark_check(name=website.name, website=website, organization=org)
+                    _perform_trademark_check(name=website.name, organization=org)

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

🤖 Prompt for AI Agents
In website/management/commands/check_all_trademarks.py around lines 53 to 63,
the command refers to Website but never imports it and calls
_perform_trademark_check with an unexpected website= parameter; fix by adding
the proper import (from website.models import Website or the correct module
path) at the top of the file, and update the call to match the function
signature used in website/signals.py (call
_perform_trademark_check(name=website.name, organization=org) — remove the
website= argument — unless you deliberately change the trademark function to
accept a website parameter, in which case update that function signature and its
callers consistently).

Comment on lines +24 to +29
parser.add_argument(
"--threshold",
type=float,
default=85.0,
help="Match threshold 0-100 (default: 85.0)",
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unused --threshold argument.

The --threshold argument is defined but never used. get_trademark_matches() internally creates a TrademarkMatcher(threshold=85.0) with a hardcoded threshold.

Either remove the unused argument or pass it to a custom matcher:

🔎 Option 1: Remove unused argument
     def add_arguments(self, parser):
         """Add command arguments."""
         parser.add_argument(
             "--company",
             type=str,
             default="BugHeist",
             help="Company name to search for (default: BugHeist)",
         )
-        parser.add_argument(
-            "--threshold",
-            type=float,
-            default=85.0,
-            help="Match threshold 0-100 (default: 85.0)",
-        )
🔎 Option 2: Use the threshold argument
+from website.services.trademark_matching import TrademarkMatcher, SAMPLE_TRADEMARKS
-from website.services.trademark_matching import get_trademark_matches

     def handle(self, *args, **options):
         """Execute the command."""
         company = options["company"]
+        threshold = options["threshold"]

         self.stdout.write(self.style.SUCCESS(f"\n🔍 Searching for trademark matches for: '{company}'"))
         self.stdout.write("=" * 60)

-        matches = get_trademark_matches(company)
+        matcher = TrademarkMatcher(threshold=threshold)
+        matches = matcher.match(company, SAMPLE_TRADEMARKS)
📝 Committable suggestion

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

Suggested change
parser.add_argument(
"--threshold",
type=float,
default=85.0,
help="Match threshold 0-100 (default: 85.0)",
)
parser.add_argument(
"--company",
type=str,
default="BugHeist",
help="Company name to search for (default: BugHeist)",
)
🤖 Prompt for AI Agents
In website/management/commands/trademark_demo.py around lines 24-29, the
--threshold argparse option is defined but never used; either remove the
argument or thread its value into the matcher creation. Preferred fix: add a
parameter to get_trademark_matches (or accept threshold in the call site) and
pass args.threshold through so TrademarkMatcher is constructed with
threshold=args.threshold instead of the hardcoded 85.0; alternatively, if
threshold is not needed, delete the parser.add_argument block and any references
to args.threshold.

Comment on lines +24 to +52
def check_website_trademarks(sender, instance, created, **kwargs):
"""
When a website is created or updated, automatically check for trademark conflicts.
"""
if not instance.name:
return

# Skip if already checked very recently
recent_check = (
TrademarkMatch.objects.filter(
website=instance,
)
.filter(checked_at__gte=timezone.now() - timezone.timedelta(days=1))
.exists()
)

if recent_check and not created:
return # Skip if updated but recently checked

# Run trademark check
org = None
if hasattr(instance, "organization"):
org = instance.organization

_perform_trademark_check(
name=instance.name,
organization=org,
website=instance,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing signal registration and parameter mismatches.

This function has multiple critical issues:

  1. Missing @receiver decorator: Line 24 is missing the decorator to register this signal handler. Without it, the function will never be called automatically when a Website is saved.

  2. Invalid field reference: Line 34 filters TrademarkMatch by website=instance, but according to the model definition (website/models.py lines 37-104), the website field was removed from TrademarkMatch. This will raise a FieldError at runtime.

  3. Parameter mismatch: Line 51 passes website=instance to _perform_trademark_check, but the function signature on line 55 only accepts name and organization parameters. This will raise a TypeError.

🔎 Proposed fixes

Add the missing decorator and remove references to the non-existent website field:

+@receiver(post_save, sender=Website)
 def check_website_trademarks(sender, instance, created, **kwargs):
     """
     When a website is created or updated, automatically check for trademark conflicts.
     """
     if not instance.name:
         return
 
-    # Skip if already checked very recently
-    recent_check = (
-        TrademarkMatch.objects.filter(
-            website=instance,
-        )
-        .filter(checked_at__gte=timezone.now() - timezone.timedelta(days=1))
-        .exists()
-    )
-
-    if recent_check and not created:
-        return  # Skip if updated but recently checked
-
     # Run trademark check
     org = None
     if hasattr(instance, "organization"):
         org = instance.organization
 
     _perform_trademark_check(
         name=instance.name,
         organization=org,
-        website=instance,
     )

Note: The Website model import will also need to be added to the imports at the top of the file.

📝 Committable suggestion

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

Suggested change
def check_website_trademarks(sender, instance, created, **kwargs):
"""
When a website is created or updated, automatically check for trademark conflicts.
"""
if not instance.name:
return
# Skip if already checked very recently
recent_check = (
TrademarkMatch.objects.filter(
website=instance,
)
.filter(checked_at__gte=timezone.now() - timezone.timedelta(days=1))
.exists()
)
if recent_check and not created:
return # Skip if updated but recently checked
# Run trademark check
org = None
if hasattr(instance, "organization"):
org = instance.organization
_perform_trademark_check(
name=instance.name,
organization=org,
website=instance,
)
@receiver(post_save, sender=Website)
def check_website_trademarks(sender, instance, created, **kwargs):
"""
When a website is created or updated, automatically check for trademark conflicts.
"""
if not instance.name:
return
# Run trademark check
org = None
if hasattr(instance, "organization"):
org = instance.organization
_perform_trademark_check(
name=instance.name,
organization=org,
)
🤖 Prompt for AI Agents
In website/signals.py around lines 24–52, register the handler and align its
behavior with the current TrademarkMatch model and _perform_trademark_check
signature: add the @receiver(post_save, sender=Website) decorator and import
receiver, post_save and Website at the top; replace the TrademarkMatch filter
that references the removed website field with a filter using existing fields
(e.g., filter(name=instance.name, organization=org) or whatever current unique
fields are on TrademarkMatch) to check recent_check; and call
_perform_trademark_check with only the accepted parameters (name=instance.name,
organization=org) — remove website=instance from that call. Ensure org is
derived safely from instance.organization before use.

Comment on lines +55 to +64
def _perform_trademark_check(name, organization=None):
"""
Core function to perform trademark matching and store results.
"""
try:
matches = get_matches_for_website(name, threshold=85.0)

if organization:
TrademarkMatch.objects.filter(organization=organization).delete()

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Unconditional deletion loses historical data and manual reviews.

Line 63 deletes all previous TrademarkMatch records for the organization every time a check is performed. This approach has significant downsides:

  1. Loses manual review data: If a maintainer has reviewed matches and set is_reviewed=True or updated the status field, this information is permanently lost.
  2. No audit trail: Historical trademark checks and their evolution over time are erased.
  3. Race conditions: If multiple checks run concurrently (unlikely but possible with signals), they could interfere with each other.

Consider instead:

  • Update existing matches if they still appear in the new results
  • Mark old matches as stale rather than deleting them
  • Only delete matches with status='pending' and is_reviewed=False
  • Or maintain a complete history and add a "latest check" timestamp to Organization
🔎 Suggested approach for preserving reviewed matches
     try:
         matches = get_matches_for_website(name, threshold=85.0)
 
         if organization:
-            TrademarkMatch.objects.filter(organization=organization).delete()
+            # Only delete unreviewed pending matches
+            TrademarkMatch.objects.filter(
+                organization=organization,
+                is_reviewed=False,
+                status='pending'
+            ).delete()
 
         for match in matches:
🤖 Prompt for AI Agents
In website/signals.py around lines 55–64, the code unconditionally deletes all
TrademarkMatch records for the organization which loses reviewed data and
history; instead, fetch existing matches for the organization, compute which
incoming matches correspond to existing records (by trademark identifier/name),
update those existing records with refreshed scores/status/timestamps, mark
existing matches that no longer appear as stale (e.g., set status='stale' and
update a last_seen/updated_at), and only delete records that are both
status='pending' and is_reviewed=False if you must remove anything;
alternatively add/maintain a latest_check timestamp on Organization and append
new matches rather than blasting historical rows. Ensure operations are done in
a transaction to avoid race conditions.

Comment on lines +38 to +42
CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
NO_CHROME = not os.path.exists(CHROME_PATH)


@skipIf(NO_CHROME, "Chrome not installed locally; skipping Selenium UI tests")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded macOS path will skip Selenium tests on Linux/Windows even when Chrome is available.

The path /Applications/Google Chrome.app/Contents/MacOS/Google Chrome is macOS-specific. This means Selenium tests will be unconditionally skipped on Linux CI environments (common for GitHub Actions) even when Chrome/Chromium is installed.

Consider using shutil.which to detect Chrome cross-platform, or use the chromedriver-autoinstaller's capabilities to detect Chrome availability:

🔎 Proposed cross-platform detection
+import shutil
+
-CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
-NO_CHROME = not os.path.exists(CHROME_PATH)
+def _chrome_available():
+    """Check if Chrome/Chromium is available on any platform."""
+    # Check common executable names
+    for name in ("google-chrome", "chromium", "chromium-browser", "chrome"):
+        if shutil.which(name):
+            return True
+    # macOS app bundle
+    macos_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+    if os.path.exists(macos_path):
+        return True
+    return False
+
+NO_CHROME = not _chrome_available()
📝 Committable suggestion

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

Suggested change
CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
NO_CHROME = not os.path.exists(CHROME_PATH)
@skipIf(NO_CHROME, "Chrome not installed locally; skipping Selenium UI tests")
def _chrome_available():
"""Check if Chrome/Chromium is available on any platform."""
# Check common executable names
for name in ("google-chrome", "chromium", "chromium-browser", "chrome"):
if shutil.which(name):
return True
# macOS app bundle
macos_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
if os.path.exists(macos_path):
return True
return False
NO_CHROME = not _chrome_available()
@skipIf(NO_CHROME, "Chrome not installed locally; skipping Selenium UI tests")
🤖 Prompt for AI Agents
In website/tests/test_main.py around lines 38-42, the code uses a hardcoded
macOS Chrome path which causes tests to be skipped on Linux/Windows even when
Chrome/Chromium is present; replace the macOS-only check with a cross-platform
detection that sets NO_CHROME only when no browser executable can be found—use
shutil.which to look for common names ("google-chrome", "chrome", "chromium",
"chromium-browser") or leverage chromedriver-autoinstaller's detection API;
update the boolean accordingly so @skipIf only triggers when none of these are
available.

Comment on lines +67 to +98
@require_GET
@login_required
def website_trademark_check(request, website_id):
"""
JSON API to get trademark matches for a specific website.
GET /api/website/{website_id}/trademarks/
"""
website = get_object_or_404(Website, id=website_id)

matches = TrademarkMatch.objects.filter(website=website).order_by("-similarity_score")

data = {
"website_id": website.id,
"website_name": website.name,
"total_matches": matches.count(),
"high_risk_count": matches.filter(risk_level="high").count(),
"matches": [
{
"id": m.id,
"matched_trademark": m.matched_trademark_name,
"similarity_score": m.similarity_score,
"risk_level": m.risk_level,
"status": m.status,
"is_reviewed": m.is_reviewed,
"checked_at": m.checked_at.isoformat(),
}
for m in matches
],
}

return JsonResponse(data)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing import and invalid field reference.

This endpoint has two critical issues that will cause runtime errors:

  1. Missing import: Line 75 uses Website but it's not imported. This will raise a NameError. The import statement on line 10 only includes Organization and TrademarkMatch.

  2. Invalid field reference: Line 77 filters TrademarkMatch.objects.filter(website=website), but according to the model definition (website/models.py lines 37-104), the website field was removed from the TrademarkMatch model. This will raise a FieldError.

🔎 Proposed fixes

Add the missing import and update the logic if the Website relationship is needed:

 from website.models import Organization, TrademarkMatch
+from website.models import Website
 from website.services.trademark_integration import get_trademark_report

For the field reference issue, you'll need to determine the correct relationship. If websites should be checked via their associated organization:

 def website_trademark_check(request, website_id):
     """
     JSON API to get trademark matches for a specific website.
 
     GET /api/website/{website_id}/trademarks/
     """
     website = get_object_or_404(Website, id=website_id)
 
-    matches = TrademarkMatch.objects.filter(website=website).order_by("-similarity_score")
+    # If website has an organization relationship:
+    if website.organization:
+        matches = TrademarkMatch.objects.filter(organization=website.organization).order_by("-similarity_score")
+    else:
+        matches = TrademarkMatch.objects.none()

Alternatively, if the website field should be added back to the TrademarkMatch model, that's a model-level decision that needs to be addressed there first.

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

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Dec 22, 2025
Copy link
Contributor

@Jayant2908 Jayant2908 left a comment

Choose a reason for hiding this comment

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

Do fix the coderabbitai issues and ask for re-review, I'll look it up!

@mdkaifansari04
Copy link
Contributor

@aaditya8979 Please resolve all the comments from code-rabbit, also it would be quite better to create an issue and get that verified from Donnie.

@Nachiket-Roy
Copy link
Contributor

@mdkaifansari04 #1926

@github-actions github-actions bot added last-active: 0d PR last updated 0 days ago and removed last-active: 0d PR last updated 0 days ago labels Dec 23, 2025
@github-actions
Copy link
Contributor

💬 Reminder: Unresolved Conversations

Hi @aaditya8979!

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

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

Thank you! 🙏

@github-actions github-actions bot added last-active: 1d PR last updated 1 day ago last-active: 2d PR last updated 2 days ago last-active: 0d PR last updated 0 days ago and removed last-active: 0d PR last updated 0 days ago last-active: 1d PR last updated 1 day ago last-active: 2d PR last updated 2 days ago labels Dec 27, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Jan 2, 2026

💬 Reminder: Unresolved Conversations

Hi @aaditya8979!

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

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

Thank you! 🙏

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

Labels

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

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

4 participants