Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
70e252a
Обновление модели проекта
Toksi86 Sep 1, 2025
ef7eeaa
Переработана модель Проекта
Toksi86 Sep 3, 2025
44b2d84
Merge pull request #541 from PROCOLLAB-github/feature/update_program_…
Toksi86 Sep 3, 2025
2399010
Переработана старница Проекта в адм. панели
Toksi86 Sep 4, 2025
99ce799
Merge pull request #542 from PROCOLLAB-github/feature/update_program_…
Toksi86 Sep 4, 2025
0b5055a
Добалвены уровни доступов при приглашении в проект и заявке на участи…
Toksi86 Sep 5, 2025
66be8f6
Merge pull request #543 from PROCOLLAB-github/feature/invite-program-…
Toksi86 Sep 5, 2025
9499c6d
Реализованы цели для проекта
Toksi86 Sep 8, 2025
93368bb
Merge branch 'dev' into feature/addotional_project_fields
Toksi86 Sep 8, 2025
04726df
Merge pull request #544 from PROCOLLAB-github/feature/addotional_proj…
Toksi86 Sep 8, 2025
7e61a1f
Добавлен эндпоинт, который принимает массив целей в POST /projects/{p…
Toksi86 Oct 1, 2025
9c8c6ad
Merge pull request #545 from PROCOLLAB-github/fix/project_goals_bulk_…
Toksi86 Oct 1, 2025
888d4fe
Созданы "Компании", "Ресурсы", "КомпанииПроекта"
Toksi86 Oct 9, 2025
f300026
Merge pull request #546 from PROCOLLAB-github/feature/company_for_pro…
Toksi86 Oct 9, 2025
0bac2da
Исправленаа ошибка доступа при созданию компаний
Toksi86 Oct 9, 2025
d2239be
Удалён лишний комментарий
Toksi86 Oct 9, 2025
b1603be
Merge pull request #547 from PROCOLLAB-github/feature/company_for_pro…
Toksi86 Oct 9, 2025
202b119
При GET запросе исправлено названия возвращаемого поля project на pro…
Toksi86 Oct 9, 2025
ea544ed
Merge pull request #548 from PROCOLLAB-github/feature/company_for_pro…
Toksi86 Oct 9, 2025
8362d09
Nginx контейнер убран из docker-compose; Проброшен порт
Toksi86 Oct 15, 2025
b9ff1fc
Merge pull request #549 from PROCOLLAB-github/fix/nginx.conf
Toksi86 Oct 15, 2025
d2cc740
Код приведён в соответствие с PEP8
Toksi86 Oct 15, 2025
2b83e93
Добавлена ручка для выгрузки данных проектов в программах
Toksi86 Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions docker-compose.dev-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ services:
- .env
environment:
HOST: 0.0.0.0
expose:
- 8000
ports:
- 8000:8000

grafana:
image: grafana/grafana:latest
Expand All @@ -37,13 +37,13 @@ services:
- prom-data:/prometheus
- ./prometheus:/etc/prometheus

nginx:
restart: unless-stopped
build: ./nginx
depends_on:
- web
ports:
- 8000:80
#nginx:
# restart: unless-stopped
# build: ./nginx
# depends_on:
# - web
# ports:
# - 8000:80

loki:
image: grafana/loki:2.9.0
Expand Down
44 changes: 44 additions & 0 deletions invites/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.apps import apps
from rest_framework import serializers

from invites.models import Invite
from projects.models import Collaborator
from projects.serializers import ProjectListSerializer
from users.serializers import UserDetailSerializer

Expand All @@ -18,6 +20,48 @@ class Meta:
"is_accepted",
]

def validate(self, attrs):
project = attrs["project"]
user = attrs["user"]

if project.leader_id == user.id:
raise serializers.ValidationError(
{"user": "Пользователь уже является лидером проекта."}
)

if Collaborator.objects.filter(project=project, user=user).exists():
raise serializers.ValidationError(
{"user": "Пользователь уже состоит в проекте."}
)

if Invite.objects.filter(
project=project, user=user, is_accepted__isnull=True
).exists():
raise serializers.ValidationError(
{"user": "У пользователя уже есть активное приглашение в этот проект."}
)

link = project.program_links.select_related("partner_program").first()
if link:
PartnerProgramUserProfile = apps.get_model(
"partner_programs", "PartnerProgramUserProfile"
)
is_participant = PartnerProgramUserProfile.objects.filter(
user_id=user.id,
partner_program_id=link.partner_program_id,
).exists()
if not is_participant:
raise serializers.ValidationError(
{
"user": (
"Нельзя пригласить пользователя: проект относится к программе, "
"а пользователь не является её участником."
)
}
)

