Skip to content

Commit a75f1a5

Browse files
committed
Fixed migrations
2 parents ee6c7f4 + c569eca commit a75f1a5

12 files changed

Lines changed: 226 additions & 4 deletions

projects/managers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ def check_if_owns_any_projects(self, user) -> bool:
7070
# I don't think this should work but the function has no usages, so I'll let it be
7171
return user.leader_projects.exists()
7272

73+
def get_projects_from_list_of_ids(self, ids):
74+
return self.get_queryset().filter(id__in=ids)
75+
7376

7477
class AchievementManager(Manager):
7578
def get_achievements_for_list_view(self):
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.1.3 on 2023-01-21 14:14
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("projects", "0010_alter_collaborator_user"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="project",
15+
name="views_count",
16+
field=models.PositiveIntegerField(default=0),
17+
),
18+
]

projects/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ class Project(models.Model):
6262

6363
objects = ProjectManager()
6464

65+
views_count = models.PositiveIntegerField(default=0)
66+
67+
def increment_views_count(self):
68+
self.views_count += 1
69+
self.save()
70+
6571
def get_short_description(self) -> Optional[str]:
6672
return self.description[:90] if self.description else None
6773

projects/serializers.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from industries.models import Industry
55
from projects.models import Project, Achievement, Collaborator
66
from projects.validators import validate_project
7+
from users.models import LikesOnProject
78
from vacancy.serializers import ProjectVacancyListSerializer
89

910

@@ -65,6 +66,7 @@ class ProjectDetailSerializer(serializers.ModelSerializer):
6566
vacancies = ProjectVacancyListSerializer(many=True, read_only=True)
6667
short_description = serializers.SerializerMethodField()
6768
industry_id = serializers.IntegerField(required=False)
69+
likes_count = serializers.SerializerMethodField(method_name="count_likes")
6870

6971
def validate(self, data):
7072
super().validate(data)
@@ -74,6 +76,9 @@ def validate(self, data):
7476
def get_short_description(cls, project):
7577
return project.get_short_description()
7678

79+
def count_likes(self, project):
80+
return LikesOnProject.objects.filter(project=project, is_liked=True).count()
81+
7782
def update(self, instance, validated_data):
7883
instance = super().update(instance, validated_data)
7984
instance.save()
@@ -99,12 +104,20 @@ class Meta:
99104
"vacancies",
100105
"datetime_created",
101106
"datetime_updated",
107+
"views_count",
108+
"likes_count",
109+
]
110+
read_only_fields = [
111+
"leader",
112+
"views_count",
113+
"datetime_created",
114+
"datetime_updated",
102115
]
103-
read_only_fields = ["leader"]
104116

105117

106118
class ProjectListSerializer(serializers.ModelSerializer):
107119
collaborators = serializers.SerializerMethodField(method_name="get_collaborators")
120+
likes_count = serializers.SerializerMethodField(method_name="count_likes")
108121
collaborator_count = serializers.SerializerMethodField(
109122
method_name="get_collaborator_count"
110123
)
@@ -120,6 +133,9 @@ def get_short_description(cls, project):
120133
def get_collaborator_count(cls, obj):
121134
return len(obj.collaborator_set.all())
122135

136+
def count_likes(self, obj):
137+
return LikesOnProject.objects.filter(project=obj, is_liked=True).count()
138+
123139
def get_collaborators(self, obj):
124140
max_collaborator_count = 4
125141
return CollaboratorSerializer(
@@ -142,6 +158,7 @@ class Meta:
142158
"collaborators",
143159
"vacancies",
144160
"datetime_created",
161+
"likes_count",
145162
]
146163

147164
read_only_fields = [

projects/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
ProjectCountView,
1111
ProjectVacancyResponses,
1212
ProjectRecommendedUsers,
13+
SetLikeOnProject,
1314
)
1415

1516
app_name = "projects"
1617

1718
urlpatterns = [
1819
path("", ProjectList.as_view()),
20+
path("<int:pk>/like/", SetLikeOnProject.as_view()),
1921
path("<int:pk>/collaborators/", ProjectCollaborators.as_view()),
2022
path("<int:pk>/", ProjectDetail.as_view()),
2123
path("<int:pk>/recommended_users", ProjectRecommendedUsers.as_view()),

projects/views.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
AchievementDetailSerializer,
2424
ProjectCollaboratorSerializer,
2525
)
26+
from users.models import LikesOnProject
2627
from users.serializers import UserListSerializer
2728
from vacancy.models import VacancyResponse
2829
from vacancy.serializers import VacancyResponseListSerializer
@@ -83,6 +84,12 @@ class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
8384
permission_classes = [HasInvolvementInProjectOrReadOnly]
8485
serializer_class = ProjectDetailSerializer
8586

