Skip to content
Merged

Dev #564

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
cbd89ef
Merge pull request #554 from PROCOLLAB-github/hotfix/export_program_info
Toksi86 Oct 17, 2025
7289f8f
В docker compose в контейнере web изменён порт на локальный
Toksi86 Oct 17, 2025
48eb1e3
Merge pull request #555 from PROCOLLAB-github/fix/nginx.conf
Toksi86 Oct 17, 2025
0d58831
Исправлены ошибки сериализации данных drf-yasg
Toksi86 Oct 22, 2025
ded251e
Расширена модель "Достижения Пользователя"
Toksi86 Oct 22, 2025
0545cbb
Merge pull request #556 from PROCOLLAB-github/feture/new_fields_for_u…
Toksi86 Oct 22, 2025
1e9b6a3
Измененён формат выдачи поля файла при получении списка достижений и …
Toksi86 Oct 23, 2025
e0c4635
Merge pull request #557 from PROCOLLAB-github/feture/new_fields_for_u…
Toksi86 Oct 23, 2025
046c5fa
Информация о файлах в достижениях пользователя возвращается в подробн…
Toksi86 Oct 24, 2025
29fecb5
Merge pull request #558 from PROCOLLAB-github/feture/new_fields_for_u…
Toksi86 Oct 24, 2025
486acbc
Расширил выдачу откликов на вакансии; Переписал обновление достижени…
Toksi86 Oct 27, 2025
273a394
Merge pull request #559 from PROCOLLAB-github/fix/UserVacancyResponses
Toksi86 Oct 27, 2025
70f00a4
Расширяет данные проектов и вакансий; ужесточает валидацию достижений
Toksi86 Oct 29, 2025
bdacb1d
Merge pull request #560 from PROCOLLAB-github/feature/project-detail-…
Toksi86 Oct 29, 2025
2d8d986
Исправлено имя автора новости; Добавлены логотипы программ; расширен …
Toksi86 Oct 30, 2025
eb30bdb
Merge pull request #561 from PROCOLLAB-github/release/user-profile-va…
Toksi86 Oct 30, 2025
8cc7c57
Расширена модель программы, в выдачу данных о проектах добавлено допо…
Toksi86 Oct 31, 2025
f588677
В раздел Программы администрационной панели добавлено поле Ссылка на …
Toksi86 Oct 31, 2025
d90c3b8
Merge pull request #563 from PROCOLLAB-github/release/minor_fixes
Toksi86 Oct 31, 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
4 changes: 4 additions & 0 deletions core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
from .models import SkillToObject, SkillCategory, Skill


class EmptySerializer(serializers.Serializer):
pass


class SetLikedSerializer(serializers.Serializer):
is_liked = serializers.BooleanField()

Expand Down
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:
- "127.0.0.1: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
19 changes: 10 additions & 9 deletions feed/views.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from django.contrib.contenttypes.models import ContentType
from django.db.models import QuerySet, Q
from django.db.models import Q, QuerySet
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from rest_framework.views import APIView

from core.serializers import EmptySerializer
from feed.pagination import FeedPagination
from feed.services import get_liked_news

from news.models import News
from .serializers import NewsFeedListSerializer
from projects.models import Project
from partner_programs.models import PartnerProgramUserProfile
from projects.models import Project
from vacancy.models import Vacancy

from .serializers import NewsFeedListSerializer


class NewSimpleFeed(APIView):
serializator_class = NewsFeedListSerializer
Expand All @@ -29,11 +30,9 @@ def _get_filter_data(self) -> list[str]:

def _get_excluded_projects_ids(self) -> list[int]:
"""IDs for exclude projects which in Partner Program."""
excluded_projects = (
PartnerProgramUserProfile.objects
.values_list("project_id", flat=True)
.exclude(project_id__isnull=True)
)
excluded_projects = PartnerProgramUserProfile.objects.values_list(
"project_id", flat=True
).exclude(project_id__isnull=True)
return excluded_projects

def get_queryset(self) -> QuerySet[News]:
Expand Down Expand Up @@ -80,6 +79,8 @@ def get(self, *args, **kwargs):


class DevScript(CreateAPIView):
serializer_class = EmptySerializer

def create(self, request):
content_type_project = ContentType.objects.filter(model="project").first()
for project in Project.objects.filter(draft=False):
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
2 changes: 1 addition & 1 deletion news/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class NewsMapping:
@classmethod
def get_name(cls, content_object) -> str:
if isinstance(content_object, User):
f"{content_object.first_name} {content_object.last_name}"
return f"{content_object.first_name} {content_object.last_name}"
if isinstance(content_object, Project) or isinstance(
content_object, PartnerProgram
):
Expand Down
46 changes: 45 additions & 1 deletion partner_programs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import urllib.parse

import tablib
from django import forms
from django.contrib import admin
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse
Expand Down Expand Up @@ -37,7 +38,19 @@ class PartnerProgramFieldInline(admin.TabularInline):

@admin.register(PartnerProgram)
class PartnerProgramAdmin(admin.ModelAdmin):
class PartnerProgramAdminForm(forms.ModelForm):
class Meta:
model = PartnerProgram
fields = "__all__"
widgets = {
"name": forms.TextInput(attrs={"size": 80}),
"tag": forms.TextInput(attrs={"size": 80}),
"description": forms.Textarea(attrs={"rows": 4, "cols": 82}),
"city": forms.TextInput(attrs={"size": 80}),
}

