Skip to content

Commit c8e09fb

Browse files
committed
Add descriptive admin method docstrings across Django admin modules
1 parent 28ba425 commit c8e09fb

File tree

8 files changed

+97
-28
lines changed

8 files changed

+97
-28
lines changed

backend/apps/owasp/admin/entity_channel.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
@admin.action(description="Mark selected EntityChannels as reviewed")
1111
def mark_as_reviewed(_modeladmin, request, queryset):
12-
"""Admin action to mark selected EntityChannels as reviewed."""
12+
"""Mark selected EntityChannel records as reviewed."""
1313
messages.success(
1414
request,
1515
f"Marked {queryset.update(is_reviewed=True)} EntityChannel(s) as reviewed.",
@@ -62,7 +62,7 @@ class EntityChannelAdmin(admin.ModelAdmin):
6262
)
6363

6464
def channel_search_display(self, obj):
65-
"""Display the channel name for the selected channel."""
65+
"""Return a readable channel label for admin display."""
6666
if obj.channel_id and obj.channel_type:
6767
try:
6868
if obj.channel_type.model == "conversation":
@@ -71,11 +71,10 @@ def channel_search_display(self, obj):
7171
except Conversation.DoesNotExist:
7272
return f"Channel {obj.channel_id} (not found)"
7373
return "-"
74-
7574
channel_search_display.short_description = "Channel Name"
7675

7776
def get_form(self, request, obj=None, **kwargs):
78-
"""Get the form for the EntityChannel model."""
77+
"""Return the admin form with Conversation content type metadata attached."""
7978
form = super().get_form(request, obj, **kwargs)
8079
form.conversation_content_type_id = ContentType.objects.get_for_model(Conversation).id
8180

backend/apps/owasp/admin/entity_member.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414

