Skip to content
Merged

Dev #572

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
83c30c1
Добавлены фильрты для выдачи новостей, чтобы избежать битых ссылок
Toksi86 Nov 5, 2025
566a5ff
Merge pull request #565 from PROCOLLAB-github/fix/empty_feeds
Toksi86 Nov 5, 2025
7257869
Расширена информация о программах в проектах, исправлена ошибка битых…
Toksi86 Nov 5, 2025
41a87c0
Merge pull request #566 from PROCOLLAB-github/feature/addotinal_field…
Toksi86 Nov 5, 2025
5de22ae
Добавлена фильтрация программ по пользователю
Toksi86 Nov 6, 2025
f30400a
Скорректирована выдача приглашений согласно бизнес-логике
Toksi86 Nov 6, 2025
f384285
Merge pull request #567 from PROCOLLAB-github/feature/filter_for_program
Toksi86 Nov 6, 2025
712b420
Исправлены тесты в связи с новой логикой отображения приглашений
Toksi86 Nov 6, 2025
cf8a4ef
Merge pull request #568 from PROCOLLAB-github/fix/user_invite
Toksi86 Nov 6, 2025
67fe217
Вернул фильтрацию по юзеру, из-за сломаных приглашений
Toksi86 Nov 6, 2025
386936c
Merge pull request #569 from PROCOLLAB-github/feature/invite-filter-a…
Toksi86 Nov 6, 2025
172b6e1
Добавлен ID эксперта в данные об оценённых проектах
Toksi86 Nov 10, 2025
3c78c63
Merge pull request #570 from PROCOLLAB-github/feature/add-scored-expe…
Toksi86 Nov 10, 2025
8c6a091
Исправлен результат поля scored_expert_id, теперь возвращает корректн…
Toksi86 Nov 11, 2025
fc691d1
Merge pull request #571 from PROCOLLAB-github/feature/add-scored-expe…
Toksi86 Nov 11, 2025
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
11 changes: 11 additions & 0 deletions feed/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ def get_queryset(self) -> QuerySet[News]:
)
.order_by("-datetime_created")
)

existing_object_filters = {
"project": Project.objects.values_list("id", flat=True),
"vacancy": Vacancy.objects.values_list("id", flat=True),
}
for model_name, ids_queryset in existing_object_filters.items():
queryset = queryset.exclude(
Q(content_type__model=model_name)
& ~Q(object_id__in=ids_queryset)
)

return queryset

