diff --git a/backend/apps/github/api/internal/nodes/user.py b/backend/apps/github/api/internal/nodes/user.py index 6426a93726..e7f635cbfc 100644 --- a/backend/apps/github/api/internal/nodes/user.py +++ b/backend/apps/github/api/internal/nodes/user.py @@ -52,45 +52,47 @@ def created_at(self, root: User) -> float: """Resolve created at.""" return root.idx_created_at - @strawberry_django.field + @strawberry_django.field(select_related=["owasp_profile"]) def first_owasp_contribution_at(self, root: User) -> float | None: """Resolve first OWASP contribution date.""" - if hasattr(root, "owasp_profile") and root.owasp_profile.first_contribution_at: - return root.owasp_profile.first_contribution_at.timestamp() - return None + return ( + root.owasp_profile.first_contribution_at.timestamp() + if hasattr(root, "owasp_profile") and root.owasp_profile.first_contribution_at + else None + ) - @strawberry_django.field + @strawberry_django.field(select_related=["owasp_profile"]) def is_owasp_board_member(self, root: User) -> bool: """Resolve if member is currently on OWASP Board of Directors.""" - if hasattr(root, "owasp_profile"): - return root.owasp_profile.is_owasp_board_member - return False + return ( + root.owasp_profile.is_owasp_board_member if hasattr(root, "owasp_profile") else False + ) - @strawberry_django.field + @strawberry_django.field(select_related=["owasp_profile"]) def is_former_owasp_staff(self, root: User) -> bool: """Resolve if member is a former OWASP staff member.""" - if hasattr(root, "owasp_profile"): - return root.owasp_profile.is_former_owasp_staff - return False + return ( + root.owasp_profile.is_former_owasp_staff if hasattr(root, "owasp_profile") else False + ) - @strawberry_django.field + @strawberry_django.field(select_related=["owasp_profile"]) def is_gsoc_mentor(self, root: User) -> bool: """Resolve if member is a Google Summer of Code mentor.""" - if hasattr(root, "owasp_profile"): - return root.owasp_profile.is_gsoc_mentor - return False + return root.owasp_profile.is_gsoc_mentor if hasattr(root, "owasp_profile") else False @strawberry_django.field def issues_count(self, root: User) -> int: """Resolve issues count.""" return root.idx_issues_count - @strawberry_django.field + @strawberry_django.field(select_related=["owasp_profile"]) def linkedin_page_id(self, root: User) -> str: """Resolve LinkedIn page ID.""" - if hasattr(root, "owasp_profile") and root.owasp_profile.linkedin_page_id: - return root.owasp_profile.linkedin_page_id - return "" + return ( + root.owasp_profile.linkedin_page_id + if hasattr(root, "owasp_profile") and root.owasp_profile.linkedin_page_id + else "" + ) @strawberry_django.field def releases_count(self, root: User) -> int: diff --git a/backend/apps/github/api/internal/queries/issue.py b/backend/apps/github/api/internal/queries/issue.py index 99441cada6..c1c077c2b3 100644 --- a/backend/apps/github/api/internal/queries/issue.py +++ b/backend/apps/github/api/internal/queries/issue.py @@ -15,28 +15,7 @@ class IssueQuery: """GraphQL query class for retrieving GitHub issues.""" - @strawberry_django.field( - select_related=[ - "author__owasp_profile", - "author__user_badges__badge", - "level", - "milestone__author__owasp_profile", - "milestone__author__user_badges__badge", - "milestone__repository__organization", - "repository__organization", - ], - prefetch_related=[ - "assignees__owasp_profile", - "assignees__user_badges__badge", - "labels", - "participant_interests__user__user_badges", - "pull_requests__author__user_badges__badge", - "pull_requests__labels", - "pull_requests__milestone__author__user_badges", - "pull_requests__milestone__repository__organization", - "pull_requests__repository__organization", - ], - ) + @strawberry_django.field def recent_issues( self, *, diff --git a/backend/apps/github/api/internal/queries/milestone.py b/backend/apps/github/api/internal/queries/milestone.py index 2806c017d7..bb87fab3a4 100644 --- a/backend/apps/github/api/internal/queries/milestone.py +++ b/backend/apps/github/api/internal/queries/milestone.py @@ -25,15 +25,7 @@ class MilestoneStateEnum(str, enum.Enum): class MilestoneQuery: """Github Milestone Queries.""" - @strawberry_django.field( - select_related=["author__owasp_profile", "repository__organization"], - prefetch_related=[ - "issues__author__user_badges__badge", - "labels", - "pull_requests__repository__organization", - "pull_requests__author__user_badges__badge", - ], - ) + @strawberry_django.field def recent_milestones( self, *, diff --git a/backend/apps/github/api/internal/queries/pull_request.py b/backend/apps/github/api/internal/queries/pull_request.py index d81e98dd3c..d001331003 100644 --- a/backend/apps/github/api/internal/queries/pull_request.py +++ b/backend/apps/github/api/internal/queries/pull_request.py @@ -16,26 +16,7 @@ class PullRequestQuery: """Pull request queries.""" - @strawberry_django.field( - select_related=[ - "author__owasp_profile", - "author__user_badges__badge", - "milestone__author__owasp_profile", - "milestone__author__user_badges__badge", - "repository__organization", - "milestone__repository__organization", - ], - prefetch_related=[ - "assignees__owasp_profile", - "assignees__user_badges__badge", - "labels", - "related_issues__repository__organization", - "related_issues__assignees__user_badges__badge", - "related_issues__labels", - "related_issues__milestone__author", - "related_issues__level", - ], - ) + @strawberry_django.field def recent_pull_requests( self, *, diff --git a/backend/apps/github/api/internal/queries/release.py b/backend/apps/github/api/internal/queries/release.py index 9cc5f8ead4..928d78a9a8 100644 --- a/backend/apps/github/api/internal/queries/release.py +++ b/backend/apps/github/api/internal/queries/release.py @@ -15,13 +15,7 @@ class ReleaseQuery: """GraphQL query class for retrieving recent GitHub releases.""" - @strawberry_django.field( - select_related=[ - "author__owasp_profile", - "author__user_badges__badge", - "repository__organization", - ] - ) + @strawberry_django.field def recent_releases( self, *, diff --git a/backend/apps/github/api/internal/queries/repository.py b/backend/apps/github/api/internal/queries/repository.py index 9ca451c25e..70a0a033b8 100644 --- a/backend/apps/github/api/internal/queries/repository.py +++ b/backend/apps/github/api/internal/queries/repository.py @@ -37,9 +37,7 @@ def repository( except Repository.DoesNotExist: return None - @strawberry_django.field( - select_related=["organization", "owner__owasp_profile", "owner__user_badges__badge"], - ) + @strawberry_django.field def repositories( self, organization: str, diff --git a/backend/apps/github/api/internal/queries/user.py b/backend/apps/github/api/internal/queries/user.py index 41b4fa0c02..d99df0de43 100644 --- a/backend/apps/github/api/internal/queries/user.py +++ b/backend/apps/github/api/internal/queries/user.py @@ -37,9 +37,7 @@ def top_contributed_repositories( .order_by("-contributions_count") ] - @strawberry_django.field( - select_related=["owasp_profile"], prefetch_related=["user_badges__badge"] - ) + @strawberry_django.field def user( self, login: str, diff --git a/backend/apps/owasp/api/internal/nodes/common.py b/backend/apps/owasp/api/internal/nodes/common.py index 5cb9645322..2a631800f6 100644 --- a/backend/apps/owasp/api/internal/nodes/common.py +++ b/backend/apps/owasp/api/internal/nodes/common.py @@ -11,7 +11,7 @@ class GenericEntityNode(strawberry.relay.Node): """Base node class for OWASP entities with common fields and resolvers.""" - @strawberry_django.field + @strawberry_django.field(prefetch_related=["entity_members__member"]) def entity_leaders(self, root) -> list[EntityMemberNode]: """Resolve entity leaders.""" return root.entity_leaders diff --git a/backend/apps/owasp/api/internal/queries/chapter.py b/backend/apps/owasp/api/internal/queries/chapter.py index 7b6089efec..64ecfd7f41 100644 --- a/backend/apps/owasp/api/internal/queries/chapter.py +++ b/backend/apps/owasp/api/internal/queries/chapter.py @@ -21,18 +21,7 @@ def chapter(self, key: str) -> ChapterNode | None: except Chapter.DoesNotExist: return None - @strawberry_django.field( - select_related=[ - "owasp_repository__organization", - "owasp_repository__owner__owasp_profile", - ], - prefetch_related=[ - "leaders__user_badges__badge", - "suggested_leaders__user_badges__badge", - "members__user_badges__badge", - "entity_leaders__member__user_badges__badge", - ], - ) + @strawberry_django.field def recent_chapters(self, limit: int = 8) -> list[ChapterNode]: """Resolve recent chapters.""" return ( diff --git a/backend/apps/owasp/api/internal/queries/member_snapshot.py b/backend/apps/owasp/api/internal/queries/member_snapshot.py index 453ede1490..4aac59e04e 100644 --- a/backend/apps/owasp/api/internal/queries/member_snapshot.py +++ b/backend/apps/owasp/api/internal/queries/member_snapshot.py @@ -45,10 +45,7 @@ def member_snapshot( except User.DoesNotExist: return None - @strawberry_django.field( - select_related=["github_user__owasp_profile", "github_user__user_badges__badge"], - prefetch_related=["issues", "pull_requests", "messages"], - ) + @strawberry_django.field def member_snapshots( self, user_login: str | None = None, limit: int = 10 ) -> list[MemberSnapshotNode]: diff --git a/backend/apps/owasp/api/internal/queries/project.py b/backend/apps/owasp/api/internal/queries/project.py index 7b9ce7f8d2..af4db3c009 100644 --- a/backend/apps/owasp/api/internal/queries/project.py +++ b/backend/apps/owasp/api/internal/queries/project.py @@ -34,19 +34,7 @@ def project(self, key: str) -> ProjectNode | None: except Project.DoesNotExist: return None - @strawberry_django.field( - select_related=[ - "owasp_repository__organization", - "owasp_repository__owner__owasp_profile", - "owasp_repository__owner__user_badges__badge", - ], - prefetch_related=[ - "organizations", - "owners", - "repositories__organization", - "entity_leaders__member", - ], - ) + @strawberry_django.field def recent_projects(self, limit: int = 8) -> list[ProjectNode]: """Resolve recent projects. @@ -63,19 +51,7 @@ def recent_projects(self, limit: int = 8) -> list[ProjectNode]: else [] ) - @strawberry_django.field( - select_related=[ - "owasp_repository__organization", - "owasp_repository__owner__owasp_profile", - "owasp_repository__owner__user_badges__badge", - ], - prefetch_related=[ - "entity_leaders__member", - "organizations", - "owners", - "repositories__organization", - ], - ) + @strawberry_django.field def search_projects(self, query: str) -> list[ProjectNode]: """Search active projects by name (case-insensitive, partial match).""" cleaned_query = query.strip() diff --git a/backend/apps/owasp/api/internal/queries/snapshot.py b/backend/apps/owasp/api/internal/queries/snapshot.py index 219e691ef6..d649acad34 100644 --- a/backend/apps/owasp/api/internal/queries/snapshot.py +++ b/backend/apps/owasp/api/internal/queries/snapshot.py @@ -24,15 +24,7 @@ def snapshot(self, key: str) -> SnapshotNode | None: except Snapshot.DoesNotExist: return None - @strawberry_django.field( - prefetch_related=[ - "new_chapters", - "new_issues", - "new_projects", - "new_releases", - "new_users", - ], - ) + @strawberry_django.field def snapshots(self, limit: int = 12) -> list[SnapshotNode]: """Resolve snapshots.""" return ( diff --git a/backend/apps/owasp/models/common.py b/backend/apps/owasp/models/common.py index 1bc18bb0b2..e339048675 100644 --- a/backend/apps/owasp/models/common.py +++ b/backend/apps/owasp/models/common.py @@ -9,6 +9,7 @@ from urllib.parse import urlparse import yaml +from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models @@ -90,20 +91,18 @@ class Meta: blank=True, ) + # GRs. + entity_members = GenericRelation( + EntityMember, + content_type_field="entity_type", + object_id_field="entity_id", + related_query_name="entity", + ) + @cached_property def entity_leaders(self) -> list[EntityMember]: """Return entity's leaders.""" - return list( - EntityMember.objects.filter( - entity_id=self.id, - entity_type=ContentType.objects.get_for_model(self.__class__), - is_active=True, - is_reviewed=True, - role=EntityMember.Role.LEADER, - ) - .select_related("member") - .order_by("order") - ) + return self.entity_members.filter(role=EntityMember.Role.LEADER).order_by("order") @property def github_url(self) -> str: diff --git a/backend/tests/apps/owasp/api/internal/nodes/common_test.py b/backend/tests/apps/owasp/api/internal/nodes/common_test.py index 59f222c2fd..0592ea71ff 100644 --- a/backend/tests/apps/owasp/api/internal/nodes/common_test.py +++ b/backend/tests/apps/owasp/api/internal/nodes/common_test.py @@ -15,7 +15,7 @@ def test_entity_leaders_resolver(self): mock_entity = Mock() mock_entity.entity_leaders = [mock_leader1, mock_leader2] - result = GenericEntityNode.entity_leaders(None, mock_entity) + result = GenericEntityNode().entity_leaders(mock_entity) assert result == [mock_leader1, mock_leader2]