-
-
Notifications
You must be signed in to change notification settings - Fork 204
Fix #2019: Link OWASP entities to related Slack channels #2049
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Summary by CodeRabbit
WalkthroughAdds an EntityChannel model (generic relations), migration, admin registration with a "mark as reviewed" action, a management command owasp_match_channels to create EntityChannel records for Chapters/Committees/Projects, and re-exports the model/admin via package init files. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Out-of-scope changesNo out-of-scope functional changes detected. All modifications relate to adding the EntityChannel model, its admin, migration, management command, and package exports. Possibly related PRs
Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (6)
backend/apps/slack/admin/entity_channel.py (1)
10-14
: Optimize changelist queriesSelecting related conversation and content_type avoids extra queries in the list view.
class EntityChannelAdmin(admin.ModelAdmin): list_display = ("entity", "conversation", "is_main_channel", "is_reviewed", "kind") list_filter = ("is_main_channel", "is_reviewed", "kind", "content_type") search_fields = ("object_id", "conversation__name") actions = [mark_as_reviewed] + list_select_related = ("conversation", "content_type")
backend/apps/slack/management/commands/owasp_match_channels.py (3)
9-9
: Wrap long help string and use double quotesFixes E501 and aligns with Ruff’s quote style preference.
- help = 'Populate EntityChannel records for Chapters, Committees, and Projects based on Slack data.' + help = ( + "Populate EntityChannel records for Chapters, Committees, and Projects " + "based on Slack data." + )
11-36
: Optional: add a workspace filter argument and ensure atomicityThis limits scope and provides safer writes; also aligns with large-run ergonomics.
class Command(BaseCommand): @@ - def handle(self, *args, **options): + def add_arguments(self, parser): + parser.add_argument( + "--workspace-id", + type=int, + help="Limit matching to a specific Slack Workspace ID.", + ) + + def handle(self, *args, **options): created = 0 - # Example: match by name, can be improved + # Example: match by name; normalization applied below + from django.db import transaction + with transaction.atomic(): for model in [ (Chapter, 'owasp', 'chapter'), (Committee, 'owasp', 'committee'), (Project, 'owasp', 'project'), ]: - content_type = ContentType.objects.get(app_label=app_label, model=model_name) + content_type = ContentType.objects.get_for_model(model) for entity in model.objects.all(): # Example: match conversation by name - conversations = Conversation.objects.filter(name__icontains=entity.name) + qs = Conversation.objects.all() + if options.get("workspace_id"): + qs = qs.filter(workspace_id=options["workspace_id"]) + conversations = qs.filter(name__icontains=entity.name) @@ - self.stdout.write(self.style.SUCCESS(f'Created {created} EntityChannel records.')) + self.stdout.write(self.style.SUCCESS(f"Created {created} EntityChannel records."))
11-36
: Add basic progress logging (optional)A small bit of feedback helps in long runs.
- created = 0 + created = 0 + processed = 0 @@ - for conv in conversations: + for conv in conversations: obj, was_created = EntityChannel.objects.get_or_create( @@ if was_created: created += 1 + processed += 1 - self.stdout.write(self.style.SUCCESS(f'Created {created} EntityChannel records.')) + self.stdout.write(self.style.SUCCESS(f"Processed {processed} links; created {created} EntityChannel records."))backend/apps/slack/models/entity_channel.py (2)
7-8
: Optional: guide content_type selection to OWASP entitiesLimit choices in the admin to Chapter/Committee/Project content types to reduce mistakes.
- content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + content_type = models.ForeignKey( + ContentType, + on_delete=models.CASCADE, + limit_choices_to={ + "app_label": "owasp", + "model__in": ["chapter", "committee", "project"], + }, + )
6-6
: Add a brief class docstring (optional)Improves readability and satisfies D101 if enforced.
-class EntityChannel(models.Model): +class EntityChannel(models.Model): + """Link between an OWASP entity (GFK) and a Slack Conversation."""
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
backend/apps/slack/admin/__init__.py
(1 hunks)backend/apps/slack/admin/entity_channel.py
(1 hunks)backend/apps/slack/management/commands/owasp_match_channels.py
(1 hunks)backend/apps/slack/migrations/0019_entitychannel.py
(1 hunks)backend/apps/slack/migrations/0020_remove_entitychannel_nest_created_at_and_more.py
(1 hunks)backend/apps/slack/models/__init__.py
(1 hunks)backend/apps/slack/models/entity_channel.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
backend/apps/slack/migrations/0020_remove_entitychannel_nest_created_at_and_more.py (1)
backend/apps/slack/migrations/0019_entitychannel.py (1)
Migration
(7-50)
backend/apps/slack/admin/__init__.py (1)
backend/apps/slack/admin/entity_channel.py (1)
EntityChannelAdmin
(10-14)
backend/apps/slack/management/commands/owasp_match_channels.py (4)
backend/apps/slack/models/conversation.py (1)
Conversation
(15-104)backend/apps/slack/models/entity_channel.py (1)
EntityChannel
(6-21)backend/apps/owasp/api/internal/queries/chapter.py (1)
chapter
(14-19)backend/apps/owasp/api/internal/queries/committee.py (1)
committee
(14-28)
backend/apps/slack/admin/entity_channel.py (1)
backend/apps/slack/models/entity_channel.py (1)
EntityChannel
(6-21)
backend/apps/slack/models/__init__.py (1)
backend/apps/slack/models/entity_channel.py (1)
EntityChannel
(6-21)
backend/apps/slack/migrations/0019_entitychannel.py (1)
backend/apps/slack/migrations/0020_remove_entitychannel_nest_created_at_and_more.py (1)
Migration
(6-21)
backend/apps/slack/models/entity_channel.py (1)
backend/apps/slack/models/conversation.py (1)
Conversation
(15-104)
🪛 Ruff (0.12.2)
backend/apps/slack/migrations/0020_remove_entitychannel_nest_created_at_and_more.py
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
14-14: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
18-18: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
19-19: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/apps/slack/management/commands/owasp_match_channels.py
1-1: Missing docstring in public module
(D100)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Line too long (103 > 99)
(E501)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
17-17: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
17-17: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
29-29: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
30-30: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
31-31: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
31-31: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
36-36: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/apps/slack/admin/entity_channel.py
1-1: Missing docstring in public module
(D100)
2-2: django.contrib.contenttypes.admin.GenericTabularInline
imported but unused
Remove unused import: django.contrib.contenttypes.admin.GenericTabularInline
(F401)
3-3: Prefer absolute imports over relative imports from parent modules
Replace relative imports from parent modules with absolute imports
(TID252)
6-6: Missing docstring in public function
(D103)
6-6: Unused function argument: modeladmin
(ARG001)
6-6: Unused function argument: request
(ARG001)
10-10: Missing docstring in public class
(D101)
backend/apps/slack/migrations/0019_entitychannel.py
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
10-10: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
10-10: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
18-18: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
20-20: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
23-23: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
24-24: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
25-25: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
26-26: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
27-27: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
28-28: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
28-28: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
30-30: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
33-33: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
37-37: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
40-40: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
45-45: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
45-45: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
45-45: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
45-45: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
46-46: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
46-46: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
47-47: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
47-47: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/apps/slack/models/entity_channel.py
1-1: Missing docstring in public module
(D100)
6-6: Missing docstring in public class
(D101)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
13-13: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
17-17: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
18-18: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
20-20: Missing docstring in magic method
(D105)
🔇 Additional comments (7)
backend/apps/slack/models/__init__.py (3)
2-2
: Re-export looks goodEntityChannel is correctly exposed for external imports. No issues.
2-2
: Public API re-export looks goodExposing EntityChannel at the package level aligns with admin/command usage patterns and keeps imports ergonomic.
2-2
: Add explicit public API and confirm no import cyclesI’ve added an
__all__
to make the module’s public surface intentional, and ran a scan for any package-level imports underbackend/apps/slack
—no occurrences were found, so there’s no need to switch any intra-package imports to relative paths.Proposed diff:
from .conversation import Conversation from .entity_channel import EntityChannel from .event import Event from .member import Member from .message import Message from .workspace import Workspace + +__all__ = ( + "Conversation", + "EntityChannel", + "Event", + "Member", + "Message", + "Workspace", +)backend/apps/slack/admin/__init__.py (1)
4-4
: LGTM on admin exportEntityChannelAdmin is properly exposed alongside other admins.
backend/apps/slack/migrations/0020_remove_entitychannel_nest_created_at_and_more.py (1)
12-21
: Migration aligns schema with modelRemoving nest_created_at/nest_updated_at matches the model definition. Safe and consistent with 0019.
backend/apps/slack/management/commands/owasp_match_channels.py (1)
1-36
: Ignore import convention suggestionThe project consistently imports Slack models using
from apps.slack.models…
, including inowasp_match_channels.py
. There are nobackend.apps.slack.models
imports elsewhere, so no change is needed here—please disregard the recommendation to switch tobackend.apps…
imports.Likely an incorrect or invalid review comment.
backend/apps/slack/migrations/0019_entitychannel.py (1)
13-49
: Initial migration looks consistentCreateModel matches the model and is correctly depended upon by 0020 which removes the timestamp fields. No changes needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (4)
backend/apps/slack/management/commands/owasp_match_channels.py (4)
10-14
: Support workspace scoping via CLI and tighten the help string (also fix quotes/length)
- Add a --workspace-id option to avoid cross-workspace matches.
- Shorten/fix the help string (E501, Q000).
class Command(BaseCommand): - help = 'Populate EntityChannel records for Chapters, Committees, and Projects based on Slack data.' + help = "Populate EntityChannel links for Chapters, Committees, and Projects." + + def add_arguments(self, parser): + parser.add_argument( + "--workspace-id", + type=int, + help="Limit matching to a specific Slack workspace ID.", + ) def handle(self, *args, **options): from django.utils.text import slugify
1-6
: Add missing imports (transaction, Q) and a module docstring
- Provide a short module docstring (D100).
- Import transaction and Q to support the atomic block and broadened matching below.
+"""Link OWASP entities to Slack conversations by creating EntityChannel records.""" from django.core.management.base import BaseCommand from django.contrib.contenttypes.models import ContentType +from django.db import transaction +from django.db.models import Q from apps.slack.models import Conversation, EntityChannel from apps.owasp.models.chapter import Chapter from apps.owasp.models.committee import Committee
24-27
: Apply the optional workspace filter when providedWire up the filtering now that the argument exists, and remove leftover commented code (ERA001).
- qs = Conversation.objects.all() - # If you add --workspace-id, filter here: - # qs = qs.filter(workspace_id=options.get("workspace_id")) + qs = Conversation.objects.all() + workspace_id = options.get("workspace_id") + if workspace_id: + qs = qs.filter(workspace_id=workspace_id)
13-41
: Wrap the bulk operation in a single atomic transactionEnsures all-or-nothing behavior and reduces commit overhead for large batches.
def handle(self, *args, **options): from django.utils.text import slugify - created = 0 - for model in (Chapter, Committee, Project): - content_type = ContentType.objects.get_for_model(model) - # Use .only and .iterator for memory efficiency - for entity in model.objects.all().only("id", "name").iterator(): - # Normalize the name for matching (e.g., "OWASP Lima" -> "owasp-lima") - needle = slugify(entity.name or "") - if not needle: - continue - qs = Conversation.objects.all() - # If you add --workspace-id, filter here: - # qs = qs.filter(workspace_id=options.get("workspace_id")) - conversations = qs.filter(name__icontains=needle) - for conv in conversations: - _, was_created = EntityChannel.objects.get_or_create( - content_type=content_type, - object_id=entity.pk, - conversation=conv, - defaults={ - "is_main_channel": False, - "is_reviewed": False, - "kind": "slack", - } - ) - if was_created: - created += 1 - self.stdout.write(self.style.SUCCESS(f'Created {created} EntityChannel records.')) + created = 0 + with transaction.atomic(): + for model in (Chapter, Committee, Project): + content_type = ContentType.objects.get_for_model(model) + # Use .only and .iterator for memory efficiency + for entity in model.objects.all().only("id", "name").iterator(): + # Normalize the name for matching (e.g., "OWASP Lima" -> "owasp-lima") + needle = slugify(entity.name or "") + if not needle: + continue + qs = Conversation.objects.all() + workspace_id = options.get("workspace_id") + if workspace_id: + qs = qs.filter(workspace_id=workspace_id) + alt_needle = needle.replace("-", "_") + conversations = qs.filter( + Q(name__icontains=needle) | Q(name__icontains=alt_needle) + ) + for conv in conversations: + _, was_created = EntityChannel.objects.get_or_create( + content_type=content_type, + object_id=entity.pk, + conversation=conv, + defaults={ + "is_main_channel": False, + "is_reviewed": False, + "kind": "slack", + } + ) + if was_created: + created += 1 + self.stdout.write(self.style.SUCCESS(f"Created {created} EntityChannel records."))
🧹 Nitpick comments (3)
backend/apps/slack/management/commands/owasp_match_channels.py (2)
27-27
: Broaden matching to handle hyphens vs underscores in channel namesSlack channel naming varies; matching both hyphenated and underscored variants improves recall.
- conversations = qs.filter(name__icontains=needle) + alt_needle = needle.replace("-", "_") + conversations = qs.filter( + Q(name__icontains=needle) | Q(name__icontains=alt_needle) + )
29-29
: Avoid unused variable binding from get_or_createThe instance return value isn’t used. Use underscore to signal intentional discard.
- obj, was_created = EntityChannel.objects.get_or_create( + _, was_created = EntityChannel.objects.get_or_create(backend/apps/slack/admin/entity_channel.py (1)
15-18
: Optimize changelist queries with list_select_relatedPreload FKs to reduce N+1 queries in the admin changelist.
class EntityChannelAdmin(admin.ModelAdmin): list_display = ("entity", "conversation", "is_main_channel", "is_reviewed", "kind") list_filter = ("is_main_channel", "is_reviewed", "kind", "content_type") search_fields = ("object_id", "conversation__name") + list_select_related = ("conversation", "content_type") actions = [mark_as_reviewed]
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
backend/apps/slack/admin/entity_channel.py
(1 hunks)backend/apps/slack/management/commands/owasp_match_channels.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
backend/apps/slack/management/commands/owasp_match_channels.py (4)
backend/apps/slack/models/conversation.py (1)
Conversation
(15-104)backend/apps/slack/models/entity_channel.py (1)
EntityChannel
(6-21)backend/apps/owasp/api/internal/queries/chapter.py (1)
chapter
(14-19)backend/apps/owasp/api/internal/queries/committee.py (1)
committee
(14-28)
backend/apps/slack/admin/entity_channel.py (1)
backend/apps/slack/models/entity_channel.py (1)
EntityChannel
(6-21)
🪛 Ruff (0.12.2)
backend/apps/slack/management/commands/owasp_match_channels.py
1-1: Missing docstring in public module
(D100)
11-11: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
11-11: Line too long (103 > 99)
(E501)
26-26: Found commented-out code
Remove commented-out code
(ERA001)
41-41: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/apps/slack/admin/entity_channel.py
1-1: Missing docstring in public module
(D100)
2-2: Prefer absolute imports over relative imports from parent modules
Replace relative imports from parent modules with absolute imports
(TID252)
5-5: Missing docstring in public function
(D103)
8-8: Private member accessed: _messages
(SLF001)
14-14: Missing docstring in public class
(D101)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
backend/apps/slack/management/commands/owasp_match_channels.py (2)
42-46
: Avoid repeating the model default for kind.The EntityChannel model already defaults kind="slack". Omitting it reduces duplication and accidental drift if the default changes later.
Apply this diff:
- defaults={ - "is_main_channel": False, - "is_reviewed": False, - "kind": "slack", - }, + defaults={ + "is_main_channel": False, + "is_reviewed": False, + },
18-50
: Enhance CLI feedback: track per-model counts and print a detailed summary.Currently only a total created count is printed. Per-model counts improve observability during runs and help validate matching quality.
Apply this diff to track and print per-model counts:
- created = 0 + created_total = 0 with transaction.atomic(): for model in (Chapter, Committee, Project): content_type = ContentType.objects.get_for_model(model) # Use .only and .iterator for memory efficiency for entity in model.objects.all().only("id", "name").iterator(): @@ ) if was_created: - created += 1 - self.stdout.write(self.style.SUCCESS(f"Created {created} EntityChannel records.")) + created_total += 1 + # Summary output + self.stdout.write(self.style.SUCCESS(f"Created {created_total} EntityChannel records."))And add a lightweight per-model counter if desired:
# Add near created_total declaration: from collections import defaultdict # At top-level imports created_by_model = defaultdict(int) # Increment alongside created_total: if was_created: created_total += 1 created_by_model[model.__name__] += 1 # After the loop, print per-model breakdown: for model_name, count in created_by_model.items(): self.stdout.write(f" {model_name}: {count}")Note: The import must be added at the top of the file:
from collections import defaultdict
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
backend/apps/slack/admin/entity_channel.py
(1 hunks)backend/apps/slack/management/commands/owasp_match_channels.py
(1 hunks)backend/apps/slack/migrations/0019_entitychannel.py
(1 hunks)backend/apps/slack/migrations/0020_remove_entitychannel_nest_created_at_and_more.py
(1 hunks)backend/apps/slack/models/entity_channel.py
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- backend/apps/slack/migrations/0019_entitychannel.py
- backend/apps/slack/admin/entity_channel.py
- backend/apps/slack/migrations/0020_remove_entitychannel_nest_created_at_and_more.py
- backend/apps/slack/models/entity_channel.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/apps/slack/management/commands/owasp_match_channels.py (2)
backend/apps/slack/models/conversation.py (1)
Conversation
(15-104)backend/apps/slack/models/entity_channel.py (1)
EntityChannel
(10-28)
🔇 Additional comments (1)
backend/apps/slack/management/commands/owasp_match_channels.py (1)
21-24
: Nice: memory-efficient iteration and per-model ContentType lookup.Using .only("id", "name").iterator() keeps memory bounded, and ContentType.objects.get_for_model(model) avoids hard-coded labels. Good choices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the EntityChannel
model structure - it now supports both arbitrary entities and channels. Please adjust code accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And also take care of bot's comments before requesting next review. Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (2)
backend/apps/slack/migrations/0019_entitychannel.py (1)
64-68
: Enforce single “main/default” channel per entity+platform and add lookup indexesTo preserve data integrity and speed common queries:
- Add a conditional UniqueConstraint ensuring only one is_default=True per (entity_type, entity_id, platform).
- Add composite indexes for GFK lookups.
This mirrors a prior suggestion and adapts it to your current field names.
options={ "verbose_name": "Entity channel", "verbose_name_plural": "Entity channels", - "unique_together": {("channel_id", "channel_type", "entity_id", "entity_type")}, + "unique_together": {("channel_id", "channel_type", "entity_id", "entity_type")}, + "constraints": [ + models.UniqueConstraint( + fields=("entity_id", "entity_type", "platform"), + condition=models.Q(is_default=True), + name="unique_default_channel_per_entity_platform", + ), + ], + "indexes": [ + models.Index(fields=("entity_type", "entity_id"), name="idx_entity_gfk"), + models.Index(fields=("channel_type", "channel_id"), name="idx_channel_gfk"), + ], },backend/apps/slack/models/entity_channel.py (1)
14-23
: Enforce one default channel per entity+platform and add lookup indexesAdd a conditional UniqueConstraint to ensure only one is_default=True per entity+platform, and indexes to speed frequent lookups by GFK pairs. This echoes an earlier recommendation, adapted to the new field names.
class Meta: unique_together = ( "channel_id", "channel_type", "entity_id", "entity_type", ) verbose_name = "Entity channel" verbose_name_plural = "Entity channels" + constraints = [ + models.UniqueConstraint( + fields=("entity_id", "entity_type", "platform"), + condition=Q(is_default=True), + name="unique_default_channel_per_entity_platform", + ), + ] + indexes = [ + models.Index(fields=("entity_type", "entity_id"), name="idx_entity_gfk"), + models.Index(fields=("channel_type", "channel_id"), name="idx_channel_gfk"), + ]
🧹 Nitpick comments (3)
backend/apps/slack/admin/entity_channel.py (2)
11-14
: Avoid side-effects inside f-strings; compute update count firstAssign the update count to a variable for clarity and to avoid embedding a side-effect in the f-string.
- messages.success( - request, - f"Marked {queryset.update(is_reviewed=True)} EntityChannel(s) as reviewed.", - ) + updated = queryset.update(is_reviewed=True) + messages.success(request, f"Marked {updated} EntityChannel(s) as reviewed.")
36-36
: Use exact-match searches for integer fields in adminDjango admin search_fields uses icontains by default, which casts integers to text. Prefer exact-match searches for numeric fields to avoid unnecessary casting and to get predictable results.
- search_fields = ("channel_id", "entity_id") + search_fields = ("=channel_id", "=entity_id")backend/apps/slack/models/entity_channel.py (1)
3-6
: Add Q import to support conditional DB constraint (see next comment)Required if you add a conditional UniqueConstraint in Meta.
-from django.db import models +from django.db import models +from django.db.models import Q
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
backend/apps/slack/admin/entity_channel.py
(1 hunks)backend/apps/slack/migrations/0019_entitychannel.py
(1 hunks)backend/apps/slack/models/entity_channel.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/apps/slack/admin/entity_channel.py (1)
backend/apps/slack/models/entity_channel.py (1)
EntityChannel
(8-59)
🔇 Additional comments (1)
backend/apps/slack/models/entity_channel.py (1)
42-49
: Confirm naming alignment with issue language (“is_main_channel” vs “is_default”)The ticket uses “is_main_channel” terminology. Your code uses “is_default.” If there’s any external API/admin/reporting depending on the former name, ensure consistency or add migration/aliases before merge.
Would you like me to scan for any references to is_main_channel/kind in the repo and open a follow-up commit if needed?
@anurag2787 is it ready for review? It's a bit overdue now. |
Not yet I will need a little more time for this and will update you soon |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
backend/apps/slack/migrations/0019_entitychannel.py (4)
26-31
: Rename is_default to is_main_channel to match domain language and PR objectives.Spec calls this “is_main_channel”. Aligning names reduces cognitive load across admin/actions/management command.
Apply this diff:
- ( - "is_default", - models.BooleanField( - default=False, - help_text="Indicates if this is the main channel for the entity", - ), - ), + ( + "is_main_channel", + models.BooleanField( + default=False, + help_text="Marks the primary channel for this entity", + ), + ),
65-68
: Replace legacy unique_together with UniqueConstraint and add indexes for common lookups.Modern Django prefers UniqueConstraint; also add non-unique indexes to speed up lookups by entity and by channel. Optional but recommended.
Apply this diff (assumes the rename to is_main_channel; if you keep is_default, adjust the field name in the constraint accordingly):
options={ "verbose_name": "Entity channel", "verbose_name_plural": "Entity channels", - "unique_together": {("channel_id", "channel_type", "entity_id", "entity_type")}, + "constraints": [ + models.UniqueConstraint( + fields=("channel_type", "channel_id", "entity_type", "entity_id"), + name="uniq_entitychannel_link", + ), + # One main channel per entity (adjust if you want per-platform instead) + models.UniqueConstraint( + fields=("entity_type", "entity_id"), + condition=models.Q(is_main_channel=True), + name="uniq_main_channel_per_entity", + ), + ], + "indexes": [ + models.Index( + fields=["entity_type", "entity_id"], + name="entity_lookup_idx", + ), + models.Index( + fields=["channel_type", "channel_id"], + name="channel_lookup_idx", + ), + ], },If you want “one main channel per entity per platform” instead, make the second constraint fields=("entity_type", "entity_id", "platform").
23-24
: Confirm: Channel side is a GenericForeignKey (GFK), but objectives asked for FK to slack.Conversation.The schema models both ends (entity and channel) as generic via channel_type/channel_id and entity_type/entity_id. The PR objectives stated a concrete ForeignKey to slack.Conversation. If GFK-for-channel is intentional (to future‑proof for Discord, etc.), please update the PR description/objectives; if not, consider switching to a concrete FK for Slack now and introduce polymorphism later.
Follow‑ups if you keep GFK on the channel side:
- Add app‑level guards to prevent orphan records when a Conversation is deleted (GFK can’t enforce ON DELETE behavior).
- Consider deriving platform from channel_type.app_label to avoid drift (see next comment).
Also applies to: 48-54
39-45
: Platform field: verify naming vs. spec (“kind”) and prevent drift with channel_type.Spec calls this field “kind”. Either rename for consistency or update docs/command/admin to use “platform”. Also, since platform is derivable from channel_type (e.g., app_label "slack"), ensure you set it automatically in the model layer to prevent inconsistencies (validators/save()).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
backend/apps/slack/management/commands/owasp_match_channels.py
(1 hunks)backend/apps/slack/migrations/0019_entitychannel.py
(1 hunks)backend/apps/slack/models/entity_channel.py
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- backend/apps/slack/management/commands/owasp_match_channels.py
- backend/apps/slack/models/entity_channel.py
🔇 Additional comments (1)
backend/apps/slack/migrations/0019_entitychannel.py (1)
23-24
: 64‑bit IDs for GFKs: good alignment with BigAutoField.Using PositiveBigIntegerField for both channel_id and entity_id correctly matches 64‑bit primary keys and avoids overflow issues.
|
Resolves #2019
Description:
This feature links OWASP entities (Chapters, Projects, Committees) to their related Slack channels for better communication mapping. A new, extensible model called EntityChannel is introduced to store these links, and a management command is provided to automatically match and populate these relationships.
Key Changes:
Added a new model
EntityChannel
with:GenericForeignKey
).ForeignKey
to the SlackConversation
.is_main_channel
andis_reviewed
boolean fields.kind
field to support multiple platforms (default:"slack"
).Created a management command
owasp_match_channels
to auto-link entities to Slack channels based on matching logic.is_reviewed=False
by default.Checklist
make check-test
locally; all checks and tests passed.