Skip to content
23 changes: 20 additions & 3 deletions backend/apps/owasp/admin/entity_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@

@admin.action(description="Mark selected EntityChannels as reviewed")
def mark_as_reviewed(_modeladmin, request, queryset):
"""Admin action to mark selected EntityChannels as reviewed."""
"""Mark EntityChannels as reviewed for entity communication tracking.

Args:
_modeladmin (EntityChannelAdmin): The admin instance.
request (HttpRequest): The current admin request.
queryset (QuerySet): Selected EntityChannel instances.

"""
messages.success(
request,
f"Marked {queryset.update(is_reviewed=True)} EntityChannel(s) as reviewed.",
Expand Down Expand Up @@ -62,7 +69,7 @@ class EntityChannelAdmin(admin.ModelAdmin):
)

def channel_search_display(self, obj):
"""Display the channel name for the selected channel."""
"""Display the human-readable channel name in the admin list."""
if obj.channel_id and obj.channel_type:
try:
if obj.channel_type.model == "conversation":
Expand All @@ -75,7 +82,17 @@ def channel_search_display(self, obj):
channel_search_display.short_description = "Channel Name"

def get_form(self, request, obj=None, **kwargs):
"""Get the form for the EntityChannel model."""
"""Customize the EntityChannel form for Slack conversation selection.

Args:
request (HttpRequest): The current admin request.
obj (EntityChannel, optional): The instance being edited.
**kwargs: Additional keyword arguments.

Returns:
Form: The customized form class.

"""
form = super().get_form(request, obj, **kwargs)
form.conversation_content_type_id = ContentType.objects.get_for_model(Conversation).id

Expand Down
24 changes: 20 additions & 4 deletions backend/apps/owasp/admin/entity_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,21 @@ class EntityMemberAdmin(admin.ModelAdmin):

@admin.action(description="Approve selected members")
def approve_members(self, request, queryset):
"""Approve selected members."""
"""Activate and mark members as reviewed for display in the entity roster.

Args:
request (HttpRequest): The current admin request.
queryset (QuerySet): Selected EntityMember instances.

"""
self.message_user(
request,
f"Successfully approved {queryset.update(is_active=True, is_reviewed=True)} members.",
)

@admin.display(description="Entity", ordering="entity_type")
def entity(self, obj):
"""Return entity link."""
"""Display the related OWASP entity for this member association."""
return (
format_html(
'<a href="{}" target="_blank">{}</a>',
Expand All @@ -66,15 +72,25 @@ def entity(self, obj):

@admin.display(description="OWASP URL", ordering="entity_type")
def owasp_url(self, obj):
"""Return entity OWASP site URL."""
"""Display the entity's public OWASP website."""
return (
format_html('<a href="{}" target="_blank">↗️</a>', obj.entity.owasp_url)
if obj.entity
else "-"
)

def get_search_results(self, request, queryset, search_term):
"""Get search results from entity name or key."""
"""Extend search to include associated entity names and keys.

Args:
request (HttpRequest): The current admin request.
queryset (QuerySet): Initial queryset of EntityMember instances.
search_term (str): The search term entered by the user.

Returns:
tuple: Filtered queryset and boolean indicating if DISTINCT is needed.

"""
queryset, use_distinct = super().get_search_results(request, queryset, search_term)

if search_term:
Expand Down
10 changes: 9 additions & 1 deletion backend/apps/owasp/admin/member_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,15 @@ class MemberProfileAdmin(admin.ModelAdmin):
)

def get_queryset(self, request):
"""Optimize queryset with select_related."""
"""Return the queryset for the admin list view.

Args:
request (HttpRequest): The current admin request.

Returns:
QuerySet: QuerySet with related github_user included.

"""
queryset = super().get_queryset(request)
return queryset.select_related("github_user")

Expand Down
10 changes: 9 additions & 1 deletion backend/apps/owasp/admin/member_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,15 @@ class MemberSnapshotAdmin(admin.ModelAdmin):
)

def get_queryset(self, request):
"""Optimize queryset with select_related."""
"""Return the queryset for the admin list view.

Args:
request (HttpRequest): The current admin request.

Returns:
QuerySet: QuerySet with related github_user included.

"""
queryset = super().get_queryset(request)
return queryset.select_related("github_user")

