diff --git a/backend/apps/owasp/admin/entity_channel.py b/backend/apps/owasp/admin/entity_channel.py
index 2f2bcf3c86..1b04b4dd29 100644
--- a/backend/apps/owasp/admin/entity_channel.py
+++ b/backend/apps/owasp/admin/entity_channel.py
@@ -9,7 +9,7 @@
@admin.action(description="Mark selected EntityChannels as reviewed")
def mark_as_reviewed(_modeladmin, request, queryset):
- """Admin action to mark selected EntityChannels as reviewed."""
+ """Mark selected EntityChannel records as reviewed."""
messages.success(
request,
f"Marked {queryset.update(is_reviewed=True)} EntityChannel(s) as reviewed.",
@@ -62,7 +62,7 @@ class EntityChannelAdmin(admin.ModelAdmin):
)
def channel_search_display(self, obj):
- """Display the channel name for the selected channel."""
+ """Return a readable channel label for admin display."""
if obj.channel_id and obj.channel_type:
try:
if obj.channel_type.model == "conversation":
@@ -71,11 +71,10 @@ def channel_search_display(self, obj):
except Conversation.DoesNotExist:
return f"Channel {obj.channel_id} (not found)"
return "-"
-
channel_search_display.short_description = "Channel Name"
def get_form(self, request, obj=None, **kwargs):
- """Get the form for the EntityChannel model."""
+ """Return the admin form with Conversation content type metadata attached."""
form = super().get_form(request, obj, **kwargs)
form.conversation_content_type_id = ContentType.objects.get_for_model(Conversation).id
diff --git a/backend/apps/owasp/admin/entity_member.py b/backend/apps/owasp/admin/entity_member.py
index 1aef460fbb..e49309a020 100644
--- a/backend/apps/owasp/admin/entity_member.py
+++ b/backend/apps/owasp/admin/entity_member.py
@@ -13,8 +13,8 @@
class EntityMemberAdmin(admin.ModelAdmin):
- """Admin for EntityMember records (generic link to any OWASP entity)."""
-
+ """Admin configuration for EntityMember records."""
+
actions = ("approve_members",)
autocomplete_fields = ("member",)
list_display = (
@@ -42,7 +42,11 @@ class EntityMemberAdmin(admin.ModelAdmin):
@admin.action(description="Approve selected members")
def approve_members(self, request, queryset):
- """Approve selected members."""
+ """Admin action to approve selected entity members.
+
+ Sets `is_active=True` and `is_reviewed=True` on all selected records
+ and displays a success message showing how many were updated.
+ """
self.message_user(
request,
f"Successfully approved {queryset.update(is_active=True, is_reviewed=True)} members.",
@@ -50,7 +54,10 @@ def approve_members(self, request, queryset):
@admin.display(description="Entity", ordering="entity_type")
def entity(self, obj):
- """Return entity link."""
+ """Return a clickable admin link to the related entity.
+
+ Example output: a link to the Project/Chapter/Committee admin change page.
+ """
return (
format_html(
'{}',
@@ -66,7 +73,7 @@ def entity(self, obj):
@admin.display(description="OWASP URL", ordering="entity_type")
def owasp_url(self, obj):
- """Return entity OWASP site URL."""
+ """Return a link to the OWASP site page of the linked entity."""
return (
format_html('↗️', obj.entity.owasp_url)
if obj.entity
@@ -74,7 +81,15 @@ def owasp_url(self, obj):
)
def get_search_results(self, request, queryset, search_term):
- """Get search results from entity name or key."""
+ """Extend default search to also match entity names and keys.
+
+ In addition to the built-in search, this method searches:
+ - Project name or key
+ - Chapter name or key
+ - Committee name or key
+
+ and includes matching EntityMember rows in the results.
+ """
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
if search_term:
diff --git a/backend/apps/owasp/admin/member_profile.py b/backend/apps/owasp/admin/member_profile.py
index 9e5ec7ddaa..c064bcf43f 100644
--- a/backend/apps/owasp/admin/member_profile.py
+++ b/backend/apps/owasp/admin/member_profile.py
@@ -72,7 +72,13 @@ class MemberProfileAdmin(admin.ModelAdmin):
)
def get_queryset(self, request):
- """Optimize queryset with select_related."""
+ """
+ Return an optimized queryset for the MemberProfile admin list view.
+
+ This override applies `select_related("github_user")` to reduce the
+ number of SQL queries when displaying MemberProfile entries that include
+ related GitHub user information.
+ """
queryset = super().get_queryset(request)
return queryset.select_related("github_user")
diff --git a/backend/apps/owasp/admin/member_snapshot.py b/backend/apps/owasp/admin/member_snapshot.py
index 84a7233852..8326575d60 100644
--- a/backend/apps/owasp/admin/member_snapshot.py
+++ b/backend/apps/owasp/admin/member_snapshot.py
@@ -108,7 +108,11 @@ class MemberSnapshotAdmin(admin.ModelAdmin):
)
def get_queryset(self, request):
- """Optimize queryset with select_related."""
+ """Return an optimized queryset for the MemberSnapshot admin list view.
+
+ Ensures related GitHub user information is loaded efficiently to
+ avoid unnecessary database queries in the admin list view.
+ """
queryset = super().get_queryset(request)
return queryset.select_related("github_user")
diff --git a/backend/apps/owasp/admin/mixins.py b/backend/apps/owasp/admin/mixins.py
index 9addfc4a6e..8535019adc 100644
--- a/backend/apps/owasp/admin/mixins.py
+++ b/backend/apps/owasp/admin/mixins.py
@@ -12,7 +12,12 @@
class BaseOwaspAdminMixin:
- """Base mixin for OWASP admin classes providing common patterns."""
+ """Base mixin for OWASP admin classes.
+
+ Provides common configuration patterns—such as default list_display,
+ list_filter, and search_fields—so individual ModelAdmin classes can avoid
+ duplicated boilerplate.
+ """
# Common configuration patterns.
list_display_field_names = (
@@ -26,7 +31,7 @@ class BaseOwaspAdminMixin:
)
def get_base_list_display(self, *additional_fields):
- """Get base list display with additional fields."""
+ """Construct a standard list_display value with optional extra fields."""
return tuple(
("name",) if hasattr(self.model, "name") else (),
*additional_fields,
@@ -34,12 +39,12 @@ def get_base_list_display(self, *additional_fields):
)
def get_base_search_fields(self, *additional_fields):
- """Get base search fields with additional fields."""
+ """Construct a standard search_fields value with optional extra fields."""
return self.search_field_names + additional_fields
class EntityMemberInline(GenericTabularInline):
- """EntityMember inline for admin."""
+ """Inline admin for EntityMember entries linking users to OWASP entities."""
ct_field = "entity_type"
ct_fk_field = "entity_id"
@@ -63,7 +68,7 @@ class EntityMemberInline(GenericTabularInline):
class EntityChannelInline(GenericTabularInline):
- """EntityChannel inline for admin."""
+ """Inline admin interface for EntityChannel records associated with an entity."""
ct_field = "entity_type"
ct_fk_field = "entity_id"
@@ -80,7 +85,11 @@ class EntityChannelInline(GenericTabularInline):
ordering = ("platform", "channel_id")
def formfield_for_dbfield(self, db_field, request, **kwargs):
- """Override to add custom widget for channel_id field and limit channel_type options."""
+ """Customize form widgets for EntityChannel inline fields.
+
+ - Uses a custom ChannelIdWidget for the channel_id field.
+ - Limits channel_type choices to only Slack Conversation content types.
+ """
if db_field.name == "channel_id":
kwargs["widget"] = ChannelIdWidget()
elif db_field.name == "channel_type":
@@ -93,14 +102,26 @@ def formfield_for_dbfield(self, db_field, request, **kwargs):
class GenericEntityAdminMixin(BaseOwaspAdminMixin):
- """Mixin for generic entity admin with common entity functionality."""
+ """Mixin providing common rendering logic for OWASP entity admin views.
+
+ Adds helpers for displaying GitHub and OWASP links and prefetches related
+ repositories for performance.
+ """
def get_queryset(self, request):
- """Get queryset with optimized relations."""
+ """Extend the base queryset to prefetch related repositories.
+
+ This reduces SQL queries when displaying GitHub-related fields.
+ """
return super().get_queryset(request).prefetch_related("repositories")
def custom_field_github_urls(self, obj):
- """Entity GitHub URLs with uniform formatting."""
+ """Render GitHub URLs for the associated entity.
+
+ Handles:
+ - Entities with multiple repositories (uses obj.repositories)
+ - Entities with a single owasp_repository field
+ """
if not hasattr(obj, "repositories"):
if not hasattr(obj, "owasp_repository") or not obj.owasp_repository:
return ""
@@ -113,7 +134,7 @@ def custom_field_github_urls(self, obj):
)
def custom_field_owasp_url(self, obj):
- """Entity OWASP URL with uniform formatting."""
+ """Render a link to the official OWASP entity webpage."""
if not hasattr(obj, "key") or not obj.key:
return ""
@@ -122,7 +143,7 @@ def custom_field_owasp_url(self, obj):
)
def _format_github_link(self, repository):
- """Format a single GitHub repository link."""
+ """Format a GitHub repository link consistently."""
if not repository or not hasattr(repository, "owner") or not repository.owner:
return ""
if not hasattr(repository.owner, "login") or not repository.owner.login:
@@ -140,12 +161,16 @@ def _format_github_link(self, repository):
class StandardOwaspAdminMixin(BaseOwaspAdminMixin):
- """Standard mixin for simple OWASP admin classes."""
+ """Simple mixin for OWASP admin classes.
+
+ Provides convenient helpers for generating common admin config
+ (list_display, list_filter, search_fields).
+ """
def get_common_config(
self, extra_list_display=None, extra_search_fields=None, extra_list_filters=None
):
- """Get common admin configuration to reduce boilerplate."""
+ """Build a dictionary of common ModelAdmin configuration values."""
config = {}
if extra_list_display:
diff --git a/backend/apps/owasp/admin/project.py b/backend/apps/owasp/admin/project.py
index ccbcd817fd..75343a37a3 100644
--- a/backend/apps/owasp/admin/project.py
+++ b/backend/apps/owasp/admin/project.py
@@ -54,7 +54,13 @@ class ProjectAdmin(admin.ModelAdmin, GenericEntityAdminMixin):
)
def custom_field_name(self, obj) -> str:
- """Project custom name."""
+ """
+ Return a display-friendly project name for the admin list view.
+
+ If the project has a defined `name`, it is shown; otherwise the project
+ key is used as a fallback. This ensures that every project row has a
+ readable identifier even when optional fields are empty.
+ """
return f"{obj.name or obj.key}"
custom_field_name.short_description = "Name"
diff --git a/backend/apps/owasp/admin/project_health_metrics.py b/backend/apps/owasp/admin/project_health_metrics.py
index bcc9574d6b..e30c0a5b4e 100644
--- a/backend/apps/owasp/admin/project_health_metrics.py
+++ b/backend/apps/owasp/admin/project_health_metrics.py
@@ -29,7 +29,13 @@ class ProjectHealthMetricsAdmin(admin.ModelAdmin, StandardOwaspAdminMixin):
search_fields = ("project__name",)
def project(self, obj):
- """Display project name."""
+ """
+ Return the name of the related project for display purposes.
+
+ Used in the admin list view to show a readable project label instead
+ of the raw project foreign key reference.
+
+ """
return obj.project.name if obj.project else "N/A"
diff --git a/backend/apps/slack/admin/member.py b/backend/apps/slack/admin/member.py
index 7dfa92e34a..61ba48b58f 100644
--- a/backend/apps/slack/admin/member.py
+++ b/backend/apps/slack/admin/member.py
@@ -24,7 +24,15 @@ class MemberAdmin(admin.ModelAdmin):
)
def approve_suggested_users(self, request, queryset):
- """Approve all suggested users for selected members, enforcing one-to-one constraints."""
+ """
+ Admin action to assign a suggested user to each selected Member.
+
+ For each Member:
+ - If exactly one suggested user exists, it is assigned to the Member.
+ - If multiple suggested users exist, an error message is returned because only one can be assigned.
+ - If none exist, a warning message is shown.
+
+ """
for entity in queryset:
suggestions = entity.suggested_users.all()