def get(self, *args, **kwargs):
Expand Down
14 changes: 5 additions & 9 deletions invites/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@ class InviteFilter(filters.FilterSet):
"""

def __init__(self, *args, **kwargs):
"""if user filter is not passed, default to request.user"""
super().__init__(*args, **kwargs)
if self.data.get("user") is None:
# default filtering by current user
self.data = dict(self.data)
self.data["user"] = kwargs.get("request").user.id
# if user == "any", remove the filter
if self.data.get("user") == "any":
self.data = dict(self.data)
self.data.pop("user")
self.data = dict(self.data)
request = kwargs.get("request")
if request and request.user.is_authenticated:
self.data["user"] = request.user.id

project = filters.Filter(method=project_id_filter)
user = filters.NumberFilter(field_name="user_id")

class Meta:
model = Invite
Expand Down
1 change: 1 addition & 0 deletions invites/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Meta:
"specialization",
"is_accepted",
]
read_only_fields = ["is_accepted"]

def validate(self, attrs):
project = attrs["project"]
Expand Down
3 changes: 1 addition & 2 deletions invites/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ def setUp(self) -> None:
"user": None,
"motivational_letter": "hello",
"role": "Developer",
"is_accepted": False,
}

self.project_create_data = {
Expand Down Expand Up @@ -59,7 +58,7 @@ def test_invites_creation(self):
)
self.assertEqual(response.data["project"]["id"], create_user["project"])
self.assertEqual(response.data["role"], create_user["role"])
self.assertEqual(response.data["is_accepted"], create_user["is_accepted"])
self.assertIsNone(response.data["is_accepted"])

def test_invites_creation_with_empty_text(self):
user_main = self._user_create("example@gmail.com")
Expand Down
25 changes: 21 additions & 4 deletions invites/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


class InviteList(generics.ListCreateAPIView):
queryset = Invite.objects.get_invite_for_list_view()
queryset = Invite.objects.get_invite_for_list_view().filter(is_accepted__isnull=True)
serializer_class = InviteDetailSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = (filters.DjangoFilterBackend,)
Expand Down Expand Up @@ -47,13 +47,30 @@ def post(self, request, *args, **kwargs):
invite = self.get_object() # type: Invite
if invite.user != request.user:
return Response(status=status.HTTP_403_FORBIDDEN)
if invite.is_accepted is True:
return Response(
{"detail": "Invite has already been accepted."},
status=status.HTTP_409_CONFLICT,
)
if invite.is_accepted is False:
return Response(
{"detail": "Invite has already been declined."},
status=status.HTTP_409_CONFLICT,
)
# add user to project collaborators
Collaborator.objects.create(
collaborator, created = Collaborator.objects.get_or_create(
user=invite.user,
project=invite.project,
role=invite.role,
specialization=invite.specialization,
defaults={
"role": invite.role,
"specialization": invite.specialization,
},
)
if not created:
return Response(
{"detail": "User is already a collaborator of this project."},
status=status.HTTP_409_CONFLICT,
)
invite.is_accepted = True
invite.save()
return Response(status=status.HTTP_200_OK)
Expand Down
2 changes: 2 additions & 0 deletions partner_programs/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def count_views(self, program):
return get_views_count(program)

def get_short_description(self, program):
if not program.description:
return ""
return program.description[:125]

def get_is_user_liked(self, obj):
Expand Down
18 changes: 18 additions & 0 deletions partner_programs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ class PartnerProgramList(generics.ListCreateAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
pagination_class = PartnerProgramPagination

def get_queryset(self):
base_qs = super().get_queryset()
participating_flag = self.request.query_params.get("participating")
if not participating_flag:
return base_qs

if not self.request.user.is_authenticated:
return PartnerProgram.objects.none()

now = timezone.now()
return (
base_qs.filter(
partner_program_profiles__user=self.request.user,
datetime_finished__gte=now,
)
.distinct()
)


class PartnerProgramDetail(generics.RetrieveAPIView):
queryset = PartnerProgram.objects.prefetch_related("materials", "managers").all()
Expand Down
2 changes: 2 additions & 0 deletions project_rates/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class ProjectListForRateSerializer(serializers.ModelSerializer):
views_count = serializers.SerializerMethodField()
criterias = serializers.SerializerMethodField()
scored = serializers.SerializerMethodField()
scored_expert_id = serializers.IntegerField(read_only=True, allow_null=True)

class Meta:
model = Project
Expand All @@ -75,6 +76,7 @@ class Meta:
"region",
"views_count",
"scored",
"scored_expert_id",
"criterias",
]

Expand Down
11 changes: 9 additions & 2 deletions project_rates/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.contrib.auth import get_user_model
from django.db.models import QuerySet, Count
from django.db.models import QuerySet, Count, OuterRef, Subquery, IntegerField

from rest_framework import generics, status
from rest_framework.response import Response
Expand Down Expand Up @@ -98,6 +98,13 @@ def get_queryset(self) -> QuerySet[Project]:
project__isnull=False, partner_program__id=self.kwargs.get("program_id")
).values_list("project__id", flat=True)
# `Count` the easiest way to check for rate exist (0 -> does not exist).
scored_expert_subquery = ProjectScore.objects.filter(
project=OuterRef("pk")
).values("user_id")[:1]

return Project.objects.filter(draft=False, id__in=projects_ids).annotate(
scored=Count("scores")
scored=Count("scores"),
scored_expert_id=Subquery(
scored_expert_subquery, output_field=IntegerField()
),
)
12 changes: 10 additions & 2 deletions projects/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@ def get_draft_projects_for_user(self):

class ProjectManager(Manager):
def get_projects_for_list_view(self):
return self.get_queryset().filter(draft=False).prefetch_related("program_links")
return (
self.get_queryset()
.filter(draft=False)
.prefetch_related("program_links__partner_program")
)

def get_user_projects_for_list_view(self):
return self.get_queryset().distinct()
return (
self.get_queryset()
.prefetch_related("program_links__partner_program")
.distinct()
)

def get_projects_for_detail_view(self):
return (
Expand Down
28 changes: 22 additions & 6 deletions projects/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ class Meta:
class ProjectListSerializer(serializers.ModelSerializer):
views_count = serializers.SerializerMethodField(method_name="count_views")
short_description = serializers.SerializerMethodField()
partner_program_id = serializers.SerializerMethodField()
partner_program = serializers.SerializerMethodField()

@classmethod
def count_views(cls, project):
Expand All @@ -345,12 +345,23 @@ def get_short_description(cls, project):
return project.get_short_description()

@staticmethod
def get_partner_program_id(project):
def _get_program_link(project):
links_cache = getattr(project, "_prefetched_objects_cache", {}).get(
"program_links"
)
link = links_cache[0] if links_cache else project.program_links.first()
return link.partner_program_id if link else None
if links_cache:
return links_cache[0]
return project.program_links.select_related("partner_program").first()

@classmethod
def get_partner_program(cls, project):
link = cls._get_program_link(project)
if link and link.partner_program:
return {
"id": link.partner_program_id,
"name": link.partner_program.name,
}
return None

class Meta:
model = Project
Expand All @@ -363,10 +374,15 @@ class Meta:
"industry",
"views_count",
"is_company",
"partner_program_id",
"partner_program",
]

read_only_fields = ["leader", "views_count", "is_company", "partner_program_id"]
read_only_fields = [
"leader",
"views_count",
"is_company",
"partner_program",
]

def is_valid(self, *, raise_exception=False):
return super().is_valid(raise_exception=raise_exception)
Expand Down
19 changes: 18 additions & 1 deletion users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ class ResendVerifyEmailSerializer(serializers.Serializer):
class UserProjectListSerializer(serializers.ModelSerializer[Project]):
views_count = serializers.SerializerMethodField(method_name="count_views")
short_description = serializers.SerializerMethodField()
partner_program = serializers.SerializerMethodField()

@classmethod
def count_views(cls, project):
Expand All @@ -949,6 +950,21 @@ def count_views(cls, project):
def get_short_description(cls, project):
return project.get_short_description()

@staticmethod
def get_partner_program(project):
links_cache = getattr(project, "_prefetched_objects_cache", {}).get(
"program_links"
)
link = links_cache[0] if links_cache else project.program_links.select_related(
"partner_program"
).first()
if link and link.partner_program:
return {
"id": link.partner_program_id,
"name": link.partner_program.name,
}
return None

class Meta:
model = Project
fields = [
Expand All @@ -961,9 +977,10 @@ class Meta:
"views_count",
"draft",
"is_company",
"partner_program",
]

read_only_fields = ["leader", "views_count", "is_company"]
read_only_fields = ["leader", "views_count", "is_company", "partner_program"]

def is_valid(self, *, raise_exception=False):
return super().is_valid(raise_exception=raise_exception)
Expand Down
9 changes: 7 additions & 2 deletions users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,13 @@ class UserProjectsList(GenericAPIView):
serializer_class = UserProjectListSerializer

def get(self, request):
self.queryset = Project.objects.get_user_projects_for_list_view().filter(
Q(leader_id=self.request.user.id) | Q(collaborator__user=self.request.user)
self.queryset = (
Project.objects.filter(
Q(leader_id=self.request.user.id)
| Q(collaborator__user=self.request.user)
)
.prefetch_related("program_links__partner_program")
.distinct()
)

page = self.paginate_queryset(self.queryset)
Expand Down