Skip to content

Commit def0360

Browse files
committed
Реализована ежедневная автопубликация проектов после завершения программы
1 parent a00a7e7 commit def0360

5 files changed

Lines changed: 169 additions & 2 deletions

File tree

partner_programs/services.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,48 @@
33

44
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
55
from django.db.models import Prefetch
6+
from django.utils import timezone
67

78
from partner_programs.models import (
9+
PartnerProgram,
810
PartnerProgramField,
911
PartnerProgramFieldValue,
1012
PartnerProgramProject,
1113
PartnerProgramUserProfile,
1214
)
1315
from project_rates.models import Criteria, ProjectScore
16+
from projects.models import Project
1417

1518
logger = logging.getLogger()
1619

1720

21+
def publish_finished_program_projects(now=None) -> int:
22+
if now is None:
23+
now = timezone.now()
24+
25+
program_ids = PartnerProgram.objects.filter(
26+
publish_projects_after_finish=True,
27+
datetime_finished__lte=now,
28+
).values_list("id", flat=True)
29+
if not program_ids.exists():
30+
return 0
31+
32+
link_project_ids = PartnerProgramProject.objects.filter(
33+
partner_program_id__in=program_ids
34+
).values_list("project_id", flat=True)
35+
profile_project_ids = PartnerProgramUserProfile.objects.filter(
36+
partner_program_id__in=program_ids,
37+
project_id__isnull=False,
38+
).values_list("project_id", flat=True)
39+
project_ids = link_project_ids.union(profile_project_ids)
40+
41+
return Project.objects.filter(
42+
id__in=project_ids,
43+
is_public=False,
44+
draft=False,
45+
).update(is_public=True)
46+
47+
1848
class ProjectScoreDataPreparer:
1949
"""
2050
Data preparer about project_rates by experts.

partner_programs/tasks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import logging
2+
3+
from procollab.celery import app
4+
from partner_programs.services import publish_finished_program_projects
5+
6+
logger = logging.getLogger(__name__)
7+
8+
9+
@app.task
10+
def publish_finished_program_projects_task() -> int:
11+
updated_count = publish_finished_program_projects()
12+
logger.info("Published %s program projects after finish", updated_count)
13+
return updated_count

partner_programs/tests.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
from django.contrib.auth import get_user_model
12
from django.test import TestCase
23
from django.utils import timezone
34

4-
from partner_programs.models import PartnerProgram, PartnerProgramField
5+
from partner_programs.models import (
6+
PartnerProgram,
7+
PartnerProgramField,
8+
PartnerProgramProject,
9+
PartnerProgramUserProfile,
10+
)
511
from partner_programs.serializers import PartnerProgramFieldValueUpdateSerializer
12+
from partner_programs.services import publish_finished_program_projects
13+
from projects.models import Project
614

715

816
class PartnerProgramFieldValueUpdateSerializerInvalidTests(TestCase):
@@ -118,6 +126,117 @@ def test_file_empty_required(self):
118126
self.assertIn("Файл обязателен для этого поля.", str(serializer.errors))
119127

120128

129+
class PublishFinishedProgramProjectsTests(TestCase):
130+
def setUp(self):
131+
self.now = timezone.now()
132+
self.user = get_user_model().objects.create_user(
133+
email="user@example.com",
134+
password="pass",
135+
first_name="User",
136+
last_name="Test",
137+
birthday="1990-01-01",
138+
)
139+
140+
def create_program(self, **overrides):
141+
defaults = {
142+
"name": "Program",
143+
"tag": "program_tag",
144+
"description": "Program description",
145+
"city": "Moscow",
146+
"image_address": "https://example.com/image.png",
147+
"cover_image_address": "https://example.com/cover.png",
148+
"advertisement_image_address": "https://example.com/advertisement.png",
149+
"presentation_address": "https://example.com/presentation.pdf",
150+
"data_schema": {},
151+
"draft": False,
152+
"projects_availability": "all_users",
153+
"datetime_registration_ends": self.now - timezone.timedelta(days=5),
154+
"datetime_started": self.now - timezone.timedelta(days=30),
155+
"datetime_finished": self.now - timezone.timedelta(days=1),
156+
}
157+
defaults.update(overrides)
158+
return PartnerProgram.objects.create(**defaults)
159+
160+
def create_project(self, **overrides):
161+
defaults = {
162+
"leader": self.user,
163+
"draft": False,
164+
"is_public": False,
165+
"name": "Project",
166+
}
167+
defaults.update(overrides)
168+
return Project.objects.create(**defaults)
169+
170+
def test_publish_updates_projects_from_both_sources(self):
171+
program = self.create_program(publish_projects_after_finish=True)
172+
173+
link_project = self.create_project(name="Linked Project")
174+
PartnerProgramProject.objects.create(
175+
partner_program=program,
176+
project=link_project,
177+
)
178+
179+
profile_project = self.create_project(name="Profile Project")
180+
PartnerProgramUserProfile.objects.create(
181+
user=self.user,
182+
partner_program=program,
183+
project=profile_project,
184+
partner_program_data={},
185+
)
186+
187+
publish_finished_program_projects()
188+
189+
link_project.refresh_from_db()
190+
profile_project.refresh_from_db()
191+
self.assertTrue(link_project.is_public)
192+
self.assertTrue(profile_project.is_public)
193+
194+
def test_publish_skips_draft_projects(self):
195+
program = self.create_program(publish_projects_after_finish=True)
196+
draft_project = self.create_project(draft=True, name="Draft Project")
197+
PartnerProgramProject.objects.create(
198+
partner_program=program,
199+
project=draft_project,
200+
)
201+
202+
publish_finished_program_projects()
203+
204+
draft_project.refresh_from_db()
205+
self.assertFalse(draft_project.is_public)
206+
207+
def test_publish_skips_when_flag_false(self):
208+
program = self.create_program(publish_projects_after_finish=False)
209+
project = self.create_project(name="Private Project")
210+
PartnerProgramProject.objects.create(
211+
partner_program=program,
212+
project=project,
213+
)
214+
215+
publish_finished_program_projects()
216+
217+
project.refresh_from_db()
218+
self.assertFalse(project.is_public)
219+
220+
def test_publish_after_flag_enabled_post_finish(self):
221+
program = self.create_program(publish_projects_after_finish=False)
222+
project = self.create_project(name="Delayed Project")
223+
PartnerProgramProject.objects.create(
224+
partner_program=program,
225+
project=project,
226+
)
227+
228+
publish_finished_program_projects()
229+
project.refresh_from_db()
230+
self.assertFalse(project.is_public)
231+
232+
program.publish_projects_after_finish = True
233+
program.save(update_fields=["publish_projects_after_finish"])
234+
235+
publish_finished_program_projects()
236+
project.refresh_from_db()
237+
self.assertTrue(project.is_public)
238+
239+
121240
class PartnerProgramFieldValueUpdateSerializerValidTests(TestCase):
122241
def setUp(self):
123242
now = timezone.now()

procollab/celery.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
"task": "vacancy.tasks.email_notificate_vacancy_outdated",
1818
# "schedule": crontab(minute=0, hour=0),
1919
"schedule": crontab(minute="*"),
20-
}
20+
},
21+
"publish_finished_program_projects": {
22+
"task": "partner_programs.tasks.publish_finished_program_projects_task",
23+
"schedule": crontab(minute=0, hour=6),
24+
},
2125
}
2226

2327
if __name__ == "__main__":

procollab/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,4 @@
418418
CELERY_ACCEPT_CONTENT = ["application/json"]
419419
CELERY_RESULT_SERIALIZER = "json"
420420
CELERY_TASK_SERIALIZER = "json"
421+
CELERY_TIMEZONE = "Europe/Moscow"

0 commit comments

Comments
 (0)