87+
def retrieve(self, request, *args, **kwargs):
88+
instance = self.get_object()
89+
instance.increment_views_count()
90+
serializer = self.get_serializer(instance)
91+
return Response(serializer.data)
92+
8693
def put(self, request, pk, **kwargs):
8794
# bootleg version of updating achievements via project
8895
if request.data.get("achievements") is not None:
@@ -116,9 +123,34 @@ def get(self, request, pk, **kwargs):
116123
return Response(status=status.HTTP_200_OK, data=serializer.data)
117124

118125

126+
class SetLikeOnProject(APIView):
127+
permission_classes = [IsAuthenticated]
128+
129+
def post(self, request, pk):
130+
"""
131+
Set like on project
132+
133+
---
134+
135+
Args:
136+
request:
137+
pk - project id
138+
139+
Returns:
140+
Response
141+
142+
"""
143+
project = Project.objects.get(pk=pk)
144+
LikesOnProject.objects.toggle_like(request.user, project)
145+
146+
return Response(ProjectListSerializer(project).data)
147+
148+
119149
class ProjectCountView(generics.GenericAPIView):
120150
queryset = Project.objects.get_projects_for_count_view()
121151
serializer_class = ProjectListSerializer
152+
# TODO: using this permission could result in a user not having verified email
153+
# creating a project; probably should make IsUserVerifiedOrReadOnly
122154
permission_classes = [permissions.IsAuthenticated]
123155

124156
def get(self, request):

users/managers.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,21 @@ def get_achievements_for_detail_view(self):
6666
.select_related("user")
6767
.only("id", "title", "status", "user")
6868
)
69+
70+
71+
class LikesOnProjectManager(Manager):
72+
def get_likes_for_list_view(self):
73+
return (
74+
self.get_queryset()
75+
.select_related("user")
76+
.only("id", "user__id", "project__id")
77+
)
78+
79+
def get_or_create(self, user, project):
80+
return super().get_or_create(user=user, project=project)
81+
82+
def toggle_like(self, user, project):
83+
like, created = self.get_or_create(user=user, project=project)
84+
if not created:
85+
like.toggle_like()
86+
return like
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Generated by Django 4.1.3 on 2023-01-21 14:45
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("projects", "0011_project_views_count"),
12+
("users", "0026_remove_member_preferred_industries_and_more"),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="LikesOnProject",
18+
fields=[
19+
(
20+
"id",
21+
models.BigAutoField(
22+
auto_created=True,
23+
primary_key=True,
24+
serialize=False,
25+
verbose_name="ID",
26+
),
27+
),
28+
("like", models.BooleanField(default=True)),
29+
(
30+
"project",
31+
models.ForeignKey(
32+
on_delete=django.db.models.deletion.CASCADE,
33+
related_name="likes",
34+
to="projects.project",
35+
),
36+
),
37+
(
38+
"user",
39+
models.ForeignKey(
40+
on_delete=django.db.models.deletion.CASCADE,
41+
related_name="likes_on_projects",
42+
to=settings.AUTH_USER_MODEL,
43+
),
44+
),
45+
],
46+
options={
47+
"verbose_name": "Лайк на проект",
48+
"verbose_name_plural": "Лайки на проекты",
49+
"unique_together": {("user", "project")},
50+
},
51+
),
52+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.1.3 on 2023-01-21 18:29
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("users", "0027_likesonproject"),
10+
]
11+
12+
operations = [
13+
migrations.RenameField(
14+
model_name="likesonproject",
15+
old_name="like",
16+
new_name="is_liked",
17+
),
18+
]

users/models.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
VERBOSE_ROLE_TYPES,
1313
VERBOSE_USER_TYPES,
1414
)
15-
from users.managers import CustomUserManager, UserAchievementManager
15+
from users.managers import (
16+
CustomUserManager,
17+
UserAchievementManager,
18+
LikesOnProjectManager,
19+
)
1620
from users.validators import user_birthday_validator
1721

1822

@@ -161,6 +165,45 @@ class Meta:
161165
abstract = True
162166

163167

168+
class LikesOnProject(models.Model):
169+
"""
170+
LikesOnProject model
171+
172+
This model is used to store the user's likes on projects.
173+
174+
Attributes:
175+
user: ForeignKey instance of user.
176+
project: ForeignKey instance of project.
177+
"""
178+
179+
is_liked = models.BooleanField(default=True)
180+
181+
user = models.ForeignKey(
182+
CustomUser,
183+
on_delete=models.CASCADE,
184+
related_name="likes_on_projects",
185+
)
186+
project = models.ForeignKey(
187+
"projects.Project",
188+
on_delete=models.CASCADE,
189+
related_name="likes",
190+
)
191+
192+
objects = LikesOnProjectManager()
193+
194+
def toggle_like(self):
195+
self.is_liked = not self.is_liked
196+
self.save()
197+
198+
def __str__(self):
199+
return f"LikesOnProject<{self.id}>"
200+
201+
class Meta:
202+
verbose_name = "Лайк на проект"
203+
verbose_name_plural = "Лайки на проекты"
204+
unique_together = ("user", "project")
205+
206+
164207
class Member(models.Model):
165208
"""
166209
Member model

0 commit comments

Comments
 (0)