Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions backend/apps/github/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ github-enrich-issues:
@echo "Enriching GitHub issues"
@CMD="python manage.py github_enrich_issues" $(MAKE) exec-backend-command

github-match-users:
@CMD="python manage.py github_match_users $(MATCH_MODEL)" $(MAKE) exec-backend-command

github-update-owasp-organization:
@echo "Updating OWASP GitHub organization"
@CMD="python manage.py github_update_owasp_organization" $(MAKE) exec-backend-command
Expand Down
131 changes: 0 additions & 131 deletions backend/apps/github/management/commands/github_match_users.py

This file was deleted.

3 changes: 3 additions & 0 deletions backend/apps/owasp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ owasp-process-snapshots:
@echo "Processing OWASP snapshots"
@CMD="python manage.py owasp_process_snapshots" $(MAKE) exec-backend-command

owasp-update-leaders:
@CMD="python manage.py owasp_update_leaders $(MATCH_MODEL)" $(MAKE) exec-backend-command

owasp-update-project-health-metrics:
@echo "Updating OWASP project health metrics"
@CMD="python manage.py owasp_update_project_health_metrics" $(MAKE) exec-backend-command
Expand Down
1 change: 1 addition & 0 deletions backend/apps/owasp/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from .chapter import ChapterAdmin
from .committee import CommitteeAdmin
from .entity_member import EntityMemberAdmin
from .event import EventAdmin
from .post import PostAdmin
from .project import ProjectAdmin
Expand Down
6 changes: 3 additions & 3 deletions backend/apps/owasp/admin/chapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

from apps.owasp.models.chapter import Chapter

from .mixins import GenericEntityAdminMixin, LeaderAdminMixin
from .mixins import EntityMemberInline, GenericEntityAdminMixin


class ChapterAdmin(admin.ModelAdmin, GenericEntityAdminMixin, LeaderAdminMixin):
class ChapterAdmin(admin.ModelAdmin, GenericEntityAdminMixin):
"""Admin for Chapter model."""

