Skip to content

Commit 96b7bd0

Browse files
authored
Merge pull request #192 from PROCOLLAB-github/dev
Реализован новый модуль "Траектории"
2 parents 6180180 + 921e157 commit 96b7bd0

17 files changed

Lines changed: 853 additions & 60 deletions

File tree

apps/courses/serializers.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ class Meta:
8484
)
8585

8686

87+
class SkillNameAndLogoSerializer(serializers.ModelSerializer):
88+
file_link = serializers.URLField(source="file.link")
89+
90+
class Meta:
91+
model = Skill
92+
fields = ("name", "file_link")
93+
94+
8795
class TaskResult(DataclassSerializer):
8896
class Meta:
8997
dataclass = TaskResultData
@@ -98,29 +106,30 @@ class Meta:
98106

99107

100108
class SkillDetailsSerializer(serializers.ModelSerializer):
101-
skill_name = serializers.CharField(source="name")
102-
file = serializers.SerializerMethodField()
109+
name = serializers.CharField()
110+
file_link = serializers.SerializerMethodField()
103111
skill_preview = serializers.SerializerMethodField()
104112
skill_point_logo = serializers.SerializerMethodField()
105-
level = serializers.SerializerMethodField()
113+
quantity_of_levels = serializers.SerializerMethodField()
106114

107115
class Meta:
108116
model = Skill
109117
fields = (
110-
"skill_name",
111-
"file",
118+
"id",
119+
"name",
120+
"file_link",
112121
"skill_preview",
113122
"skill_point_logo",
114123
"description",
115-
"level",
124+
"quantity_of_levels",
116125
"free_access",
117126
)
118127

119-
def get_level(self, obj) -> int:
128+
def get_quantity_of_levels(self, obj) -> int:
120129
# Просьба захардкодить на 1 уровень везде.
121130
return 1
122131

123-
def get_file(self, obj) -> str | None:
132+
def get_file_link(self, obj) -> str | None:
124133
return obj.file.link if obj.file else None
125134

126135
def get_skill_preview(self, obj) -> str | None:

apps/procollab_skills/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"questions",
7373
"subscription",
7474
"webinars",
75+
"trajectories",
7576
]
7677

7778
MIDDLEWARE = [

apps/procollab_skills/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
path("progress/", include("progress.urls")),
3232
path("subscription/", include("subscription.urls")),
3333
path("webinars/", include("webinars.urls")),
34+
path("trajectories/", include("trajectories.urls")),
3435
]
3536

3637
if settings.DEBUG:

apps/progress/views/profile.py

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,7 @@ class UserChooseSkills(generics.UpdateAPIView):
7979

8080
def update(self, request, *args, **kwargs):
8181
try:
82-
skills = (
83-
Skill.published
84-
.for_user(self.request.user)
85-
.filter(id__in=request.data)
86-
)
82+
skills = Skill.published.for_user(self.request.user).filter(id__in=request.data)
8783

8884
IntermediateUserSkills.objects.bulk_create(
8985
[IntermediateUserSkills(user_profile=self.user_profile, skill=skill) for skill in skills]
@@ -153,6 +149,7 @@ def get(self, request, *args, **kwargs) -> Response:
153149
serialized_data = self.serializer_class(user).data
154150

155151
serialized_data["verification_date"] = self._get_date_verificated()
152+
serialized_data["is_mentor"] = user.mentored_trajectories.exists()
156153

157154
return Response(serialized_data, status=status.HTTP_200_OK)
158155

@@ -161,37 +158,26 @@ class SyncUserProfile(generics.GenericAPIView):
161158
"""
162159
API-эндпоинт для синхронизации данных пользователя между сервисами Skills и Procollab.
163160
"""
161+
164162
permission_classes = [permissions.IsAuthenticated]
165163

166164
@extend_schema(
167165
summary="Синхронизация данных профиля skills с сервисом procollab",
168166
responses={
169-
status.HTTP_200_OK: OpenApiResponse(
170-
description="Данные успешно синхронизированы",
171-
examples={
172-
"application/json": {
173-
"message": "Данные успешно синхронизированы"
174-
}
175-
}
176-
),
177-
status.HTTP_400_BAD_REQUEST: OpenApiResponse(
178-
description="Ошибка при получении данных",
179-
examples={
180-
"application/json": {
181-
"error": "Ошибка получения данных от procollab"
182-
}
183-
}
184-
),
185-
status.HTTP_401_UNAUTHORIZED: OpenApiResponse(
186-
description="Неавторизованный доступ",
187-
examples={
188-
"application/json": {
189-
"error": "Неавторизованный доступ"
190-
}
191-
}
192-
)
193-
},
194-
tags=["Профиль"]
167+
status.HTTP_200_OK: OpenApiResponse(
168+
description="Данные успешно синхронизированы",
169+
examples={"application/json": {"message": "Данные успешно синхронизированы"}},
170+
),
171+
status.HTTP_400_BAD_REQUEST: OpenApiResponse(
172+
description="Ошибка при получении данных",
173+
examples={"application/json": {"error": "Ошибка получения данных от procollab"}},
174+
),
175+
status.HTTP_401_UNAUTHORIZED: OpenApiResponse(
176+
description="Неавторизованный доступ",
177+
examples={"application/json": {"error": "Неавторизованный доступ"}},
178+
),
179+
},
180+
tags=["Профиль"],
195181
)
196182
def post(self, request: HttpRequest, *args, **kwargs) -> Response:
197183
"""
@@ -217,10 +203,7 @@ def _fetch_procollab_data(self, email: str) -> dict:
217203
email (str): Email пользователя для поиска в Procollab
218204
"""
219205
url_name = "dev" if settings.DEBUG else "api"
220-
response = requests.get(
221-
f"https://{url_name}.procollab.ru/auth/users/clone-data",
222-
data={"email": email}
223-
)
206+
response = requests.get(f"https://{url_name}.procollab.ru/auth/users/clone-data", data={"email": email})
224207