return attrs


class InviteDetailSerializer(serializers.ModelSerializer[Invite]):
user = UserDetailSerializer(many=False, read_only=True)
Expand Down
1 change: 0 additions & 1 deletion invites/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def setUp(self) -> None:
"name": "Test",
"description": "Test",
"industry": Industry.objects.create(name="Test").id,
"step": 1,
"draft": False,
}

Expand Down
4 changes: 1 addition & 3 deletions partner_programs/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ class PartnerProgramForMemberSerializer(PartnerProgramBaseSerializerMixin):

views_count = serializers.SerializerMethodField(method_name="count_views")
links = serializers.SerializerMethodField(method_name="get_links")
is_user_manager = serializers.SerializerMethodField(
method_name="get_is_user_manager"
)
is_user_manager = serializers.SerializerMethodField(method_name="get_is_user_manager")

def count_views(self, program):
return get_views_count(program)
Expand Down
159 changes: 137 additions & 22 deletions partner_programs/services.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging
from collections import OrderedDict

from partner_programs.models import PartnerProgramUserProfile
from project_rates.models import Criteria, ProjectScore


logger = logging.getLogger()


Expand All @@ -23,16 +23,14 @@ class ProjectScoreDataPreparer:
"Класс_курс": "ОШИБКА",
}

EXPERT_ERROR_FIELDS = {
"Фамилия эксперта": "ОШИБКА"
}
EXPERT_ERROR_FIELDS = {"Фамилия эксперта": "ОШИБКА"}

