Skip to content

Commit e7b338f

Browse files
authored
Merge pull request #635 from PROCOLLAB-github/refactor/modules
Refactor/modules
2 parents 1253798 + 143a204 commit e7b338f

18 files changed

Lines changed: 1638 additions & 85 deletions

courses/tests/helpers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
from users.models import CustomUser
2727

2828

29+
DEFAULT_MODULE_START_DATE = date(2026, 1, 1)
30+
31+
2932
@dataclass(frozen=True)
3033
class CourseTestContext:
3134
user: CustomUser
@@ -110,7 +113,7 @@ def create_module(
110113
return CourseModule.objects.create(
111114
course=course,
112115
title=title,
113-
start_date=start_date_value or date.today(),
116+
start_date=start_date_value or DEFAULT_MODULE_START_DATE,
114117
status=status,
115118
order=order,
116119
)

docs/modules/projects.md

Lines changed: 271 additions & 1 deletion
Large diffs are not rendered by default.

projects/serializers.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.contrib.auth import get_user_model
22
from django.core.cache import cache
3+
from django.core.exceptions import ValidationError as DjangoValidationError
34
from django.db import transaction
45
from rest_framework import serializers
56

@@ -216,14 +217,20 @@ def validate(self, attrs):
216217

217218
def create(self, validated_data):
218219
obj = Resource(**validated_data)
219-
obj.full_clean()
220+
try:
221+
obj.full_clean()
222+
except DjangoValidationError as exc:
223+
raise serializers.ValidationError(exc.message_dict)
220224
obj.save()
221225
return obj
222226

223227
def update(self, instance, validated_data):
224228
for key, value in validated_data.items():
225229
setattr(instance, key, value)
226-
instance.full_clean()
230+
try:
231+
instance.full_clean()
232+
except DjangoValidationError as exc:
233+
raise serializers.ValidationError(exc.message_dict)
227234
instance.save()
228235
return instance
229236

@@ -375,6 +382,7 @@ class Meta:
375382
"short_description",
376383
"image_address",
377384
"industry",
385+
"draft",
378386
"views_count",
379387
"is_company",
380388
"partner_program",

projects/signals.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,13 @@ def create_project(sender, instance, created, **kwargs):
2525
@receiver(post_save, sender=Project)
2626
def update_vacancy(sender, instance, created, **kwargs):
2727
vacancies = Vacancy.objects.filter(project=instance)
28-
old_values = vacancies.values_list("is_active", flat=True)
28+
old_values_by_id = dict(vacancies.values_list("id", "is_active"))
2929
vacancies.update(is_active=False if instance.draft else True)
30-
new_values = vacancies.values_list("is_active", flat=True)
3130

32-
vacancies_list = list(vacancies)
33-
34-
for i in range(len(new_values)):
35-
old = old_values[i]
36-
new = new_values[i]
31+
for vacancy in vacancies:
32+
old = old_values_by_id[vacancy.id]
33+
new = vacancy.is_active
3734
if old != new and new is False:
38-
delete_news_for_model(vacancies_list[i])
35+
delete_news_for_model(vacancy)
3936
elif old != new and new is True:
40-
create_news_for_model(vacancies_list[i])
37+
create_news_for_model(vacancy)

projects/tests.py

Lines changed: 0 additions & 72 deletions
This file was deleted.

projects/tests/__init__.py

Whitespace-only changes.

projects/tests/helpers.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
from dataclasses import dataclass
2+
from datetime import timedelta
3+
from uuid import uuid4
4+
5+
from django.utils import timezone
6+
7+
from industries.models import Industry
8+
from partner_programs.models import (
9+
PartnerProgram,
10+
PartnerProgramProject,
11+
PartnerProgramUserProfile,
12+
)
13+
from projects.models import (
14+
Collaborator,
15+
Company,
16+
Project,
17+
ProjectCompany,
18+
ProjectGoal,
19+
Resource,
20+
)
21+
from users.models import CustomUser
22+
from vacancy.models import Vacancy
23+
24+
25+
@dataclass(frozen=True)
26+
class ProjectTestContext:
27+
user: CustomUser
28+
project: Project
29+
30+
31+
def unique_suffix() -> str:
32+
return uuid4().hex[:8]
33+
34+
35+
def unique_digits(length: int = 10) -> str:
36+
return str(uuid4().int)[:length]
37+
38+
39+
def create_user(*, prefix: str = "projects-test") -> CustomUser:
40+
suffix = unique_suffix()
41+
return CustomUser.objects.create_user(
42+
email=f"{prefix}-{suffix}@example.com",
43+
password="testpass123",
44+
first_name="Test",
45+
last_name="User",
46+
birthday="2000-01-01",
47+
is_active=True,
48+
)
49+
50+
51+
def create_staff_user(*, prefix: str = "projects-admin") -> CustomUser:
52+
suffix = unique_suffix()
53+
return CustomUser.objects.create_superuser(
54+
email=f"{prefix}-{suffix}@example.com",
55+
password="testpass123",
56+
first_name="Admin",
57+
last_name="User",
58+
)
59+
60+
61+
def create_industry(*, name: str = "Industry") -> Industry:
62+
return Industry.objects.create(name=f"{name} {unique_suffix()}")
63+
64+
65+
def create_project(
66+
*,
67+
leader: CustomUser | None = None,
68+
name: str = "Project",
69+
description: str = "Project description",
70+
draft: bool = True,
71+
is_public: bool = True,
72+
industry: Industry | None = None,
73+
) -> Project:
74+
return Project.objects.create(
75+
leader=leader or create_user(prefix="project-leader"),
76+
name=f"{name} {unique_suffix()}",
77+
description=description,
78+
draft=draft,
79+
is_public=is_public,
80+
industry=industry or create_industry(),
81+
)
82+
83+
84+
def create_project_context(
85+
*,
86+
user_prefix: str = "projects-test",
87+
project_name: str = "Project",
88+
draft: bool = True,
89+
is_public: bool = True,
90+
) -> ProjectTestContext:
91+
user = create_user(prefix=user_prefix)
92+
project = create_project(
93+
leader=user,
94+
name=project_name,
95+
draft=draft,
96+
is_public=is_public,
97+
)
98+
return ProjectTestContext(user=user, project=project)
99+
100+
101+
def create_collaborator(
102+
project: Project,
103+
*,
104+
user: CustomUser | None = None,
105+
role: str = "Участник",
106+
specialization: str | None = None,
107+
) -> Collaborator:
108+
collaborator, _ = Collaborator.objects.get_or_create(
109+
project=project,
110+
user=user or create_user(prefix="project-collaborator"),
111+
defaults={
112+
"role": role,
113+
"specialization": specialization,
114+
},
115+
)
116+
return collaborator
117+
118+
119+
def create_partner_program(
120+
*,
121+
name: str = "Program",
122+
is_competitive: bool = False,
123+
finished: bool = False,
124+
submission_closed: bool = False,
125+
) -> PartnerProgram:
126+
suffix = unique_suffix()
127+
now = timezone.now()
128+
datetime_finished = now - timedelta(days=1) if finished else now + timedelta(days=30)
129+
registration_ends = (
130+
now - timedelta(days=1) if submission_closed else now + timedelta(days=10)
131+
)
132+
return PartnerProgram.objects.create(
133+
name=f"{name} {suffix}",
134+
tag=f"program-{suffix}",
135+
city="Moscow",
136+
is_competitive=is_competitive,
137+
datetime_registration_ends=registration_ends,
138+
datetime_started=now - timedelta(days=1),
139+
datetime_finished=datetime_finished,
140+
draft=False,
141+
)
142+
143+
144+
def add_program_member(
145+
program: PartnerProgram,
146+
user: CustomUser,
147+
*,
148+
project: Project | None = None,
149+
) -> PartnerProgramUserProfile:
150+
return PartnerProgramUserProfile.objects.create(
151+
user=user,
152+
partner_program=program,
153+
project=project,
154+
partner_program_data={},
155+
)
156+
157+
158+
def link_project_to_program(
159+
project: Project,
160+
program: PartnerProgram,
161+
*,
162+
submitted: bool = False,
163+
) -> PartnerProgramProject:
164+
return PartnerProgramProject.objects.create(
165+
project=project,
166+
partner_program=program,
167+
submitted=submitted,
168+
)
169+
170+
171+
def create_company(*, name: str = "Company", inn: str | None = None) -> Company:
172+
suffix = unique_suffix()
173+
return Company.objects.create(
174+
name=f"{name} {suffix}",
175+
inn=inn or unique_digits(10),
176+
)
177+
178+
179+
def create_project_company(
180+
project: Project,
181+
company: Company | None = None,
182+
*,
183+
contribution: str = "Contribution",
184+
decision_maker: CustomUser | None = None,
185+
) -> ProjectCompany:
186+
return ProjectCompany.objects.create(
187+
project=project,
188+
company=company or create_company(),
189+
contribution=contribution,
190+
decision_maker=decision_maker,
191+
)
192+
193+
194+
def create_resource(
195+
project: Project,
196+
*,
197+
type: str = Resource.ResourceType.INFORMATION,
198+
description: str = "Resource description",
199+
partner_company: Company | None = None,
200+
) -> Resource:
201+
if partner_company:
202+
ProjectCompany.objects.get_or_create(
203+
project=project,
204+
company=partner_company,
205+
)
206+
207+
resource = Resource(
208+
project=project,
209+
type=type,
210+
description=description,
211+
partner_company=partner_company,
212+
)
213+
resource.full_clean()
214+
resource.save()
215+
return resource
216+
217+
218+
def create_project_goal(
219+
project: Project,
220+
*,
221+
responsible: CustomUser | None = None,
222+
title: str = "Project goal",
223+
is_done: bool = False,
224+
) -> ProjectGoal:
225+
return ProjectGoal.objects.create(
226+
project=project,
227+
title=f"{title} {unique_suffix()}",
228+
responsible=responsible or project.leader,
229+
is_done=is_done,
230+
)
231+
232+
233+
def create_vacancy(
234+
project: Project,
235+
*,
236+
role: str = "Backend developer",
237+
is_active: bool = True,
238+
) -> Vacancy:
239+
return Vacancy.objects.create(
240+
project=project,
241+
role=f"{role} {unique_suffix()}",
242+
description="Vacancy description",
243+
is_active=is_active,
244+
)

0 commit comments

Comments
 (0)