225208
if response.status_code != status.HTTP_200_OK:
226209
raise ValueError("Ошибка получения данных от procollab")
@@ -242,12 +225,12 @@ def _update_user_profile(self, user: CustomUser, data: dict) -> None:
242225
- Аватар профиля (если предоставлен)
243226
"""
244227
user_fields = {
245-
'first_name': data["first_name"],
246-
'last_name': data["last_name"],
247-
'patronymic': data["patronymic"],
248-
'city': data["city"],
249-
'age': data["birthday"],
250-
'specialization': data["speciality"]
228+
"first_name": data["first_name"],
229+
"last_name": data["last_name"],
230+
"patronymic": data["patronymic"],
231+
"city": data["city"],
232+
"age": data["birthday"],
233+
"specialization": data["speciality"],
251234
}
252235
for field, value in user_fields.items():
253236
setattr(user, field, value)
@@ -261,7 +244,7 @@ def _update_user_profile(self, user: CustomUser, data: dict) -> None:
261244
"user": user,
262245
"name": "avatar",
263246
"extension": avatar_url.split(".")[-1],
264-
}
247+
},
265248
)
266249
user_profile.file = file_instance
267250
user_profile.save()
@@ -270,7 +253,4 @@ def _handle_error(self, error: Exception) -> Response:
270253
"""
271254
Обработчик ошибок для эндпоинта синхронизации.
272255
"""
273-
return Response(
274-
{"error": str(error)},
275-
status=status.HTTP_400_BAD_REQUEST
276-
)
256+
return Response({"error": str(error)}, status=status.HTTP_400_BAD_REQUEST)

apps/tests/courses_tests/constants.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,24 +121,26 @@
121121

122122
# Response по /courses/skill-details/1:
123123
SKILL_DETAILS_RESPONSE_NEW_SUB = {
124-
"skill_name": "Навык 1",
125-
"file": "http://some.com/",
124+
"id": 1,
125+
"name": "Навык 1",
126+
"file_link": "http://some.com/",
126127
"skill_preview": "http://some.com/",
127128
"skill_point_logo": "http://some.com/",
128129
"description": "Описание",
129130
"free_access": False,
130-
"level": 1
131+
"quantity_of_levels": 1
131132
}
132133

133134
# Response по /courses/skill-details/1:
134135
SKILL_DETAILS_RESPONSE_FREE = {
135-
"skill_name": "Навык 1",
136-
"file": "http://some.com/",
136+
"id": 1,
137+
"name": "Навык 1",
138+
"file_link": "http://some.com/",
137139
"skill_preview": "http://some.com/",
138140
"skill_point_logo": "http://some.com/",
139141
"description": "Описание",
140142
"free_access": True,
141-
"level": 1
143+
"quantity_of_levels": 1
142144
}
143145

144146

apps/trajectories/__init__.py

Whitespace-only changes.

apps/trajectories/admin.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from django.contrib import admin
2+
3+
from .models import Meeting, Month, Trajectory, UserTrajectory
4+
5+
6+
@admin.register(Trajectory)
7+
class TrajectoryAdmin(admin.ModelAdmin):
8+
list_display = (
9+
"id",
10+
"name",
11+
"company",
12+
"start_date",
13+
"duration_months",
14+
"background_color",
15+
"button_color",
16+
"select_button_color",
17+
"text_color",
18+
)
19+
list_filter = ["company", "start_date"]
20+
search_fields = ["name", "company"]
21+
22+
23+
@admin.register(Month)
24+
class MonthAdmin(admin.ModelAdmin):
25+
list_display = (
26+
"id",
27+
"trajectory",
28+
"skills_list",
29+
)
30+
search_fields = ["trajectory__name"]
31+
32+
def skills_list(self, obj):
33+
return ", ".join(skill.name for skill in obj.skills.all())
34+
35+
skills_list.short_description = "Навыки"
36+
37+
38+
@admin.register(UserTrajectory)
39+
class UserTrajectoryAdmin(admin.ModelAdmin):
40+
list_display = (
41+
"id",
42+
"user",
43+
"trajectory_name",
44+
"start_date",
45+
"is_active",
46+
"mentor",
47+
)
48+
list_filter = ["is_active", "trajectory"]
49+
search_fields = ["user__email", "trajectory__name"]
50+
51+
def trajectory_name(self, obj):
52+
return obj.trajectory.name
53+
54+
trajectory_name.short_description = "Траектория"
55+
56+
57+
@admin.register(Meeting)
58+
class MeetingAdmin(admin.ModelAdmin):
59+
list_display = (
60+
"id",
61+
"user_trajectory_user_email",
62+
"initial_meeting",
63+
"final_meeting",
64+
)
65+
list_filter = ["initial_meeting", "final_meeting"]
66+
search_fields = ["user_trajectory__user__email"]
67+
68+
def user_trajectory_user_email(self, obj):
69+
return obj.user_trajectory.user.email
70+
71+
user_trajectory_user_email.short_description = "Пользователь"

apps/trajectories/apps.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.apps import AppConfig
2+
3+
4+
class TrajectoriesConfig(AppConfig):
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "trajectories"
7+
verbose_name = "Траектории"

0 commit comments

Comments
 (0)