def __init__(
self,
user_profiles: dict[int, PartnerProgramUserProfile],
scores: dict[int, list[ProjectScore]],
project_id: int,
program_id: int
program_id: int,
):
self._project_id = project_id
self._user_profiles = user_profiles
Expand All @@ -41,35 +39,54 @@ def __init__(

def get_project_user_info(self) -> dict[str, str]:
try:
user_program_profile: PartnerProgramUserProfile = self._user_profiles.get(self._project_id)
user_program_profile_json: dict = user_program_profile.partner_program_data if user_program_profile else {}
user_program_profile: PartnerProgramUserProfile = self._user_profiles.get(
self._project_id
)
user_program_profile_json: dict = (
user_program_profile.partner_program_data if user_program_profile else {}
)

user_info: dict[str, str] = {
"Фамилия": user_program_profile.user.last_name if user_program_profile else '',
"Имя": user_program_profile.user.first_name if user_program_profile else '',
"Отчество": user_program_profile.user.patronymic if user_program_profile else '',
"Фамилия": (
user_program_profile.user.last_name if user_program_profile else ""
),
"Имя": (
user_program_profile.user.first_name if user_program_profile else ""
),
"Отчество": (
user_program_profile.user.patronymic if user_program_profile else ""
),
"Email": (
user_program_profile_json.get('email') if user_program_profile_json.get('email')
user_program_profile_json.get("email")
if user_program_profile_json.get("email")
else user_program_profile.user.email
),
"Регион_РФ": user_program_profile_json.get('region', ''),
"Учебное_заведение": user_program_profile_json.get('education_type', ''),
"Название_учебного_заведения": user_program_profile_json.get('institution_name', ''),
"Класс_курс": user_program_profile_json.get('class_course', ''),
"Регион_РФ": user_program_profile_json.get("region", ""),
"Учебное_заведение": user_program_profile_json.get("education_type", ""),
"Название_учебного_заведения": user_program_profile_json.get(
"institution_name", ""
),
"Класс_курс": user_program_profile_json.get("class_course", ""),
}
return user_info
except Exception as e:
logger.error(f"Prepare export rates data about user error: {str(e)}", exc_info=True)
logger.error(
f"Prepare export rates data about user error: {str(e)}", exc_info=True
)
return self.USER_ERROR_FIELDS

def get_project_expert_info(self) -> dict[str, str]:
try:
project_scores: list[ProjectScore] = self._scores.get(self._project_id, [])
first_score = project_scores[0] if project_scores else None
expert_last_name: dict[str, str] = {"Фамилия эксперта": first_score.user.last_name if first_score else ''}
expert_last_name: dict[str, str] = {
"Фамилия эксперта": first_score.user.last_name if first_score else ""
}
return expert_last_name
except Exception as e:
logger.error(f"Prepare export rates data about expert error: {str(e)}", exc_info=True)
logger.error(
f"Prepare export rates data about expert error: {str(e)}", exc_info=True
)
return self.EXPERT_ERROR_FIELDS

def get_project_scores_info(self) -> dict[str, str]:
Expand All @@ -78,16 +95,114 @@ def get_project_scores_info(self) -> dict[str, str]:
project_scores: list[ProjectScore] = self._scores.get(self._project_id, [])
score_info_with_out_comment: dict[str, str] = {
score.criteria.name: score.value
for score in project_scores if score.criteria.name != "Комментарий"
for score in project_scores
if score.criteria.name != "Комментарий"
}
project_scores_dict.update(score_info_with_out_comment)
comment = next((score for score in project_scores if score.criteria.name == "Комментарий"), None)
comment = next(
(
score
for score in project_scores
if score.criteria.name == "Комментарий"
),
None,
)
if comment is not None:
project_scores_dict["Комментарий"] = comment.value
return project_scores_dict
except Exception as e:
logger.error(f"Prepare export rates data about project_scores error: {str(e)}", exc_info=True)
logger.error(
f"Prepare export rates data about project_scores error: {str(e)}",
exc_info=True,
)
return {
criteria.name: "ОШИБКА"
for criteria in Criteria.objects.filter(partner_program__id=self._program_id)
for criteria in Criteria.objects.filter(
partner_program__id=self._program_id
)
}


BASE_COLUMNS = [
("row_number", "№ п/п"),
("project_name", "Название проекта"),
("project_description", "Описание проекта"),
("project_region", "Регион проекта"),
("project_presentation", "Ссылка на презентацию"),
("team_size", "Количество человек в команде"),
("leader_full_name", "Имя фамилия лидера"),
]


def _leader_full_name(user):
if not user:
return ""
if hasattr(user, "get_full_name") and callable(user.get_full_name):
full = user.get_full_name()
if full:
return full
first = getattr(user, "first_name", "") or ""
last = getattr(user, "last_name", "") or ""
return (first + " " + last).strip() or getattr(user, "username", "") or str(user.pk)


def _calc_team_size(project):
try:
if hasattr(project, "get_collaborators_user_list"):
return 1 + len(project.get_collaborators_user_list())
if hasattr(project, "collaborator_set"):
return 1 + project.collaborator_set.count()
except Exception:
pass
return 1


def build_program_field_columns(program) -> list[tuple[str, str]]:
program_fields = program.fields.all().order_by("pk")
return [
(f"name:{program_field.name}", program_field.label)
for program_field in program_fields
]


def row_dict_for_link(
program_project_link,
extra_field_keys_order: list[str],
row_number: int,
) -> OrderedDict:
"""
program_project_link: PartnerProgramProject
extra_field_keys_order: список псевдоключей "name:<field.name>" в нужном порядке
row_number: порядковый номер строки в Excel (начиная с 1)
"""
project = program_project_link.project
row = OrderedDict()

row["row_number"] = row_number

row["project_name"] = project.name or ""
row["project_description"] = project.description or ""
row["project_region"] = project.region or ""
row["project_presentation"] = project.presentation_address or ""
row["team_size"] = _calc_team_size(project)
row["leader_full_name"] = _leader_full_name(getattr(project, "leader", None))

values_map: dict[str, str] = {}
prefetched_values = getattr(program_project_link, "_prefetched_field_values", None)
field_values_iterable = (
prefetched_values
if prefetched_values is not None
else program_project_link.field_values.all()
)

for field_value in field_values_iterable:
if (
field_value.field.partner_program_id
== program_project_link.partner_program_id
):
values_map[f"name:{field_value.field.name}"] = field_value.get_value()

for field_key in extra_field_keys_order:
row[field_key] = values_map.get(field_key, "")

return row
6 changes: 6 additions & 0 deletions partner_programs/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
PartnerProgramCreateUserAndRegister,
PartnerProgramDataSchema,
PartnerProgramDetail,
PartnerProgramExportProjectsAPIView,
PartnerProgramList,
PartnerProgramProjectsAPIView,
PartnerProgramProjectSubmitView,
Expand Down Expand Up @@ -50,4 +51,9 @@
PartnerProgramProjectsAPIView.as_view(),
name="partner-program-projects",
),
path(
"<int:pk>/export-projects/",
PartnerProgramExportProjectsAPIView.as_view(),
name="partner-program-export-projects",
),
]
Loading