1515
class EntityMemberAdmin(admin.ModelAdmin):
16-
"""Admin for EntityMember records (generic link to any OWASP entity)."""
17-
16+
"""Admin configuration for EntityMember records."""
17+
1818
actions = ("approve_members",)
1919
autocomplete_fields = ("member",)
2020
list_display = (
@@ -42,15 +42,22 @@ class EntityMemberAdmin(admin.ModelAdmin):
4242

4343
@admin.action(description="Approve selected members")
4444
def approve_members(self, request, queryset):
45-
"""Approve selected members."""
45+
"""Admin action to approve selected entity members.
46+
47+
Sets `is_active=True` and `is_reviewed=True` on all selected records
48+
and displays a success message showing how many were updated.
49+
"""
4650
self.message_user(
4751
request,
4852
f"Successfully approved {queryset.update(is_active=True, is_reviewed=True)} members.",
4953
)
5054

5155
@admin.display(description="Entity", ordering="entity_type")
5256
def entity(self, obj):
53-
"""Return entity link."""
57+
"""Return a clickable admin link to the related entity.
58+
59+
Example output: a link to the Project/Chapter/Committee admin change page.
60+
"""
5461
return (
5562
format_html(
5663
'<a href="{}" target="_blank">{}</a>',
@@ -66,15 +73,23 @@ def entity(self, obj):
6673

6774
@admin.display(description="OWASP URL", ordering="entity_type")
6875
def owasp_url(self, obj):
69-
"""Return entity OWASP site URL."""
76+
"""Return a link to the OWASP site page of the linked entity."""
7077
return (
7178
format_html('<a href="{}" target="_blank">↗️</a>', obj.entity.owasp_url)
7279
if obj.entity
7380
else "-"
7481
)
7582

7683
def get_search_results(self, request, queryset, search_term):
77-
"""Get search results from entity name or key."""
84+
"""Extend default search to also match entity names and keys.
85+
86+
In addition to the built-in search, this method searches:
87+
- Project name or key
88+
- Chapter name or key
89+
- Committee name or key
90+
91+
and includes matching EntityMember rows in the results.
92+
"""
7893
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
7994

8095
if search_term:

backend/apps/owasp/admin/member_profile.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,13 @@ class MemberProfileAdmin(admin.ModelAdmin):
7272
)
7373

7474
def get_queryset(self, request):
75-
"""Optimize queryset with select_related."""
75+
"""
76+
Return an optimized queryset for the MemberProfile admin list view.
77+
78+
This override applies `select_related("github_user")` to reduce the
79+
number of SQL queries when displaying MemberProfile entries that include
80+
related GitHub user information.
81+
"""
7682
queryset = super().get_queryset(request)
7783
return queryset.select_related("github_user")
7884

backend/apps/owasp/admin/member_snapshot.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ class MemberSnapshotAdmin(admin.ModelAdmin):
108108
)
109109

110110
def get_queryset(self, request):
111-
"""Optimize queryset with select_related."""
111+
"""Return an optimized queryset for the MemberSnapshot admin list view.
112+
113+
Ensures related GitHub user information is loaded efficiently to
114+
avoid unnecessary database queries in the admin list view.
115+
"""
112116
queryset = super().get_queryset(request)
113117
return queryset.select_related("github_user")
114118

backend/apps/owasp/admin/mixins.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212

1313

1414
class BaseOwaspAdminMixin:
15-
"""Base mixin for OWASP admin classes providing common patterns."""
15+
"""Base mixin for OWASP admin classes.
16+
17+
Provides common configuration patterns—such as default list_display,
18+
list_filter, and search_fields—so individual ModelAdmin classes can avoid
19+
duplicated boilerplate.
20+
"""
1621

1722
# Common configuration patterns.
1823
list_display_field_names = (
@@ -26,20 +31,20 @@ class BaseOwaspAdminMixin:
2631
)
2732

2833
def get_base_list_display(self, *additional_fields):
29-
"""Get base list display with additional fields."""
34+
"""Construct a standard list_display value with optional extra fields."""
3035
return tuple(
3136
("name",) if hasattr(self.model, "name") else (),
3237
*additional_fields,
3338
*self.list_display_field_names,
3439
)
3540

3641
def get_base_search_fields(self, *additional_fields):
37-
"""Get base search fields with additional fields."""
42+
"""Construct a standard search_fields value with optional extra fields."""
3843
return self.search_field_names + additional_fields
3944

4045

4146
class EntityMemberInline(GenericTabularInline):
42-
"""EntityMember inline for admin."""
47+
"""Inline admin for EntityMember entries linking users to OWASP entities."""
4348

4449
ct_field = "entity_type"
4550
ct_fk_field = "entity_id"
@@ -63,7 +68,7 @@ class EntityMemberInline(GenericTabularInline):
6368

6469

6570
class EntityChannelInline(GenericTabularInline):
66-
"""EntityChannel inline for admin."""
71+
"""Inline admin interface for EntityChannel records associated with an entity."""
6772

6873
ct_field = "entity_type"
6974
ct_fk_field = "entity_id"
@@ -80,7 +85,11 @@ class EntityChannelInline(GenericTabularInline):
8085
ordering = ("platform", "channel_id")
8186

8287
def formfield_for_dbfield(self, db_field, request, **kwargs):
83-
"""Override to add custom widget for channel_id field and limit channel_type options."""
88+
"""Customize form widgets for EntityChannel inline fields.
89+
90+
- Uses a custom ChannelIdWidget for the channel_id field.
91+
- Limits channel_type choices to only Slack Conversation content types.
92+
"""
8493
if db_field.name == "channel_id":
8594
kwargs["widget"] = ChannelIdWidget()
8695
elif db_field.name == "channel_type":
@@ -93,14 +102,26 @@ def formfield_for_dbfield(self, db_field, request, **kwargs):
93102

94103

95104
class GenericEntityAdminMixin(BaseOwaspAdminMixin):
96-
"""Mixin for generic entity admin with common entity functionality."""
105+
"""Mixin providing common rendering logic for OWASP entity admin views.
106+
107+
Adds helpers for displaying GitHub and OWASP links and prefetches related
108+
repositories for performance.
109+
"""
97110

98111
def get_queryset(self, request):
99-
"""Get queryset with optimized relations."""
112+
"""Extend the base queryset to prefetch related repositories.
113+
114+
This reduces SQL queries when displaying GitHub-related fields.
115+
"""
100116
return super().get_queryset(request).prefetch_related("repositories")
101117

102118
def custom_field_github_urls(self, obj):
103-
"""Entity GitHub URLs with uniform formatting."""
119+
"""Render GitHub URLs for the associated entity.
120+
121+
Handles:
122+
- Entities with multiple repositories (uses obj.repositories)
123+
- Entities with a single owasp_repository field
124+
"""
104125
if not hasattr(obj, "repositories"):
105126
if not hasattr(obj, "owasp_repository") or not obj.owasp_repository:
106127
return ""
@@ -113,7 +134,7 @@ def custom_field_github_urls(self, obj):
113134
)
114135

