Skip to content

Commit 1f8115a

Browse files
authored
Merge pull request #241 from PROCOLLAB-github/dev
Dev
2 parents 025f4e2 + 5e1430a commit 1f8115a

7 files changed

Lines changed: 213 additions & 46 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 4.2.3 on 2023-11-14 20:07
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11+
("projects", "0020_project_cover_defaultprojectcover"),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name="project",
17+
name="subscribers",
18+
field=models.ManyToManyField(
19+
related_name="subscribed_projects",
20+
to=settings.AUTH_USER_MODEL,
21+
verbose_name="Подписчики",
22+
),
23+
),
24+
]

projects/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ class Project(models.Model):
107107
blank=True,
108108
)
109109

110+
subscribers = models.ManyToManyField(
111+
User, verbose_name="Подписчики", related_name="subscribed_projects"
112+
)
113+
110114
datetime_created = models.DateTimeField(
111115
verbose_name="Дата создания", null=False, auto_now_add=True
112116
)

projects/serializers.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from django.contrib.auth import get_user_model
22
from rest_framework import serializers
3-
3+
from django.core.cache import cache
44
from core.fields import CustomListField
55
from core.services import get_views_count, get_likes_count, is_fan
6+
from core.utils import get_user_online_cache_key
67
from files.serializers import UserFileSerializer
78
from industries.models import Industry
89
from projects.models import Project, Achievement, Collaborator, ProjectNews
@@ -275,3 +276,26 @@ class Meta:
275276
"is_user_liked",
276277
"files",
277278
]
279+
280+
281+
class ProjectSubscribersListSerializer(serializers.ModelSerializer):
282+
is_online = serializers.SerializerMethodField()
283+
284+
def get_is_online(self, user: User) -> bool:
285+
request = self.context.get("request")
286+
if request and request.user.is_authenticated and request.user.id == user.id:
287+
return True
288+
289+
cache_key = get_user_online_cache_key(user)
290+
is_online = cache.get(cache_key, False)
291+
return is_online
292+
293+
class Meta:
294+
model = User
295+
fields = [
296+
"id",
297+
"first_name",
298+
"last_name",
299+
"avatar",
300+
"is_online",
301+
]

projects/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
ProjectVacancyResponses,
1313
ProjectRecommendedUsers,
1414
SetLikeOnProject,
15+
ProjectSubscribe,
16+
ProjectUnsubscribe,
17+
ProjectSubscribers,
1518
)
1619

1720
app_name = "projects"
@@ -20,6 +23,9 @@
2023
path("", ProjectList.as_view()),
2124
path("<int:pk>/like/", SetLikeOnProject.as_view()),
2225
path("<int:project_pk>/news/", NewsList.as_view()),
26+
path("<int:project_pk>/subscribe/", ProjectSubscribe.as_view()),
27+
path("<int:project_pk>/unsubscribe/", ProjectUnsubscribe.as_view()),
28+
path("<int:project_pk>/subscribers/", ProjectSubscribers.as_view()),
2329
path("<int:project_pk>/news/<int:pk>/", NewsDetail.as_view()),
2430
path("<int:project_pk>/news/<int:pk>/set_viewed/", NewsDetailSetViewed.as_view()),
2531
path("<int:project_pk>/news/<int:pk>/set_liked/", NewsDetailSetLiked.as_view()),

projects/views.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import logging
2+
13
from django.contrib.auth import get_user_model
24
from django.db.models import Q
35
from django_filters import rest_framework as filters
6+
from drf_yasg import openapi
7+
from drf_yasg.utils import swagger_auto_schema
48
from rest_framework import generics, permissions, status
9+
from rest_framework.exceptions import NotFound
510
from rest_framework.permissions import IsAuthenticated
611
from rest_framework.response import Response
712
from rest_framework.views import APIView
@@ -33,12 +38,15 @@
3338
ProjectCollaboratorSerializer,
3439
ProjectNewsListSerializer,
3540
ProjectNewsDetailSerializer,
41+
ProjectSubscribersListSerializer,
3642
)
3743
from users.models import LikesOnProject
3844
from users.serializers import UserListSerializer
3945
from vacancy.models import VacancyResponse
4046
from vacancy.serializers import VacancyResponseListSerializer
4147