inlines = [PartnerProgramMaterialInline, PartnerProgramFieldInline]
form = PartnerProgramAdminForm
list_display = ("id", "name", "tag", "city", "datetime_created")
list_display_links = (
"id",
Expand All @@ -53,8 +66,39 @@ class PartnerProgramAdmin(admin.ModelAdmin):
)
list_filter = ("city",)

filter_horizontal = ("users", "managers")
filter_horizontal = ("managers",)
date_hierarchy = "datetime_started"
readonly_fields = ("datetime_created", "datetime_updated")
fieldsets = (
(
None,
{
"fields": (
"name",
"tag",
"description",
"city",
"is_competitive",
"projects_availability",
"draft",
(
"datetime_started",
"datetime_registration_ends",
"datetime_finished",
),
(
"image_address",
"cover_image_address",
"advertisement_image_address",
),
("presentation_address", "registration_link"),
"data_schema",
)
},
),
("Менеджеры программы", {"fields": ("managers",)}),
("Служебная информация", {"fields": ("datetime_created", "datetime_updated")}),
)

def get_queryset(self, request: HttpRequest) -> QuerySet[PartnerProgram]:
qs = super().get_queryset(request)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.24 on 2025-10-31 10:20

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('partner_programs', '0011_partnerprogram_is_competitive_and_more'),
]

operations = [
migrations.AddField(
model_name='partnerprogram',
name='registration_link',
field=models.URLField(blank=True, help_text='Адрес страницы регистрации (например, на Tilda)', null=True, verbose_name='Ссылка на регистрацию'),
),
]
6 changes: 6 additions & 0 deletions partner_programs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ class PartnerProgram(models.Model):
blank=True,
verbose_name="Ссылка на презентацию",
)
registration_link = models.URLField(
null=True,
blank=True,
verbose_name="Ссылка на регистрацию",
help_text="Адрес страницы регистрации (например, на Tilda)",
)
data_schema = models.JSONField(
verbose_name="Схема данных в формате JSON",
help_text="Ключи - имена полей, значения - тип поля ввода",
Expand Down
7 changes: 4 additions & 3 deletions partner_programs/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Meta:
"name",
"image_address",
"short_description",
"registration_link",
"datetime_registration_ends",
"datetime_started",
"datetime_finished",
Expand Down Expand Up @@ -80,9 +81,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 Expand Up @@ -112,6 +111,7 @@ class Meta:
"image_address",
"cover_image_address",
"presentation_address",
"registration_link",
"views_count",
"datetime_registration_ends",
"is_user_manager",
Expand All @@ -133,6 +133,7 @@ class Meta:
"cover_image_address",
"advertisement_image_address",
"presentation_address",
"registration_link",
"datetime_registration_ends",
"is_user_manager",
)
Expand Down
9 changes: 7 additions & 2 deletions partner_programs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from core.serializers import SetLikedSerializer, SetViewedSerializer
from core.serializers import EmptySerializer, SetLikedSerializer, SetViewedSerializer
from core.services import add_view, set_like
from partner_programs.helpers import date_to_iso
from partner_programs.models import (
Expand Down Expand Up @@ -69,6 +69,7 @@ class PartnerProgramList(generics.ListCreateAPIView):
class PartnerProgramDetail(generics.RetrieveAPIView):
queryset = PartnerProgram.objects.prefetch_related("materials", "managers").all()
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = PartnerProgramForUnregisteredUserSerializer

def get(self, request, *args, **kwargs):
program = self.get_object()
Expand Down Expand Up @@ -320,7 +321,7 @@ def put(self, request, project_id, *args, **kwargs):

class PartnerProgramProjectSubmitView(GenericAPIView):
permission_classes = [IsAuthenticated, IsProjectLeader]
serializer_class = None
serializer_class = EmptySerializer
queryset = PartnerProgramProject.objects.all()

@swagger_auto_schema(
Expand Down Expand Up @@ -375,6 +376,7 @@ class ProgramProjectFilterAPIView(GenericAPIView):
serializer_class = ProgramProjectFilterRequestSerializer
permission_classes = [permissions.IsAuthenticated]
pagination_class = PartnerProgramPagination
queryset = PartnerProgram.objects.none()

def post(self, request, pk):
serializer = self.get_serializer(data=request.data)
Expand Down Expand Up @@ -445,6 +447,9 @@ class PartnerProgramProjectsAPIView(generics.ListAPIView):
pagination_class = PartnerProgramPagination

def get_queryset(self):
if "pk" not in self.kwargs:
return Project.objects.none()

program = get_object_or_404(PartnerProgram, pk=self.kwargs["pk"])
return Project.objects.filter(program_links__partner_program=program).distinct()

Expand Down
7 changes: 1 addition & 6 deletions procollab/celery.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import os

from celery import Celery
import django

# from celery.schedules import crontab
from celery.schedules import crontab

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "procollab.settings")
django.setup()

app = Celery("procollab")

app = Celery("procollab")
app.config_from_object("django.conf:settings", namespace="CELERY")

app.autodiscover_tasks()

app.conf.task_serializer = "json"

app.conf.beat_schedule = {
"outdate_vacancy": {
"task": "vacancy.tasks.email_notificate_vacancy_outdated",
Expand Down
Loading