diff --git a/projects/managers.py b/projects/managers.py index 2aca6843..7f5d2568 100644 --- a/projects/managers.py +++ b/projects/managers.py @@ -7,34 +7,38 @@ class ProjectManager(Manager): def get_projects_for_list_view(self): - return self.get_queryset().prefetch_related( - Prefetch( - "industry", - queryset=Industry.objects.only("name").all(), - ), - Prefetch( - "leader", - queryset=CustomUser.objects.only("id").all(), - ), - # Prefetch( - # "collaborator_set", - # queryset=Collaborator.objects.filter( - # id__in=Subquery( - # Collaborator.objects - # .filter(project_id=OuterRef('project_id')).values_list('id', flat=True)[:4] - # ) - # ), - # to_attr='collaborators' - # ), - # Yes, this fetches the entire collaborator_set even though we only need 4 and the total count. - # No, You can't do it any other way than this. - # Above is a hack that can fetch max 4 vacancies, but if you use it the count will always be <=4. - # To get the count right using the thing above you either have to make another godawful hack, - # Or override the default QuerySet to always ask the DB only count after all the filters. - # (ticket referring to the reason why you can't - # prefetch N items easily https://code.djangoproject.com/ticket/26780) - # (seems like in django 4.2.0 it'll be fixed but at the time of writing the latest version is 4.1.3 - Prefetch("collaborator_set"), + return ( + self.get_queryset() + .filter(draft=False) + .prefetch_related( + Prefetch( + "industry", + queryset=Industry.objects.only("name").all(), + ), + Prefetch( + "leader", + queryset=CustomUser.objects.only("id").all(), + ), + # Prefetch( + # "collaborator_set", + # queryset=Collaborator.objects.filter( + # id__in=Subquery( + # Collaborator.objects + # .filter(project_id=OuterRef('project_id')).values_list('id', flat=True)[:4] + # ) + # ), + # to_attr='collaborators' + # ), + # Yes, this fetches the entire collaborator_set even though we only need 4 and the total count. + # No, You can't do it any other way than this. + # Above is a hack that can fetch max 4 vacancies, but if you use it the count will always be <=4. + # To get the count right using the thing above you either have to make another godawful hack, + # Or override the default QuerySet to always ask the DB only count after all the filters. + # (ticket referring to the reason why you can't + # prefetch N items easily https://code.djangoproject.com/ticket/26780) + # (seems like in django 4.2.0 it'll be fixed but at the time of writing the latest version is 4.1.3 + Prefetch("collaborator_set"), + ) ) def get_projects_for_detail_view(self): @@ -42,6 +46,9 @@ def get_projects_for_detail_view(self): self.get_queryset().prefetch_related("achievements", "collaborator_set").all() ) + def get_projects_for_count_view(self): + return self.get_queryset().filter(draft=False).only("id") + def check_if_owns_any_projects(self, user) -> bool: # I don't think this should work but the function has no usages, so I'll let it be return user.leader_projects.exists() diff --git a/projects/urls.py b/projects/urls.py index c042e236..09beb211 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -8,6 +8,7 @@ AchievementList, AchievementDetail, ProjectCollaborators, + ProjectCountView, ) app_name = "projects" @@ -16,6 +17,7 @@ path("", ProjectList.as_view()), path("/collaborators", ProjectCollaborators.as_view()), path("/", ProjectDetail.as_view()), + path("count/", ProjectCountView.as_view()), path("steps/", ProjectSteps.as_view()), path("achievements/", AchievementList.as_view()), path("achievements//", AchievementDetail.as_view()), diff --git a/projects/views.py b/projects/views.py index 4db5aec6..d381ca1f 100644 --- a/projects/views.py +++ b/projects/views.py @@ -1,5 +1,5 @@ from django_filters import rest_framework as filters -from rest_framework import generics, permissions +from rest_framework import generics, permissions, status from rest_framework.response import Response from rest_framework.views import APIView @@ -73,6 +73,17 @@ class ProjectDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = [IsProjectLeaderOrReadOnly] +class ProjectCountView(generics.GenericAPIView): + queryset = Project.objects.get_projects_for_count_view() + serializer_class = ProjectListSerializer + # TODO: using this permission could result in a user not having verified email + # creating a project; probably should make IsUserVerifiedOrReadOnly + permission_classes = [permissions.IsAuthenticatedOrReadOnly] + + def get(self, request): + return Response({"count": self.get_queryset().count()}, status=status.HTTP_200_OK) + + class ProjectCollaborators(generics.GenericAPIView): """ Project collaborator retrieve/add/delete view