diff --git a/greg/api/serializers/identity.py b/greg/api/serializers/identity.py index bd0ce0ba4e6e0acd155ac5c092b159c0650a6f34..337f5e54332141d3ec3123d10779358592b06721 100644 --- a/greg/api/serializers/identity.py +++ b/greg/api/serializers/identity.py @@ -16,9 +16,3 @@ class IdentitySerializer(serializers.ModelSerializer): if self.is_duplicate(attrs["type"], attrs["value"]): raise ValidationError("Identity already exists") return attrs - - -class SpecialIdentitySerializer(serializers.ModelSerializer): - class Meta: - model = Identity - fields = ["id", "value", "type", "verified_at"] diff --git a/greg/api/serializers/person.py b/greg/api/serializers/person.py index a92a9d576e7c04ea6cb35b2fe3f47fd3e7ee5b7c..5c0a749de0531b3b0406c60725743b5ad25246f0 100644 --- a/greg/api/serializers/person.py +++ b/greg/api/serializers/person.py @@ -1,9 +1,8 @@ from rest_framework import serializers -from rest_framework.fields import BooleanField, CharField, SerializerMethodField from greg.api.serializers.consent import ConsentSerializerBrief -from greg.api.serializers.identity import IdentitySerializer, SpecialIdentitySerializer -from greg.api.serializers.role import RoleSerializer, SpecialRoleSerializer +from greg.api.serializers.identity import IdentitySerializer +from greg.api.serializers.role import RoleSerializer from greg.models import Person @@ -25,54 +24,3 @@ class PersonSerializer(serializers.ModelSerializer): "roles", "consents", ] - - -class SpecialPersonSerializer(serializers.ModelSerializer): - """ - Serializer for the person endpoint - - Can be used to change or add an email to the person - - """ - - pid = CharField(source="id", read_only=True) - first = CharField(source="first_name", read_only=True) - last = CharField(source="last_name", read_only=True) - email = SerializerMethodField(source="private_email") - mobile = SerializerMethodField(source="private_mobile", read_only=True) - fnr = SpecialIdentitySerializer(read_only=True) - passport = SpecialIdentitySerializer(read_only=True) - feide_id = SerializerMethodField(source="feide_id", read_only=True) - active = SerializerMethodField(source="active", read_only=True) - registered = BooleanField(source="is_registered", read_only=True) - verified = BooleanField(source="is_verified", read_only=True) - roles = SpecialRoleSerializer(many=True, read_only=True) - - def get_email(self, obj): - return obj.private_email and obj.private_email.value - - def get_mobile(self, obj): - return obj.private_mobile and obj.private_mobile.value - - def get_active(self, obj): - return obj.is_registered and obj.is_verified - - def get_feide_id(self, obj): - return obj.feide_id and obj.feide_id.value - - class Meta: - model = Person - fields = [ - "pid", - "first", - "last", - "mobile", - "fnr", - "email", - "passport", - "feide_id", - "active", - "registered", - "verified", - "roles", - ] diff --git a/greg/api/serializers/role.py b/greg/api/serializers/role.py index acdb7b4a0151fd5e2b86d727313897995f6eef5a..1ef704c47c66179ee7b89f9986a58e4745989330 100644 --- a/greg/api/serializers/role.py +++ b/greg/api/serializers/role.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from rest_framework.fields import IntegerField, SerializerMethodField +from rest_framework.fields import IntegerField from greg.api.serializers.organizational_unit import OrganizationalUnitSerializer from greg.models import Role, RoleType @@ -42,44 +42,3 @@ class RoleWriteSerializer(RoleSerializer): """ orgunit = IntegerField(source="orgunit_id") # type: ignore - - -class SpecialRoleSerializer(serializers.ModelSerializer): - name_nb = SerializerMethodField(source="type") - name_en = SerializerMethodField(source="type") - ou_nb = SerializerMethodField(source="orgunit") - ou_en = SerializerMethodField(source="orgunit") - max_days = SerializerMethodField(source="type") - - def get_name_nb(self, obj): - return obj.type.name_nb - - def get_name_en(self, obj): - return obj.type.name_en - - def get_ou_nb(self, obj): - return obj.orgunit.name_nb - - def get_ou_en(self, obj): - return obj.orgunit.name_en - - def get_max_days(self, obj): - return obj.type.max_days - - class Meta: - model = Role - fields = [ - "id", - "name_nb", - "name_en", - "ou_nb", - "ou_en", - "start_date", - "end_date", - "max_days", - "contact_person_unit", - "comments", - ] - read_only_fields = [ - "contact_person_unit", - ] diff --git a/gregui/api/serializers/guest.py b/gregui/api/serializers/guest.py index 0cfe4b4c1457dea92b0fb9c1aca368ba7024e03c..33607815dcd10980b0f41fcba35337bf7cbcf237 100644 --- a/gregui/api/serializers/guest.py +++ b/gregui/api/serializers/guest.py @@ -1,19 +1,50 @@ import datetime from django.conf import settings -from django.utils.timezone import now +from django.utils import timezone from rest_framework import serializers from rest_framework.exceptions import ValidationError +from rest_framework.fields import BooleanField, CharField, SerializerMethodField +<<<<<<< HEAD from greg.models import Consent, ConsentChoice, ConsentType, Identity, Person from greg.utils import is_identity_duplicate from gregui.api.serializers.IdentityDuplicateError import IdentityDuplicateError +======= + +from greg.models import ( + Consent, + ConsentChoice, + ConsentType, + Identity, + Person, + InvitationLink, +) +from gregui.api.serializers.identity import PartialIdentitySerializer +from gregui.api.serializers.role import ExtendedRoleSerializer +>>>>>>> 5be5116 (Add an invitation status field to the guest serializer for use in the UI.) from gregui.validation import ( validate_phone_number, validate_norwegian_national_id_number, ) +def create_identity_or_update( + identity_type: Identity.IdentityType, value: str, person: Person +): + existing_identity = person.identities.filter(type=identity_type).first() + if not existing_identity: + Identity.objects.create( + person=person, + type=identity_type, + source=settings.DEFAULT_IDENTITY_SOURCE, + value=value, + ) + else: + existing_identity.value = value + existing_identity.save() + + # pylint: disable=W0223 class GuestConsentChoiceSerializer(serializers.Serializer): type = serializers.CharField(required=True) @@ -107,7 +138,7 @@ class GuestRegisterSerializer(serializers.ModelSerializer): type=consent_type, person=person, choice=choice, - defaults={"consent_given_at": now()}, + defaults={"consent_given_at": timezone.now()}, ) if not created and consent_instance.choice != choice: consent_instance.choice = choice @@ -194,17 +225,64 @@ class GuestRegisterSerializer(serializers.ModelSerializer): read_only_fields = ("id",) -def create_identity_or_update( - identity_type: Identity.IdentityType, value: str, person: Person -): - existing_identity = person.identities.filter(type=identity_type).first() - if not existing_identity: - Identity.objects.create( - person=person, - type=identity_type, - source=settings.DEFAULT_IDENTITY_SOURCE, - value=value, +class GuestSerializer(serializers.ModelSerializer): + """ + Serializer used for presenting guests to sponsors. + """ + + pid = CharField(source="id", read_only=True) + first = CharField(source="first_name", read_only=True) + last = CharField(source="last_name", read_only=True) + email = SerializerMethodField(source="private_email") + mobile = SerializerMethodField(source="private_mobile", read_only=True) + fnr = PartialIdentitySerializer(read_only=True) + passport = PartialIdentitySerializer(read_only=True) + feide_id = SerializerMethodField(source="feide_id", read_only=True) + active = SerializerMethodField(source="active", read_only=True) + registered = BooleanField(source="is_registered", read_only=True) + verified = BooleanField(source="is_verified", read_only=True) + invitation_status = SerializerMethodField( + source="get_invitation_status", read_only=True + ) + roles = ExtendedRoleSerializer(many=True, read_only=True) + + def get_email(self, obj): + return obj.private_email and obj.private_email.value + + def get_mobile(self, obj): + return obj.private_mobile and obj.private_mobile.value + + def get_active(self, obj): + return obj.is_registered and obj.is_verified + + def get_feide_id(self, obj): + return obj.feide_id and obj.feide_id.value + + def get_invitation_status(self, obj): + invitation_links = InvitationLink.objects.filter( + invitation__role__person__id=obj.id ) - else: - existing_identity.value = value - existing_identity.save() + non_expired_links = invitation_links.filter(expire__gt=timezone.now()) + if non_expired_links.count(): + return "active" + if invitation_links.count(): + return "expired" + return "none" + + class Meta: + model = Person + fields = [ + "pid", + "first", + "last", + "mobile", + "fnr", + "email", + "passport", + "feide_id", + "active", + "registered", + "verified", + "invitation_status", + "roles", + ] diff --git a/gregui/api/serializers/identity.py b/gregui/api/serializers/identity.py index 6395cf12fd7cb53459451a2d5a22600b3336b4dc..44412cbe2392b81e86815ccc0701067c6b92c462 100644 --- a/gregui/api/serializers/identity.py +++ b/gregui/api/serializers/identity.py @@ -61,3 +61,9 @@ class IdentitySerializer(serializers.ModelSerializer): attrs["verified_by"] = self._get_sponsor() attrs["verified_at"] = timezone.now() return attrs + + +class PartialIdentitySerializer(serializers.ModelSerializer): + class Meta: + model = Identity + fields = ["id", "value", "type", "verified_at"] diff --git a/gregui/api/serializers/role.py b/gregui/api/serializers/role.py index b092f8b8102a9079fcb9cdd8cc494823f0cc03b1..40b2e8093b8b106c123d3c24404f8d1c76f5121f 100644 --- a/gregui/api/serializers/role.py +++ b/gregui/api/serializers/role.py @@ -1,5 +1,6 @@ import datetime from rest_framework import serializers +from rest_framework.fields import SerializerMethodField from rest_framework.exceptions import ValidationError from rest_framework.validators import UniqueTogetherValidator @@ -89,3 +90,49 @@ class InviteRoleSerializerUi(RoleSerializerUi): "comments", "available_in_search", ] + + +class ExtendedRoleSerializer(serializers.ModelSerializer): + """ + A role serializer with additional human readable names for the + role type and associated organizational unit. + """ + + name_nb = SerializerMethodField(source="type") + name_en = SerializerMethodField(source="type") + ou_nb = SerializerMethodField(source="orgunit") + ou_en = SerializerMethodField(source="orgunit") + max_days = SerializerMethodField(source="type") + + def get_name_nb(self, obj): + return obj.type.name_nb + + def get_name_en(self, obj): + return obj.type.name_en + + def get_ou_nb(self, obj): + return obj.orgunit.name_nb + + def get_ou_en(self, obj): + return obj.orgunit.name_en + + def get_max_days(self, obj): + return obj.type.max_days + + class Meta: + model = Role + fields = [ + "id", + "name_nb", + "name_en", + "ou_nb", + "ou_en", + "start_date", + "end_date", + "max_days", + "contact_person_unit", + "comments", + ] + read_only_fields = [ + "contact_person_unit", + ] diff --git a/gregui/api/views/person.py b/gregui/api/views/person.py index 06ea9d3b52a4dd5e1711bc73cc24b6e7582b1a58..32f027bfd0027f22c74e0eca401dbd1fe21b5081 100644 --- a/gregui/api/views/person.py +++ b/gregui/api/views/person.py @@ -6,13 +6,12 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet -from greg.api.serializers.person import SpecialPersonSerializer from greg.models import Identity, Person from greg.permissions import IsSponsor from greg.utils import is_identity_duplicate from gregui import validation from gregui.api.serializers.IdentityDuplicateError import IdentityDuplicateError -from gregui.api.serializers.guest import create_identity_or_update +from gregui.api.serializers.guest import GuestSerializer, create_identity_or_update from gregui.models import GregUserProfile @@ -30,7 +29,7 @@ class PersonViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericV permission_classes = [IsAuthenticated, IsSponsor] queryset = Person.objects.all() http_methods = ["get", "patch"] - serializer_class = SpecialPersonSerializer + serializer_class = GuestSerializer def update(self, request, *args, **kwargs): """ @@ -169,7 +168,7 @@ class GuestInfoViewSet(mixins.ListModelMixin, GenericViewSet): authentication_classes = [SessionAuthentication, BasicAuthentication] permission_classes = [IsAuthenticated, IsSponsor] - serializer_class = SpecialPersonSerializer + serializer_class = GuestSerializer def get_queryset(self): """ diff --git a/gregui/tests/api/serializers/test_guest.py b/gregui/tests/api/serializers/test_guest.py new file mode 100644 index 0000000000000000000000000000000000000000..82176be4321fc6f21bdf5c99c0f81da3008bb846 --- /dev/null +++ b/gregui/tests/api/serializers/test_guest.py @@ -0,0 +1,58 @@ +from datetime import timedelta + +import pytest +from django.conf import settings +from django.utils import timezone + +from gregui.api.serializers.guest import GuestSerializer + + +@pytest.mark.django_db +def test_serialize_guest(invited_person): + person, _ = invited_person + assert GuestSerializer().to_representation(person) == { + "active": False, + "email": "foo@example.org", + "feide_id": None, + "first": "Foo", + "fnr": None, + "invitation_status": "active", + "last": "Bar", + "mobile": None, + "passport": None, + "pid": "1", + "registered": False, + "roles": [ + { + "id": 1, + "name_nb": "Role Foo NB", + "name_en": "Role Foo EN", + "ou_nb": "Foo NB", + "ou_en": "Foo EN", + "start_date": None, + "end_date": "2050-10-15", + "max_days": 365, + "contact_person_unit": "", + "comments": "", + }, + ], + "verified": False, + } + + +@pytest.mark.django_db +def test_invitation_status(invited_person): + s = GuestSerializer() + person, invitation = invited_person + + # there's an active invitation link + assert s.to_representation(person).get("invitation_status") == "active" + + # the invitation link has expired + invitation.expire = timezone.now() - timedelta(days=settings.INVITATION_DURATION) + invitation.save() + assert s.to_representation(person).get("invitation_status") == "expired" + + # there are no invitation links + invitation.delete() + assert s.to_representation(person).get("invitation_status") == "none"