autocomplete_fields = ("owasp_repository",)
filter_horizontal = LeaderAdminMixin.filter_horizontal
inlines = (EntityMemberInline,)
list_display = (
"name",
"created_at",
Expand Down
11 changes: 4 additions & 7 deletions backend/apps/owasp/admin/committee.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@

from apps.owasp.models.committee import Committee

from .mixins import GenericEntityAdminMixin, LeaderAdminMixin
from .mixins import EntityMemberInline, GenericEntityAdminMixin


class CommitteeAdmin(admin.ModelAdmin, GenericEntityAdminMixin, LeaderAdminMixin):
class CommitteeAdmin(admin.ModelAdmin, GenericEntityAdminMixin):
"""Admin for Committee model."""

autocomplete_fields = (
"leaders",
"owasp_repository",
)
filter_horizontal = LeaderAdminMixin.filter_horizontal
autocomplete_fields = ("owasp_repository",)
inlines = (EntityMemberInline,)
search_fields = ("name",)


Expand Down
91 changes: 91 additions & 0 deletions backend/apps/owasp/admin/entity_member.py
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please restore bulk approval action functionality.

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""EntityMember admin configuration."""

from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.urls import reverse
from django.utils.html import format_html

from apps.owasp.models.chapter import Chapter
from apps.owasp.models.committee import Committee
from apps.owasp.models.entity_member import EntityMember
from apps.owasp.models.project import Project


class EntityMemberAdmin(admin.ModelAdmin):
"""Admin for EntityMember records (generic link to any OWASP entity)."""

autocomplete_fields = ("member",)
fields = (
"entity_type",
"entity_id",
"member",
"kind",
"order",
"is_reviewed",
"description",
)
list_display = (
"member",
"entity",
"kind",
"is_reviewed",
"order",
)
list_filter = (
"kind",
"is_reviewed",
)
raw_id_fields = ("member",)
search_fields = (
"member__login",
"member__name",
"description",
)
ordering = ("member__name", "order",)

@admin.display(description="Entity", ordering="entity_type")
def entity(self, obj):
"""Return entity link."""
if obj.entity:
link = reverse(
f"admin:{obj.entity_type.app_label}_{obj.entity_type.model}_change",
args=[obj.entity_id],
)
return format_html('<a href="{}">{}</a>', link, obj.entity)
return "— No Associated Entity —"

def get_search_results(self, request, queryset, search_term):
"""Get search results from entity name or key."""
queryset, use_distinct = super().get_search_results(request, queryset, search_term)

if search_term:
project_ids = Project.objects.filter(
Q(name__icontains=search_term) | Q(key__icontains=search_term)
).values_list("pk", flat=True)

chapter_ids = Chapter.objects.filter(
Q(name__icontains=search_term) | Q(key__icontains=search_term)
).values_list("pk", flat=True)

committee_ids = Committee.objects.filter(
Q(name__icontains=search_term) | Q(key__icontains=search_term)
).values_list("pk", flat=True)

project_ct = ContentType.objects.get_for_model(Project)
chapter_ct = ContentType.objects.get_for_model(Chapter)
committee_ct = ContentType.objects.get_for_model(Committee)

entity_match_query = (
Q(entity_type=project_ct, entity_id__in=list(project_ids))
| Q(entity_type=chapter_ct, entity_id__in=list(chapter_ids))
| Q(entity_type=committee_ct, entity_id__in=list(committee_ids))
)

queryset |= self.model.objects.filter(entity_match_query)
use_distinct = True

return queryset, use_distinct


admin.site.register(EntityMember, EntityMemberAdmin)
59 changes: 24 additions & 35 deletions backend/apps/owasp/admin/mixins.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""OWASP admin mixins for common functionality."""

from django.contrib import messages
from django.contrib.contenttypes.admin import GenericTabularInline
from django.utils.html import escape
from django.utils.safestring import mark_safe

from apps.owasp.models.entity_member import EntityMember


class BaseOwaspAdminMixin:
"""Base mixin for OWASP admin classes providing common patterns."""
Expand Down Expand Up @@ -32,6 +34,27 @@ def get_base_search_fields(self, *additional_fields):
return self.search_field_names + additional_fields


class EntityMemberInline(GenericTabularInline):
"""EntityMember inline for admin."""

ct_field = "entity_type"
ct_fk_field = "entity_id"
extra = 1
fields = (
"member",
"kind",
"description",
"order",
"is_reviewed",
)
model = EntityMember
ordering = (
"order",
"member__login",
)
raw_id_fields = ("member",)


class GenericEntityAdminMixin(BaseOwaspAdminMixin):
"""Mixin for generic entity admin with common entity functionality."""

Expand Down Expand Up @@ -79,40 +102,6 @@ def _format_github_link(self, repository):
custom_field_owasp_url.short_description = "OWASP 🔗"


class LeaderAdminMixin(BaseOwaspAdminMixin):
"""Admin mixin for entities that can have leaders."""

actions = ("approve_suggested_leaders",)
filter_horizontal = (
"leaders",
"suggested_leaders",
)

def approve_suggested_leaders(self, request, queryset):
"""Approve suggested leaders for selected entities."""
total_approved = 0
for entity in queryset:
suggestions = entity.suggested_leaders.all()
if count := suggestions.count():
entity.leaders.add(*suggestions)
total_approved += count
entity_name = entity.name if hasattr(entity, "name") else str(entity)
self.message_user(
request,
f"Approved {count} leader suggestions for {entity_name}",
messages.SUCCESS,
)

if total_approved:
self.message_user(
request,
f"Total approved suggestions: {total_approved}",
messages.INFO,
)

approve_suggested_leaders.short_description = "Approve suggested leaders"


class StandardOwaspAdminMixin(BaseOwaspAdminMixin):
"""Standard mixin for simple OWASP admin classes."""

Expand Down
6 changes: 3 additions & 3 deletions backend/apps/owasp/admin/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

from apps.owasp.models.project import Project

from .mixins import GenericEntityAdminMixin, LeaderAdminMixin
from .mixins import EntityMemberInline, GenericEntityAdminMixin


class ProjectAdmin(admin.ModelAdmin, GenericEntityAdminMixin, LeaderAdminMixin):
class ProjectAdmin(admin.ModelAdmin, GenericEntityAdminMixin):
"""Admin for Project model."""

autocomplete_fields = (
Expand All @@ -16,7 +16,7 @@ class ProjectAdmin(admin.ModelAdmin, GenericEntityAdminMixin, LeaderAdminMixin):
"owners",
"repositories",
)
filter_horizontal = LeaderAdminMixin.filter_horizontal
inlines = (EntityMemberInline,)
list_display = (
"custom_field_name",
"created_at",
Expand Down
Loading
Loading