120 lines
4.5 KiB
Python
120 lines
4.5 KiB
Python
|
|
"""Read-only serializers for profile payloads."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from rest_framework import serializers
|
||
|
|
|
||
|
|
from apps.profiles.models import Company, Education, Experience, Profile, School, Skill
|
||
|
|
from apps.profiles.privacy import mask_emails, mask_phones
|
||
|
|
|
||
|
|
|
||
|
|
class SkillSerializer(serializers.ModelSerializer):
|
||
|
|
class Meta:
|
||
|
|
model = Skill
|
||
|
|
fields = ("id", "name", "slug", "profile_count")
|
||
|
|
|
||
|
|
|
||
|
|
class CompanySerializer(serializers.ModelSerializer):
|
||
|
|
class Meta:
|
||
|
|
model = Company
|
||
|
|
fields = ("id", "name", "industry", "size", "founded", "website", "location_name")
|
||
|
|
|
||
|
|
|
||
|
|
class SchoolSerializer(serializers.ModelSerializer):
|
||
|
|
class Meta:
|
||
|
|
model = School
|
||
|
|
fields = ("id", "name", "type", "location_name")
|
||
|
|
|
||
|
|
|
||
|
|
class ExperienceSerializer(serializers.ModelSerializer):
|
||
|
|
company = CompanySerializer(read_only=True)
|
||
|
|
|
||
|
|
class Meta:
|
||
|
|
model = Experience
|
||
|
|
fields = (
|
||
|
|
"id", "title", "title_role", "title_sub_role", "levels", "company", "company_name",
|
||
|
|
"start_date", "end_date", "start_year", "end_year", "is_primary", "location_name",
|
||
|
|
"summary",
|
||
|
|
) # fmt: skip
|
||
|
|
|
||
|
|
|
||
|
|
class EducationSerializer(serializers.ModelSerializer):
|
||
|
|
school = SchoolSerializer(read_only=True)
|
||
|
|
|
||
|
|
class Meta:
|
||
|
|
model = Education
|
||
|
|
fields = (
|
||
|
|
"id", "school", "school_name", "degrees", "majors", "minors", "gpa",
|
||
|
|
"start_date", "end_date", "end_year",
|
||
|
|
) # fmt: skip
|
||
|
|
|
||
|
|
|
||
|
|
class PIIMixin:
|
||
|
|
"""Masks contact details unless the caller is allowed to see them."""
|
||
|
|
|
||
|
|
def _can_view_pii(self) -> bool:
|
||
|
|
request = self.context.get("request") # type: ignore[attr-defined]
|
||
|
|
user = getattr(request, "user", None)
|
||
|
|
return bool(user and user.is_authenticated and user.can_view_pii)
|
||
|
|
|
||
|
|
def get_emails(self, obj: Profile) -> list[dict[str, str]]:
|
||
|
|
return obj.emails if self._can_view_pii() else mask_emails(obj.emails)
|
||
|
|
|
||
|
|
def get_phone_numbers(self, obj: Profile) -> list[str]:
|
||
|
|
return obj.phone_numbers if self._can_view_pii() else mask_phones(obj.phone_numbers)
|
||
|
|
|
||
|
|
|
||
|
|
class ProfileListSerializer(PIIMixin, serializers.ModelSerializer):
|
||
|
|
"""Compact representation used for result cards."""
|
||
|
|
|
||
|
|
headline = serializers.CharField(read_only=True)
|
||
|
|
company = serializers.CharField(source="current_company.name", default="", read_only=True)
|
||
|
|
skills = serializers.SerializerMethodField()
|
||
|
|
emails = serializers.SerializerMethodField()
|
||
|
|
|
||
|
|
class Meta:
|
||
|
|
model = Profile
|
||
|
|
fields = (
|
||
|
|
"id", "full_name", "headline", "job_title", "job_title_role", "seniority_levels",
|
||
|
|
"company", "industry", "location_name", "location_country", "years_experience",
|
||
|
|
"linkedin_url", "skills", "emails",
|
||
|
|
) # fmt: skip
|
||
|
|
|
||
|
|
def get_skills(self, obj: Profile) -> list[str]:
|
||
|
|
# ``prefetch_related`` in the view keeps this at one extra query total.
|
||
|
|
return [link.skill.name for link in obj.profile_skills.all()[:10]]
|
||
|
|
|
||
|
|
|
||
|
|
class ProfileDetailSerializer(PIIMixin, serializers.ModelSerializer):
|
||
|
|
headline = serializers.CharField(read_only=True)
|
||
|
|
current_company = CompanySerializer(read_only=True)
|
||
|
|
skills = serializers.SerializerMethodField()
|
||
|
|
experiences = ExperienceSerializer(many=True, read_only=True)
|
||
|
|
educations = EducationSerializer(many=True, read_only=True)
|
||
|
|
emails = serializers.SerializerMethodField()
|
||
|
|
phone_numbers = serializers.SerializerMethodField()
|
||
|
|
pii_masked = serializers.SerializerMethodField()
|
||
|
|
|
||
|
|
class Meta:
|
||
|
|
model = Profile
|
||
|
|
fields = (
|
||
|
|
"id", "external_id", "full_name", "first_name", "last_name", "gender", "headline",
|
||
|
|
"job_title", "job_title_role", "job_title_sub_role", "seniority_levels",
|
||
|
|
"current_company", "industry", "job_summary", "job_start_date",
|
||
|
|
"location_name", "location_locality", "location_region", "location_country",
|
||
|
|
"location_continent", "summary", "years_experience", "inferred_salary",
|
||
|
|
"connections", "interests", "languages", "linkedin_url", "linkedin_username",
|
||
|
|
"github_url", "twitter_url", "skills", "experiences", "educations",
|
||
|
|
"emails", "phone_numbers", "pii_masked",
|
||
|
|
) # fmt: skip
|
||
|
|
|
||
|
|
def get_skills(self, obj: Profile) -> list[dict[str, Any]]:
|
||
|
|
return [
|
||
|
|
{"name": link.skill.name, "slug": link.skill.slug} for link in obj.profile_skills.all()
|
||
|
|
]
|
||
|
|
|
||
|
|
def get_pii_masked(self, obj: Profile) -> bool: # noqa: ARG002
|
||
|
|
return not self._can_view_pii()
|