Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
110 changes: 85 additions & 25 deletions users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ def to_representation(self, data):
if isinstance(data, list):
return data
return [
i.replace("'", "") for i in data.strip("][").split(",") if i.replace("'", "")
i.replace("'", "")
for i in data.strip("][").split(",")
if i.replace("'", "")
]


Expand Down Expand Up @@ -107,6 +109,7 @@ class Meta:

class UserDataConfirmationSerializer(serializers.ModelSerializer):
"""Information about the User to add to the skill confirmation information."""

v2_speciality = SpecializationSerializer()

class Meta:
Expand Down Expand Up @@ -148,12 +151,15 @@ def to_representation(self, instance):
"""Returns correct data about user in `confirmed_by`."""
data = super().to_representation(instance)
data.pop("skill_to_object", None)
data["confirmed_by"] = UserDataConfirmationSerializer(instance.confirmed_by).data
data["confirmed_by"] = UserDataConfirmationSerializer(
instance.confirmed_by
).data
return data


class UserApproveSkillResponse(serializers.Serializer):
"""For swagger response presentation."""

confirmed_by = UserDataConfirmationSerializer(read_only=True)


Expand All @@ -173,14 +179,14 @@ class Meta:

def get_approves(self, obj):
"""Adds information about confirm to the skill."""
confirmations = (
UserSkillConfirmation.objects
.filter(skill_to_object=obj)
.select_related('confirmed_by')
)
confirmations = UserSkillConfirmation.objects.filter(
skill_to_object=obj
).select_related("confirmed_by")
return [
{
"confirmed_by": UserDataConfirmationSerializer(confirmation.confirmed_by).data,
"confirmed_by": UserDataConfirmationSerializer(
confirmation.confirmed_by
).data,
}
for confirmation in confirmations
]
Expand Down Expand Up @@ -300,14 +306,15 @@ def validate(self, attrs):
completion_year = attrs.get("completion_year")
entry_year = attrs.get("entry_year")
if (entry_year and completion_year) and (entry_year > completion_year):
raise ValidationError({
"entry_year": constants.USER_EXPERIENCE_YEAR_VALIDATION_MESSAGE,
})
raise ValidationError(
{
"entry_year": constants.USER_EXPERIENCE_YEAR_VALIDATION_MESSAGE,
}
)
return attrs


class UserEducationSerializer(UserExperienceMixin, serializers.ModelSerializer):