48+
logger = logging.getLogger()
49+
4250
User = get_user_model()
4351

4452

@@ -381,3 +389,75 @@ def post(self, request, *args, **kwargs):
381389
return Response(status=status.HTTP_200_OK)
382390
except ProjectNews.DoesNotExist:
383391
return Response(status=status.HTTP_404_NOT_FOUND)
392+
393+
394+
class ProjectSubscribers(APIView):
395+
permission_classes = [IsAuthenticated]
396+
397+
@swagger_auto_schema(
398+
responses={
399+
200: openapi.Response(
400+
"List of project subscribers", ProjectSubscribersListSerializer(many=True)
401+
)
402+
}
403+
)
404+
def get(self, request, *args, **kwargs):
405+
try:
406+
project = Project.objects.get(pk=self.kwargs["project_pk"])
407+
except Project.DoesNotExist:
408+
raise NotFound
409+
subscribers = ProjectSubscribersListSerializer(
410+
project.subscribers.all(), many=True
411+
).data
412+
return Response(subscribers, status=status.HTTP_200_OK)
413+
414+
415+
class ProjectSubscribe(APIView):
416+
permission_classes = [IsAuthenticated]
417+
418+
def post(self, request, project_pk):
419+
try:
420+
project = Project.objects.get(pk=project_pk)
421+
except Project.DoesNotExist:
422+
raise NotFound
423+
try:
424+
project.subscribers.add(request.user)
425+
except Exception:
426+
return Response(
427+
{
428+
"detail": f"User {request.user.id} is not part of project {project.pk}."
429+
},
430+
status=status.HTTP_400_BAD_REQUEST,
431+
)
432+
433+
logger.info(f"User {request.user.id} subscribed to project {project_pk}")
434+
435+
return Response(
436+
{"detail": "Subscriber was successfully added"}, status=status.HTTP_200_OK
437+
)
438+
439+
440+
class ProjectUnsubscribe(APIView):
441+
permission_classes = [IsAuthenticated]
442+
443+
def post(self, request, project_pk):
444+
try:
445+
project = Project.objects.get(pk=project_pk)
446+
except Project.DoesNotExist:
447+
raise NotFound
448+
try:
449+
project.subscribers.remove(request.user)
450+
# todo: add more specific error here
451+
except Exception:
452+
return Response(
453+
{
454+
"detail": f"User {request.user.id} is not part of project {project.pk}."
455+
},
456+
status=status.HTTP_400_BAD_REQUEST,
457+
)
458+
459+
logger.info(f"User {request.user.id} unsubscribed to project {project_pk}")
460+
461+
return Response(
462+
{"detail": "Subscriber was successfully removed"}, status=status.HTTP_200_OK
463+
)

users/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ class CustomUser(AbstractUser):
9696
verbose_name="Стадия онбординга",
9797
help_text="0, 1, 2 - номера стадий онбординга, null(пустое) - онбординг пройден",
9898
)
99+
99100
verification_date = models.DateField(
100101
null=True,
101102
blank=True,
102103
verbose_name="Дата верификации",
103104
)
104-
105105
datetime_updated = models.DateTimeField(auto_now=True)
106106
datetime_created = models.DateTimeField(auto_now_add=True)
107107

users/serializers.py

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,76 @@ class Meta:
7979
]
8080

8181

