diff --git a/projects/migrations/0008_alter_project_description_and_more.py b/projects/migrations/0008_alter_project_description_and_more.py new file mode 100644 index 00000000..2ba3030c --- /dev/null +++ b/projects/migrations/0008_alter_project_description_and_more.py @@ -0,0 +1,67 @@ +# Generated by Django 4.1.3 on 2022-11-18 11:45 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +def set_userachievement_user_to_not_null(apps, schema_editor): + Project = apps.get_model('projects', 'Project') + Project.objects.filter(leader=None).delete() + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("industries", "0001_initial"), + ("projects", "0007_remove_collaborator_unique_collaorator_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="project", + name="description", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="project", + name="image_address", + field=models.URLField(blank=True, null=True), + ), + migrations.AlterField( + model_name="project", + name="industry", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="projects", + to="industries.industry", + ), + ), + migrations.RunPython(set_userachievement_user_to_not_null), + migrations.AlterField( + model_name="project", + name="leader", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="leaders_projects", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterField( + model_name="project", + name="presentation_address", + field=models.URLField(blank=True, null=True), + ), + migrations.AlterField( + model_name="project", + name="region", + field=models.CharField(blank=True, max_length=256, null=True), + ), + migrations.AlterField( + model_name="project", + name="short_description", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/projects/models.py b/projects/models.py index 7b1898d4..890aa516 100644 --- a/projects/models.py +++ b/projects/models.py @@ -31,24 +31,24 @@ class Project(models.Model): """ name = models.CharField(max_length=256, null=True, blank=True) - description = models.TextField(blank=True) - short_description = models.TextField(blank=True) - region = models.CharField(max_length=256, blank=True) + description = models.TextField(null=True, blank=True) + short_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) industry = models.ForeignKey( Industry, on_delete=models.SET_NULL, null=True, + blank=True, related_name="projects", ) - presentation_address = models.URLField(blank=True) - image_address = models.URLField(blank=True) + presentation_address = models.URLField(null=True, blank=True) + image_address = models.URLField(null=True, blank=True) leader = models.ForeignKey( User, - on_delete=models.SET_NULL, - null=True, + on_delete=models.CASCADE, related_name="leaders_projects", # projects in which this user is the leader ) @@ -85,10 +85,7 @@ class Achievement(models.Model): status = models.CharField(max_length=256, null=False) project = models.ForeignKey( - Project, - on_delete=models.SET_NULL, - null=True, - related_name="achievements", + Project, on_delete=models.SET_NULL, null=True, related_name="achievements" ) objects = AchievementManager() diff --git a/projects/serializers.py b/projects/serializers.py index 1bd35dc5..023e223e 100644 --- a/projects/serializers.py +++ b/projects/serializers.py @@ -3,7 +3,6 @@ from industries.models import Industry from projects.models import Project, Achievement, Collaborator from projects.validators import validate_project -from users.models import CustomUser from vacancy.serializers import ProjectVacancyListSerializer @@ -95,10 +94,6 @@ class Meta: class ProjectListSerializer(serializers.ModelSerializer): - def __init__(self, *args, **kwargs): - # might be unnecessary - self.max_collaborator_count = kwargs.pop("max_collaborator_count", 4) - super().__init__(*args, **kwargs) collaborators = serializers.SerializerMethodField(method_name="get_collaborators") collaborator_count = serializers.SerializerMethodField( @@ -111,8 +106,9 @@ def get_collaborator_count(cls, obj): return len(obj.collaborator_set.all()) def get_collaborators(self, obj): + max_collaborator_count = 4 return CollaboratorSerializer( - instance=obj.collaborator_set.all()[: self.max_collaborator_count], many=True + instance=obj.collaborator_set.all()[:max_collaborator_count], many=True ).data class Meta: @@ -144,16 +140,6 @@ def validate(self, data): super().validate(data) return validate_project(data) - def create(self, validated_data): - industry = Industry.objects.get(id=validated_data.pop("industry")) - leader = CustomUser.objects.get(id=validated_data.pop("leader")) - project = Project.objects.create( - **validated_data, - industry=industry, - leader=leader, - ) - return project - class ProjectIndustrySerializer(serializers.ModelSerializer): id = serializers.IntegerField() diff --git a/projects/views.py b/projects/views.py index 4bc6956c..d86e9f47 100644 --- a/projects/views.py +++ b/projects/views.py @@ -30,8 +30,7 @@ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) # Doesn't work if not explicitly set like this - serializer.validated_data["leader"] = request.user.id - serializer.validated_data["industry"] = request.data["industry"] + serializer.validated_data["leader"] = request.user self.perform_create(serializer) headers = self.get_success_headers(serializer.data) diff --git a/users/migrations/0019_alter_userachievement_user.py b/users/migrations/0019_alter_userachievement_user.py new file mode 100644 index 00000000..fee4aff7 --- /dev/null +++ b/users/migrations/0019_alter_userachievement_user.py @@ -0,0 +1,30 @@ +# Generated by Django 4.1.3 on 2022-11-18 11:18 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def set_userachievement_user_to_not_null(apps, schema_editor): + UserAchievement = apps.get_model('users', 'UserAchievement') + UserAchievement.objects.filter(user=None).delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0018_alter_customuser_about_me"), + ] + + operations = [ + migrations.RunPython(set_userachievement_user_to_not_null), + migrations.AlterField( + model_name="userachievement", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="achievements", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/users/models.py b/users/models.py index 4a966cb3..b713cb10 100644 --- a/users/models.py +++ b/users/models.py @@ -82,10 +82,14 @@ class CustomUser(AbstractUser): objects = CustomUserManager() - def get_member_key_skills(self) -> str: + def get_member_key_skills(self) -> list[str]: if self.user_type == CustomUser.MEMBER: - return str(self.member.key_skills) - return "" + return [ + skill.strip() + for skill in self.member.key_skills.split(",") + if skill.strip() + ] + return [] def __str__(self): return f"User<{self.id}> - {self.first_name} {self.last_name}" @@ -106,8 +110,7 @@ class UserAchievement(models.Model): user = models.ForeignKey( CustomUser, - on_delete=models.SET_NULL, - null=True, + on_delete=models.CASCADE, related_name="achievements", ) @@ -170,6 +173,9 @@ class Member(models.Model): Industry, blank=True, related_name="members" ) + def get_key_skills(self) -> list[str]: + return [skill.strip() for skill in self.key_skills.split(",") if skill.strip()] + def __str__(self): return f"Member<{self.id}> - {self.user.first_name} {self.user.last_name}" diff --git a/users/permissions.py b/users/permissions.py index 546c7075..df7eb8f9 100644 --- a/users/permissions.py +++ b/users/permissions.py @@ -6,13 +6,6 @@ class IsAchievementOwnerOrReadOnly(BasePermission): Allows access to update only to himself. """ - def has_permission(self, request, view) -> bool: - if request.method in SAFE_METHODS or ( - request.user and request.user.id == request.data.get("user") - ): - return True - return False - def has_object_permission(self, request, view, obj): if request.method in SAFE_METHODS or (obj.user == request.user): return True diff --git a/users/serializers.py b/users/serializers.py index 0a01a0b4..2a3c0856 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -7,15 +7,22 @@ class AchievementListSerializer(serializers.ModelSerializer): class Meta: model = UserAchievement - fields = [ - "id", - "title", - "status", - ] + fields = ["id", "title", "status"] + # extra_kwargs = {"user": {"write_only": True}} ref_name = "Users" +class KeySkillsField(serializers.Field): + def to_representation(self, value): + return [skill.strip() for skill in value.split(",") if skill.strip()] + + def to_internal_value(self, data): + return ",".join(data) + + class MemberSerializer(serializers.ModelSerializer): + key_skills = KeySkillsField() + class Meta: model = Member fields = [ diff --git a/users/views.py b/users/views.py index 0ed4426a..6c30b9c3 100644 --- a/users/views.py +++ b/users/views.py @@ -264,6 +264,26 @@ class AchievementList(ListCreateAPIView): serializer_class = AchievementListSerializer permission_classes = [IsAchievementOwnerOrReadOnly] + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.validated_data["user"] = request.user + # warning for someone who tries to set user variable (the user will always be yourself anyway) + if ( + request.data.get("user") is not None + and request.data.get("user") != request.user.id + ): + return Response( + { + "error": "you can't edit USER field for this view since " + "you can't create achievements for other people" + }, + status=status.HTTP_403_FORBIDDEN, + ) + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + class AchievementDetail(RetrieveUpdateDestroyAPIView): queryset = UserAchievement.objects.get_achievements_for_detail_view()