class Meta:
model = UserEducation
fields = [
Expand All @@ -321,7 +328,6 @@ class Meta:


class UserWorkExperienceSerializer(UserExperienceMixin, serializers.ModelSerializer):

class Meta:
model = UserWorkExperience
fields = [
Expand All @@ -334,7 +340,6 @@ class Meta:


class UserLanguagesSerializer(serializers.ModelSerializer):

class Meta:
model = UserLanguages
fields = [
Expand Down Expand Up @@ -391,11 +396,9 @@ def get_projects(self, user: CustomUser):
).data

def get_programs(self, user: CustomUser):
user_program_profiles = (
user.partner_program_profiles
.select_related('partner_program')
.filter(partner_program__draft=False)
)
user_program_profiles = user.partner_program_profiles.select_related(
"partner_program"
).filter(partner_program__draft=False)
return UserProgramsSerializer(
[profile.partner_program for profile in user_program_profiles],
context={"request": self.context.get("request"), "user": user},
Expand Down Expand Up @@ -523,7 +526,10 @@ def update(self, instance, validated_data):
if attr in IMMUTABLE_FIELDS + USER_TYPE_FIELDS + RELATED_FIELDS:
continue
if attr == "user_type":
if value == instance.user_type or value not in user_types_to_attr.keys():
if (
value == instance.user_type
or value not in user_types_to_attr.keys()
):
continue
# we can't change user type to Member
if value == CustomUser.MEMBER:
Expand Down Expand Up @@ -556,13 +562,17 @@ def _update_user_education(self, instance: CustomUser, data: list[dict]) -> None
serializer.save(user=instance)

@transaction.atomic
def _update_user_work_experience(self, instance: CustomUser, data: list[dict]) -> None:
def _update_user_work_experience(
self, instance: CustomUser, data: list[dict]
) -> None:
"""
Update user work experience.
`PUT`/ `PATCH` methods require full data about education.
"""
instance.work_experience.all().delete()
serializer = UserWorkExperienceSerializer(data=data, many=True, context=self.context)
serializer = UserWorkExperienceSerializer(
data=data, many=True, context=self.context
)
if serializer.is_valid(raise_exception=True):
serializer.save(user=instance)

Expand All @@ -575,7 +585,9 @@ def _update_user_languages(self, instance: CustomUser, data: list[dict]) -> None
# Only unique languages in profile.
languages = [lang_data["language"] for lang_data in data]
if len(languages) != len(set(languages)):
raise ValidationError({"language": constants.UNIQUE_LANGUAGES_VALIDATION_MESSAGE})
raise ValidationError(
{"language": constants.UNIQUE_LANGUAGES_VALIDATION_MESSAGE}
)
# Custom validation to limit the number of languages per user to `USER_MAX_LANGUAGES_COUNT`.
if len(languages) > constants.USER_MAX_LANGUAGES_COUNT:
raise ValidationError(constants.COUNT_LANGUAGES_VALIDATION_MESSAGE)
Expand All @@ -591,7 +603,9 @@ def _update_user_skills(self, instance: CustomUser, data: list[int]) -> None:
Required count of skills between 1 and `USER_MAX_SKILL_QUANTITY`.
"""
if not (1 <= len(data) <= constants.USER_MAX_SKILL_QUANTITY):
raise serializers.ValidationError(constants.USER_SKILL_QUANTITY_VALIDATIONS_MESSAGE)
raise serializers.ValidationError(
constants.USER_SKILL_QUANTITY_VALIDATIONS_MESSAGE
)

user_content_type = ContentType.objects.get_for_model(CustomUser)

Expand Down Expand Up @@ -624,7 +638,9 @@ def _update_user_skills(self, instance: CustomUser, data: list[int]) -> None:

def _user_skills_quantity_limit_validation(self, instance: CustomUser) -> None:
if instance.skills_count > constants.USER_MAX_SKILL_QUANTITY:
raise serializers.ValidationError(constants.USER_SKILL_QUANTITY_VALIDATIONS_MESSAGE)
raise serializers.ValidationError(
constants.USER_SKILL_QUANTITY_VALIDATIONS_MESSAGE
)

def to_representation(self, instance) -> dict[str, Any]:
"""
Expand Down Expand Up @@ -734,6 +750,50 @@ class Meta:
}


class PublicUserSerializer(serializers.ModelSerializer):
firstName = serializers.CharField(source="first_name")
lastName = serializers.CharField(source="last_name")
skills = serializers.SerializerMethodField()
is_online = serializers.SerializerMethodField()

def get_skills(self, user: CustomUser) -> list:
"""Возвращает список навыков без поля approves"""
skills = []
for sto in getattr(user, "prefetched_skills", []):
skill = sto.skill
skills.append(
{
"id": skill.id,
"name": skill.name,
"category": {"id": skill.category.id, "name": skill.category.name},
}
)
return skills

def get_is_online(self, user: CustomUser) -> bool:
"""Логика проверки онлайн-статуса"""
request = self.context.get("request")
if request and request.user.is_authenticated and request.user.id == user.id:
return True

cache_key = get_user_online_cache_key(user)
return cache.get(cache_key, False)

class Meta:
model = CustomUser
fields = [
"id",
"firstName",
"lastName",
"avatar",
"user_type",
"skills",
"is_online",
"birthday",
"speciality",
]


class UserFeedSerializer(serializers.ModelSerializer, SkillsSerializerMixin):
class Meta:
model = CustomUser
Expand Down
2 changes: 2 additions & 0 deletions users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
AchievementDetail,
AchievementList,
CurrentUser,
PublicUserListView,
SpecialistsList,
UserAdditionalRolesView,
UserDetail,
Expand Down Expand Up @@ -38,6 +39,7 @@
"specialists/", SpecialistsList.as_view()
), # this url actually returns mentors, experts and investors
path("users/", UserList.as_view()),
path('public-users/', PublicUserListView.as_view(), name='public-users'),
path("users/projects/", UserProjectsList.as_view()),
path("users/liked/", LikedProjectList.as_view()),
path("users/roles/", UserAdditionalRolesView.as_view()),
Expand Down
92 changes: 58 additions & 34 deletions users/views.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,86 @@
import jwt
import requests
import urllib.parse

import jwt
import requests
from django.apps import apps
from django.conf import settings
from django.core.cache import cache
from django.utils import timezone
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.db import transaction
from django.db.models import Q
from django.db.models import Prefetch, Q
from django.http import HttpResponse
from django.shortcuts import redirect, get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from django.template.loader import render_to_string
from rest_framework import status, permissions, exceptions
from django.utils import timezone
from django_filters import rest_framework as filters
from drf_yasg.utils import swagger_auto_schema
from rest_framework import exceptions, permissions, status
from rest_framework.generics import (
GenericAPIView,
ListAPIView,
ListCreateAPIView,
RetrieveUpdateDestroyAPIView,
RetrieveAPIView,
RetrieveUpdateDestroyAPIView,
)
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken, TokenError

from django_filters import rest_framework as filters
from drf_yasg.utils import swagger_auto_schema
from weasyprint import HTML

from core.models import SpecializationCategory, Specialization, SkillToObject
from core.models import SkillToObject, Specialization, SpecializationCategory
from core.pagination import Pagination
from core.permissions import IsOwnerOrReadOnly
from events.models import Event
from events.serializers import EventsListSerializer
from partner_programs.models import PartnerProgram
from partner_programs.serializers import (
UserProgramsSerializer,
PartnerProgramListSerializer,
UserProgramsSerializer,
)
from projects.pagination import ProjectsPagination
from projects.serializers import ProjectListSerializer
from users.helpers import (
verify_email,
check_related_fields_update,
force_verify_user,
)
from users.constants import (
VERBOSE_ROLE_TYPES,
VERBOSE_USER_TYPES,
VERIFY_EMAIL_REDIRECT_URL,
OnboardingStage,
)
from users.models import UserAchievement, LikesOnProject, UserSkillConfirmation
from users.helpers import (
check_related_fields_update,
force_verify_user,
verify_email,
)
from users.models import LikesOnProject, UserAchievement, UserSkillConfirmation
from users.permissions import IsAchievementOwnerOrReadOnly
from users.serializers import (
AchievementDetailSerializer,
AchievementListSerializer,
PublicUserSerializer,
RemoteBuySubSerializer,
ResendVerifyEmailSerializer,
SpecializationSerializer,
SpecializationsSerializer,
UserApproveSkillResponse,
UserCloneDataSerializer,
UserDetailSerializer,
UserListSerializer,
VerifyEmailSerializer,
ResendVerifyEmailSerializer,
UserProjectListSerializer,
UserSubscribedProjectsSerializer,
UserSkillConfirmationSerializer,
UserApproveSkillResponse,
SpecializationsSerializer,
SpecializationSerializer,
UserCloneDataSerializer,
UserSubscribedProjectsSerializer,
UserSubscriptionDataSerializer,
RemoteBuySubSerializer,
VerifyEmailSerializer,
)
from users.typing import UserCVDataV2

from .filters import SpecializationFilter, UserFilter
from .helpers import check_chache_for_cv
from .filters import UserFilter, SpecializationFilter
from .pagination import UsersPagination
from .services.verification import VerificationTasks
from .schema import SKILL_PK_PARAM, USER_PK_PARAM
from .services.cv_data_prepare import UserCVDataPreparerV2
from .schema import USER_PK_PARAM, SKILL_PK_PARAM
from .services.verification import VerificationTasks
from .tasks import send_mail_cv

User = get_user_model()
Expand All @@ -97,9 +98,7 @@ def get_permissions(self):
if self.request.method == "POST":
permission_classes = [AllowAny]
else:
permission_classes = [
IsAdminUser
]
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]

def post(self, request, *args, **kwargs):
Expand Down Expand Up @@ -671,3 +670,28 @@ def get(self, request, *args, **kwargs):
cache.set(cache_key, timezone.now(), timeout=cooldown_time)

return Response(data={"detail": "success"}, status=status.HTTP_200_OK)


class PublicUserListView(ListAPIView):
queryset = User.objects.get_active()
serializer_class = PublicUserSerializer
pagination_class = UsersPagination
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = UserFilter
permission_classes = [AllowAny]

def get_queryset(self):
"""Оптимизация запросов для навыков и категорий"""
return (
super()
.get_queryset()
.prefetch_related(
Prefetch(
"skills",
queryset=SkillToObject.objects.select_related(
"skill", "skill__category"
),
to_attr="prefetched_skills",
)
)
)