diff --git a/invites/tests.py b/invites/tests.py index dcabc897..613fd603 100644 --- a/invites/tests.py +++ b/invites/tests.py @@ -36,7 +36,6 @@ def setUp(self) -> None: "name": "Test", "description": "Test", "industry": Industry.objects.create(name="Test").id, - "step": 1, "draft": False, } diff --git a/projects/admin.py b/projects/admin.py index d7cbce7e..99435e5d 100644 --- a/projects/admin.py +++ b/projects/admin.py @@ -18,8 +18,9 @@ class ProjectAdmin(admin.ModelAdmin): "name", "draft", "is_company", - "track", - "direction", + "trl", + "target_audience", + "implementation_deadline", ) list_display_links = ( "id", @@ -27,13 +28,11 @@ class ProjectAdmin(admin.ModelAdmin): ) search_fields = ( "name", - "track", ) list_filter = ( "draft", "is_company", - "track", - "direction", + "trl", ) fieldsets = ( @@ -46,7 +45,6 @@ class ProjectAdmin(admin.ModelAdmin): "leader", "industry", "region", - "step", "draft", "is_company", ) @@ -56,10 +54,7 @@ class ProjectAdmin(admin.ModelAdmin): "Для проектов ПД МосПолитеха", { "fields": ( - "track", - "direction", "actuality", - "goal", "problem", ) }, diff --git a/projects/constants.py b/projects/constants.py deleted file mode 100644 index 0dd9684e..00000000 --- a/projects/constants.py +++ /dev/null @@ -1,9 +0,0 @@ -VERBOSE_STEPS = ( - (0, "Идея"), - (1, "Прототип"), - (2, "MVP(Минимально жизнеспособный продукт)"), - (3, "Первые продажи"), - (4, "Масштабирование"), -) - -RECOMMENDATIONS_COUNT = 5 diff --git a/projects/filters.py b/projects/filters.py index 3d1f36c6..9ece8dc4 100644 --- a/projects/filters.py +++ b/projects/filters.py @@ -13,9 +13,9 @@ class ProjectFilter(filters.FilterSet): Adds filtering to DRF list retrieve views Parameters to filter by: - industry (int), step (int), region (str), name__contains (str), + industry (int), region (str), name__contains (str), description__contains (str), collaborator__user__in (List[int]), - datetime_created__gt (datetime.datetime), step (int), any_vacancies (bool), + datetime_created__gt (datetime.datetime), any_vacancies (bool), member_count__gt (int), member_count__lt (int), leader (int), partner_program (int), is_company (bool). @@ -25,7 +25,6 @@ class ProjectFilter(filters.FilterSet): ?datetime_created__gt=25.10.2022 equals to .filter(datetime_created__gt=datetime.datetime(...)) ?collaborator__user__in=1,2 equals to .filter(collaborator__user__in=[1, 2]) - ?step=1 equals to .filter(step=1) ?any_vacancies=true equals to .filter(any_vacancies=True) ?collaborator__count__gt=1 equals to .filter(collaborator__count__gt=1) ?is_company=0/?is_company=false equals .filter(is_company=False) @@ -113,7 +112,6 @@ def filter_by_have_expert_rates(self, queryset, name, value): collaborator__count__lte = filters.NumberFilter( field_name="collaborator", method="filter_collaborator_count_lte" ) - step = filters.NumberFilter(field_name="step") partner_program = filters.NumberFilter( field_name="partner_program", method="filter_by_partner_program" ) @@ -128,10 +126,8 @@ class Meta: model = Project fields = ( "industry", - "step", "region", "leader", - "step", "partner_program", "is_company", ) diff --git a/projects/helpers.py b/projects/helpers.py index 36e7a50b..ad3c02bc 100644 --- a/projects/helpers.py +++ b/projects/helpers.py @@ -7,7 +7,6 @@ from rest_framework.exceptions import ValidationError from partner_programs.models import PartnerProgram, PartnerProgramUserProfile -from projects.constants import RECOMMENDATIONS_COUNT from projects.models import Project, ProjectLink, Achievement from users.models import CustomUser @@ -18,7 +17,7 @@ def get_recommended_users(project: Project) -> list[User]: """ Searches for users by matching their skills and vacancies required_skills """ - + RECOMMENDATIONS_COUNT = 5 all_needed_skills = set() for vacancy in project.vacancies.all(): all_needed_skills.update(set(vacancy.get_required_skills())) diff --git a/projects/migrations/0028_remove_project_direction_remove_project_goal_and_more.py b/projects/migrations/0028_remove_project_direction_remove_project_goal_and_more.py new file mode 100644 index 00000000..84e7897f --- /dev/null +++ b/projects/migrations/0028_remove_project_direction_remove_project_goal_and_more.py @@ -0,0 +1,65 @@ +# Generated by Django 4.2.11 on 2025-09-02 07:59 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("projects", "0027_alter_defaultprojectcover_datetime_created_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="project", + name="direction", + ), + migrations.RemoveField( + model_name="project", + name="goal", + ), + migrations.RemoveField( + model_name="project", + name="step", + ), + migrations.RemoveField( + model_name="project", + name="track", + ), + migrations.AddField( + model_name="project", + name="implementation_deadline", + field=models.DateField( + blank=True, + help_text="Дата, до которой планируется реализовать проект", + null=True, + verbose_name="Общий срок реализации проекта", + ), + ), + migrations.AddField( + model_name="project", + name="target_audience", + field=models.CharField( + blank=True, + help_text="Описание целевой аудитории проекта (до 500 симв.)", + max_length=500, + null=True, + verbose_name="Целевая аудитория", + ), + ), + migrations.AddField( + model_name="project", + name="trl", + field=models.PositiveSmallIntegerField( + blank=True, + help_text="Technology Readiness Level (от 1 до 9)", + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(9), + ], + verbose_name="TRL", + ), + ), + ] diff --git a/projects/models.py b/projects/models.py index 117b65c7..9bc99bfc 100644 --- a/projects/models.py +++ b/projects/models.py @@ -2,14 +2,17 @@ from django.contrib.auth import get_user_model from django.contrib.contenttypes.fields import GenericRelation -from django.core.validators import MaxLengthValidator +from django.core.validators import ( + MaxLengthValidator, + MaxValueValidator, + MinValueValidator, +) from django.db import models from django.db.models import UniqueConstraint from core.models import Like, View from files.models import UserFile from industries.models import Industry -from projects.constants import VERBOSE_STEPS from projects.managers import AchievementManager, CollaboratorManager, ProjectManager from users.models import CustomUser @@ -60,48 +63,36 @@ class Meta: class Project(models.Model): """ - Project model - - Attributes: - name: A CharField name of the project. - description: A TextField description of the project. - region: A CharField region of the project. - step: A PositiveSmallIntegerField which indicates status of the project - according to VERBOSE_STEPS. - industry: A ForeignKey referring to the Industry model. - presentation_address: A URLField presentation URL address. - image_address: A URLField image URL address. - leader: A ForeignKey referring to the User model. - draft: A boolean indicating if Project is a draft. - is_company: A boolean indicating if Project is a company. - cover_image_address: A URLField cover image URL address. - cover: A ForeignKey referring to the UserFile model, which is the image cover of the project. - datetime_created: A DateTimeField indicating date of creation. - datetime_updated: A DateTimeField indicating date of update. + Модель проекта. + + Атрибуты: + name (CharField): Название проекта. + description (TextField): Подробное описание проекта. + region (CharField): Регион, в котором реализуется проект. + hidden_score (PositiveSmallIntegerField): Скрытый рейтинг проекта, + используется для внутренней сортировки. + actuality (TextField): Актуальность проекта (почему он важен). + target_audience (CharField): Описание целевой аудитории проекта. + implementation_deadline (DateField): Общий срок реализации проекта (дата завершения). + problem (TextField): Проблема, которую решает проект. + trl (PositiveSmallIntegerField): Уровень технологической готовности (Technology Readiness Level) от 1 до 9. + industry (ForeignKey): Ссылка на отрасль (модель Industry). + presentation_address (URLField): Ссылка на презентацию проекта. + image_address (URLField): Ссылка на изображение (аватар проекта). + leader (ForeignKey): Руководитель проекта (пользователь). + draft (BooleanField): Флаг, указывающий, является ли проект черновиком. + is_company (BooleanField): Признак того, что проект представляет компанию. + cover_image_address (URLField): Ссылка на обложку проекта. + cover (ForeignKey): Файл-обложка проекта (устаревшее поле). + subscribers (ManyToManyField): Подписчики проекта. + datetime_created (DateTimeField): Дата создания проекта. + datetime_updated (DateTimeField): Дата последнего изменения проекта. """ name = models.CharField(max_length=256, null=True, blank=True) description = models.TextField(null=True, blank=True) region = models.CharField(max_length=256, null=True, blank=True) - step = models.PositiveSmallIntegerField( - choices=VERBOSE_STEPS, null=True, blank=True - ) hidden_score = models.PositiveSmallIntegerField(default=100) - - track = models.CharField( - max_length=256, - blank=True, - null=True, - verbose_name="Трек", - help_text="Направление/курс, в рамках которого реализуется проект", - ) - direction = models.CharField( - max_length=256, - blank=True, - null=True, - verbose_name="Направление", - help_text="Более общее направление деятельности проекта", - ) actuality = models.TextField( blank=True, null=True, @@ -109,12 +100,25 @@ class Project(models.Model): verbose_name="Актуальность", help_text="Почему проект важен (до 1000 симв.)", ) - goal = models.CharField( + target_audience = models.CharField( max_length=500, blank=True, null=True, - verbose_name="Цель", - help_text="Главная цель проекта (до 500 симв.)", + verbose_name="Целевая аудитория", + help_text="Описание целевой аудитории проекта (до 500 симв.)", + ) + trl = models.PositiveSmallIntegerField( + verbose_name="TRL", + help_text="Technology Readiness Level (от 1 до 9)", + validators=[MinValueValidator(1), MaxValueValidator(9)], + null=True, + blank=True, + ) + implementation_deadline = models.DateField( + verbose_name="Общий срок реализации проекта", + help_text="Дата, до которой планируется реализовать проект", + null=True, + blank=True, ) problem = models.TextField( blank=True, diff --git a/projects/serializers.py b/projects/serializers.py index e2d300be..b6e409bf 100644 --- a/projects/serializers.py +++ b/projects/serializers.py @@ -122,11 +122,13 @@ class ProjectDetailSerializer(serializers.ModelSerializer): links = serializers.SerializerMethodField() partner_program = serializers.SerializerMethodField() partner_program_tags = serializers.SerializerMethodField() - track = serializers.CharField(required=False, allow_null=True, allow_blank=True) - direction = serializers.CharField(required=False, allow_null=True, allow_blank=True) actuality = serializers.CharField(required=False, allow_null=True, allow_blank=True) - goal = serializers.CharField(required=False, allow_null=True, allow_blank=True) problem = serializers.CharField(required=False, allow_null=True, allow_blank=True) + target_audience = serializers.CharField( + required=False, allow_blank=True, allow_null=True + ) + implementation_deadline = serializers.DateField(required=False, allow_null=True) + trl = serializers.IntegerField(required=False, allow_null=True) def get_partner_program(self, project): try: @@ -172,7 +174,6 @@ class Meta: "achievements", "links", "region", - "step", "industry", "industry_id", "presentation_address", @@ -187,11 +188,11 @@ class Meta: "views_count", "cover", "cover_image_address", - "track", - "direction", "actuality", - "goal", "problem", + "target_audience", + "implementation_deadline", + "trl", "partner_program_tags", "partner_program", ] diff --git a/projects/tests.py b/projects/tests.py index ff30af9d..c046d699 100644 --- a/projects/tests.py +++ b/projects/tests.py @@ -19,7 +19,6 @@ def setUp(self): "name": "Test", "description": "Test", "industry": Industry.objects.create(name="Test").id, - "step": 1, } def test_project_creation(self): @@ -42,7 +41,6 @@ def test_project_creation_with_wrong_data(self): "name": "T" * 257, "description": "Test", "industry": Industry.objects.create(name="Test").id, - "step": 1, }, ) force_authenticate(request, user=user) diff --git a/projects/urls.py b/projects/urls.py index 636f8ca7..046b13d2 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -12,7 +12,6 @@ ProjectDetail, ProjectList, ProjectRecommendedUsers, - ProjectSteps, ProjectSubscribe, ProjectSubscribers, ProjectUnsubscribe, @@ -50,7 +49,6 @@ name="update_program_fields", ), path("count/", ProjectCountView.as_view()), - path("steps/", ProjectSteps.as_view()), path("achievements/", AchievementList.as_view()), path("achievements//", AchievementDetail.as_view()), path("/responses/", ProjectVacancyResponses.as_view()), diff --git a/projects/views.py b/projects/views.py index bc330f9e..7fbdce6b 100644 --- a/projects/views.py +++ b/projects/views.py @@ -23,7 +23,6 @@ PartnerProgramProject, PartnerProgramUserProfile, ) -from projects.constants import VERBOSE_STEPS from projects.exceptions import CollaboratorDoesNotExist from projects.filters import ProjectFilter from projects.helpers import ( @@ -121,7 +120,6 @@ def post(self, request, *args, **kwargs): [name] - название проекта [description] - описание проекта [industry] - id отрасли - [step] - этап проекта [image_address] - адрес изображения [presentation_address] - адрес презентации [short_description] - краткое описание проекта @@ -322,16 +320,6 @@ def _collabs_queryset( ) -class ProjectSteps(APIView): - permission_classes = [IsStaffOrReadOnly] - - def get(self, request, format=None): - """ - Return a tuple of project steps. - """ - return Response(VERBOSE_STEPS) - - class AchievementList(generics.ListCreateAPIView): queryset = Achievement.objects.get_achievements_for_list_view() serializer_class = AchievementListSerializer @@ -695,12 +683,11 @@ def post(self, request): name=original_project.name, description=original_project.description, region=original_project.region, - step=original_project.step, hidden_score=original_project.hidden_score, - track=original_project.track, - direction=original_project.direction, actuality=original_project.actuality, - goal=original_project.goal, + target_audience=original_project.target_audience, + trl=original_project.trl, + implementation_deadline=original_project.implementation_deadline, problem=original_project.problem, industry=original_project.industry, image_address=original_project.image_address, diff --git a/users/migrations/0055_alter_customuser_avatar_alter_customuser_email.py b/users/migrations/0055_alter_customuser_avatar_alter_customuser_email.py new file mode 100644 index 00000000..2bbb6595 --- /dev/null +++ b/users/migrations/0055_alter_customuser_avatar_alter_customuser_email.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.11 on 2025-09-02 07:59 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0054_alter_customuser_first_name_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="customuser", + name="avatar", + field=models.URLField( + blank=True, + null=True, + validators=[ + django.core.validators.URLValidator(message="Введите корректный URL") + ], + ), + ), + migrations.AlterField( + model_name="customuser", + name="email", + field=models.EmailField( + error_messages={"unique": "Пользователь с таким email уже существует"}, + max_length=254, + unique=True, + ), + ), + ] diff --git a/vacancy/tests.py b/vacancy/tests.py index 356c5212..fb6d3596 100644 --- a/vacancy/tests.py +++ b/vacancy/tests.py @@ -33,7 +33,6 @@ def setUp(self): name="Test", description="Test", industry=Industry.objects.create(name="Test"), - step=1, leader=self.user_project_owner, ) self.vacancy_create_data = {