82+
class UserProjectsSerializer(serializers.ModelSerializer):
83+
short_description = serializers.SerializerMethodField()
84+
views_count = serializers.SerializerMethodField()
85+
collaborator = serializers.SerializerMethodField(method_name="get_collaborator")
86+
87+
def get_collaborator(self, project: Project):
88+
# TODO: fix me, import in a functon
89+
from projects.serializers import CollaboratorSerializer
90+
91+
user = (
92+
self.context.get("request").user
93+
if self.context.get("user") is None
94+
else self.context.get("user")
95+
)
96+
try:
97+
collaborator = project.collaborator_set.get(user=user)
98+
except Collaborator.DoesNotExist:
99+
return {}
100+
101+
return CollaboratorSerializer(collaborator).data
102+
103+
@classmethod
104+
def get_views_count(cls, project):
105+
return get_views_count(project)
106+
107+
@classmethod
108+
def get_short_description(cls, project):
109+
return project.get_short_description()
110+
111+
class Meta:
112+
model = Project
113+
fields = [
114+
"id",
115+
"name",
116+
"leader",
117+
"short_description",
118+
"image_address",
119+
"industry",
120+
"views_count",
121+
"collaborator",
122+
]
123+
read_only_fields = ["leader", "collaborator"]
124+
125+
126+
class UserSubscribedProjectsSerializer(serializers.ModelSerializer):
127+
short_description = serializers.SerializerMethodField()
128+
views_count = serializers.SerializerMethodField()
129+
130+
@classmethod
131+
def get_views_count(cls, project):
132+
return get_views_count(project)
133+
134+
@classmethod
135+
def get_short_description(cls, project):
136+
return project.get_short_description()
137+
138+
class Meta:
139+
model = Project
140+
fields = [
141+
"id",
142+
"name",
143+
"leader",
144+
"short_description",
145+
"image_address",
146+
"industry",
147+
"views_count",
148+
]
149+
read_only_fields = ["leader", "collaborator"]
150+
151+
82152
class UserDetailSerializer(serializers.ModelSerializer):
83153
member = MemberSerializer(required=False)
84154
investor = InvestorSerializer(required=False)
@@ -89,6 +159,8 @@ class UserDetailSerializer(serializers.ModelSerializer):
89159
links = serializers.SerializerMethodField()
90160
is_online = serializers.SerializerMethodField()
91161
projects = serializers.SerializerMethodField()
162+
# inline serializer with fields name, id, image_address, source is self.subscribed_projects
163+
subscribed_projects = UserSubscribedProjectsSerializer(many=True, read_only=True)
92164

93165
def get_projects(self, user: CustomUser):
94166
return UserProjectsSerializer(
@@ -138,6 +210,7 @@ class Meta:
138210
"verification_date",
139211
"onboarding_stage",
140212
"projects",
213+
"subscribed_projects",
141214
]
142215

143216
def update(self, instance, validated_data):
@@ -211,50 +284,6 @@ def update(self, instance, validated_data):
211284
return instance
212285

213286

214-
class UserProjectsSerializer(serializers.ModelSerializer):
215-
short_description = serializers.SerializerMethodField()
216-
views_count = serializers.SerializerMethodField()
217-
collaborator = serializers.SerializerMethodField(method_name="get_collaborator")
218-
219-
def get_collaborator(self, project: Project):
220-
# TODO: fix me, import in a functon
221-
from projects.serializers import CollaboratorSerializer
222-
223-
user = (
224-
self.context.get("request").user
225-
if self.context.get("user") is None
226-
else self.context.get("user")
227-
)
228-
try:
229-
collaborator = project.collaborator_set.get(user=user)
230-
except Collaborator.DoesNotExist:
231-
return {}
232-
233-
return CollaboratorSerializer(collaborator).data
234-
235-
@classmethod
236-
def get_views_count(cls, project):
237-
return get_views_count(project)
238-
239-
@classmethod
240-
def get_short_description(cls, project):
241-
return project.get_short_description()
242-
243-
class Meta:
244-
model = Project
245-
fields = [
246-
"id",
247-
"name",
248-
"leader",
249-
"short_description",
250-
"image_address",
251-
"industry",
252-
"views_count",
253-
"collaborator",
254-
]
255-
read_only_fields = ["leader", "collaborator"]
256-
257-
258287
class UserListSerializer(serializers.ModelSerializer):
259288
member = MemberSerializer(required=False)
260289
key_skills = KeySkillsField(required=False)

0 commit comments

Comments
 (0)