Expand Down
63 changes: 51 additions & 12 deletions backend/apps/owasp/admin/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,28 @@ class BaseOwaspAdminMixin:
)

def get_base_list_display(self, *additional_fields):
"""Get base list display with additional fields."""
return tuple(
("name",) if hasattr(self.model, "name") else (),
*additional_fields,
*self.list_display_field_names,
)
"""Build the standard list display configuration for OWASP entities.

Args:
*additional_fields: Field names to add to base list display.

Returns:
tuple: Complete list_display configuration.

"""
base = ("name",) if hasattr(self.model, "name") else ()
return base + tuple(additional_fields) + self.list_display_field_names

def get_base_search_fields(self, *additional_fields):
"""Get base search fields with additional fields."""
"""Build the standard search configuration for OWASP entities.

Args:
*additional_fields: Field names to add to base search fields.

Returns:
tuple: Complete search_fields configuration.

"""
return self.search_field_names + additional_fields


Expand Down Expand Up @@ -80,7 +93,17 @@ 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 fields for EntityChannel inline entries.

Args:
db_field (Field): The database field being rendered.
request (HttpRequest): The current admin request.
**kwargs: Additional keyword arguments.

Returns:
FormField: The customized form field.

"""
if db_field.name == "channel_id":
kwargs["widget"] = ChannelIdWidget()
elif db_field.name == "channel_type":
Expand All @@ -96,11 +119,19 @@ class GenericEntityAdminMixin(BaseOwaspAdminMixin):
"""Mixin for generic entity admin with common entity functionality."""

def get_queryset(self, request):
"""Get queryset with optimized relations."""
"""Return the queryset for entities with repository relationships.

Args:
request (HttpRequest): The current admin request.

Returns:
QuerySet: QuerySet with related repositories prefetched.

"""
return super().get_queryset(request).prefetch_related("repositories")

def custom_field_github_urls(self, obj):
"""Entity GitHub URLs with uniform formatting."""
"""Display GitHub repository links for the entity."""
if not hasattr(obj, "repositories"):
if not hasattr(obj, "owasp_repository") or not obj.owasp_repository:
return ""
Expand All @@ -113,7 +144,7 @@ def custom_field_github_urls(self, obj):
)

def custom_field_owasp_url(self, obj):
"""Entity OWASP URL with uniform formatting."""
"""Display the entity's OWASP website."""
if not hasattr(obj, "key") or not obj.key:
return ""

Expand All @@ -122,7 +153,15 @@ def custom_field_owasp_url(self, obj):
)

def _format_github_link(self, repository):
"""Format a single GitHub repository link."""
"""Return a formatted HTML link to a GitHub repository.

Args:
repository (Repository): The repository instance.

Returns:
str: HTML link or empty string if repository data is incomplete.

"""
if not repository or not hasattr(repository, "owner") or not repository.owner:
return ""
if not hasattr(repository.owner, "login") or not repository.owner.login:
Expand Down
2 changes: 1 addition & 1 deletion backend/apps/owasp/admin/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ProjectAdmin(admin.ModelAdmin, GenericEntityAdminMixin):
)

def custom_field_name(self, obj) -> str:
"""Project custom name."""
"""Display the project identifier in the admin list."""
return f"{obj.name or obj.key}"

custom_field_name.short_description = "Name"
Expand Down
2 changes: 1 addition & 1 deletion backend/apps/owasp/admin/project_health_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ProjectHealthMetricsAdmin(admin.ModelAdmin, StandardOwaspAdminMixin):
search_fields = ("project__name",)

def project(self, obj):
"""Display project name."""
"""Display the associated project in the admin list."""
return obj.project.name if obj.project else "N/A"


Expand Down
8 changes: 7 additions & 1 deletion backend/apps/slack/admin/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ class MemberAdmin(admin.ModelAdmin):
)

def approve_suggested_users(self, request, queryset):
"""Approve all suggested users for selected members, enforcing one-to-one constraints."""
"""Link selected Slack members to their suggested GitHub user accounts.

Args:
request (HttpRequest): The current admin request.
queryset (QuerySet): Selected member instances.

"""
for entity in queryset:
suggestions = entity.suggested_users.all()

Expand Down