115136
def custom_field_owasp_url(self, obj):
116-
"""Entity OWASP URL with uniform formatting."""
137+
"""Render a link to the official OWASP entity webpage."""
117138
if not hasattr(obj, "key") or not obj.key:
118139
return ""
119140

@@ -122,7 +143,7 @@ def custom_field_owasp_url(self, obj):
122143
)
123144

124145
def _format_github_link(self, repository):
125-
"""Format a single GitHub repository link."""
146+
"""Format a GitHub repository link consistently."""
126147
if not repository or not hasattr(repository, "owner") or not repository.owner:
127148
return ""
128149
if not hasattr(repository.owner, "login") or not repository.owner.login:
@@ -140,12 +161,16 @@ def _format_github_link(self, repository):
140161

141162

142163
class StandardOwaspAdminMixin(BaseOwaspAdminMixin):
143-
"""Standard mixin for simple OWASP admin classes."""
164+
"""Simple mixin for OWASP admin classes.
165+
166+
Provides convenient helpers for generating common admin config
167+
(list_display, list_filter, search_fields).
168+
"""
144169

145170
def get_common_config(
146171
self, extra_list_display=None, extra_search_fields=None, extra_list_filters=None
147172
):
148-
"""Get common admin configuration to reduce boilerplate."""
173+
"""Build a dictionary of common ModelAdmin configuration values."""
149174
config = {}
150175

151176
if extra_list_display:

backend/apps/owasp/admin/project.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ class ProjectAdmin(admin.ModelAdmin, GenericEntityAdminMixin):
5454
)
5555

5656
def custom_field_name(self, obj) -> str:
57-
"""Project custom name."""
57+
"""
58+
Return a display-friendly project name for the admin list view.
59+
60+
If the project has a defined `name`, it is shown; otherwise the project
61+
key is used as a fallback. This ensures that every project row has a
62+
readable identifier even when optional fields are empty.
63+
"""
5864
return f"{obj.name or obj.key}"
5965

6066
custom_field_name.short_description = "Name"

backend/apps/owasp/admin/project_health_metrics.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ class ProjectHealthMetricsAdmin(admin.ModelAdmin, StandardOwaspAdminMixin):
2929
search_fields = ("project__name",)
3030

3131
def project(self, obj):
32-
"""Display project name."""
32+
"""
33+
Return the name of the related project for display purposes.
34+
35+
Used in the admin list view to show a readable project label instead
36+
of the raw project foreign key reference.
37+
38+
"""
3339
return obj.project.name if obj.project else "N/A"
3440

3541

backend/apps/slack/admin/member.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ class MemberAdmin(admin.ModelAdmin):
2424
)
2525

2626
def approve_suggested_users(self, request, queryset):
27-
"""Approve all suggested users for selected members, enforcing one-to-one constraints."""
27+
"""
28+
Admin action to assign a suggested user to each selected Member.
29+
30+
For each Member:
31+
- If exactly one suggested user exists, it is assigned to the Member.
32+
- If multiple suggested users exist, an error message is returned because only one can be assigned.
33+
- If none exist, a warning message is shown.
34+
35+
"""
2836
for entity in queryset:
2937
suggestions = entity.suggested_users.all()
3038

0 commit comments

Comments
 (0)