From 6b1661ba04d3b0d8d44155fdd0e00a9acdbbe0fb Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 2 Dec 2025 15:12:50 -0400 Subject: [PATCH 1/2] refactor: combine redundant serializers --- ietf/api/serializers_rpc.py | 24 +++--------------------- ietf/api/views_rpc.py | 4 ++-- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/ietf/api/serializers_rpc.py b/ietf/api/serializers_rpc.py index 98f8693514..9aef8bb3f6 100644 --- a/ietf/api/serializers_rpc.py +++ b/ietf/api/serializers_rpc.py @@ -10,8 +10,8 @@ from rest_framework import serializers from ietf.doc.expire import move_draft_files_to_archive -from ietf.doc.models import DocumentAuthor, Document, RfcAuthor, RelatedDocument, State, \ - DocEvent +from ietf.doc.models import DocumentAuthor, Document, RelatedDocument, State, DocEvent +from ietf.doc.serializers import RfcAuthorSerializer from ietf.doc.utils import default_consensus, prettify_std_name, update_action_holders from ietf.group.models import Group from ietf.name.models import StreamName, StdLevelName, FormalLanguageName @@ -201,24 +201,6 @@ class Meta: read_only_fields = ["id", "name"] -class AuthorSerializer(serializers.ModelSerializer): - """Serialize an RfcAuthor record - - todo fix naming confusion with ietf.doc.serializers.RfcAuthorSerializer - """ - class Meta: - model = RfcAuthor - fields = [ - "id", - "titlepage_name", - "is_editor", - "person", - "email", - "affiliation", - "country", - ] - - class RfcPubSerializer(serializers.ModelSerializer): # publication-related fields published = serializers.DateTimeField(default_timezone=datetime.timezone.utc) @@ -273,7 +255,7 @@ class RfcPubSerializer(serializers.ModelSerializer): regex=r"^(bcp|std|fyi)[1-9][0-9]{0,4}$", ) ) - authors = AuthorSerializer(many=True) + authors = RfcAuthorSerializer(many=True) class Meta: model = Document diff --git a/ietf/api/views_rpc.py b/ietf/api/views_rpc.py index 7e5feed6e6..e41434ddc1 100644 --- a/ietf/api/views_rpc.py +++ b/ietf/api/views_rpc.py @@ -22,7 +22,6 @@ from rest_framework.pagination import LimitOffsetPagination from ietf.api.serializers_rpc import ( - AuthorSerializer, PersonSerializer, FullDraftSerializer, DraftSerializer, @@ -35,6 +34,7 @@ NotificationAckSerializer, RfcPubSerializer, RfcFileSerializer, ) from ietf.doc.models import Document, DocHistory, RfcAuthor +from ietf.doc.serializers import RfcAuthorSerializer from ietf.person.models import Email, Person @@ -328,7 +328,7 @@ class RfcAuthorViewSet(viewsets.ModelViewSet): api_key_endpoint = "ietf.api.views_rpc" queryset = RfcAuthor.objects.all() - serializer_class = AuthorSerializer + serializer_class = RfcAuthorSerializer lookup_url_kwarg = "author_id" rfc_number_param = "rfc_number" From 74fabc2237aa754d26112df00bfb5110fd5afe96 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 2 Dec 2025 22:22:47 -0400 Subject: [PATCH 2/2] feat: edit authors via RFC update API --- ietf/api/serializers_rpc.py | 43 ++++++++++++++++++++++++++++++++++++- ietf/api/views_rpc.py | 5 ++++- ietf/doc/serializers.py | 23 ++++++++++++++++++-- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/ietf/api/serializers_rpc.py b/ietf/api/serializers_rpc.py index 9aef8bb3f6..de83775ec1 100644 --- a/ietf/api/serializers_rpc.py +++ b/ietf/api/serializers_rpc.py @@ -10,7 +10,14 @@ from rest_framework import serializers from ietf.doc.expire import move_draft_files_to_archive -from ietf.doc.models import DocumentAuthor, Document, RelatedDocument, State, DocEvent +from ietf.doc.models import ( + DocumentAuthor, + Document, + RelatedDocument, + State, + DocEvent, + RfcAuthor, +) from ietf.doc.serializers import RfcAuthorSerializer from ietf.doc.utils import default_consensus, prettify_std_name, update_action_holders from ietf.group.models import Group @@ -201,6 +208,40 @@ class Meta: read_only_fields = ["id", "name"] +class EditableRfcSerializer(serializers.ModelSerializer): + # Would be nice to reconcile this with ietf.doc.serializers.RfcSerializer. + # The purposes of that serializer (representing data for Red) and this one + # (accepting updates from Purple) are different enough that separate formats + # may be needed, but if not it'd be nice to have a single RfcSerializer that + # can serve both. + # + # For now, only handles authors + authors = RfcAuthorSerializer(many=True, min_length=1, source="rfcauthor_set") + + class Meta: + model = Document + fields = ["id", "authors"] + + def update(self, instance, validated_data): + authors_data = validated_data.pop("rfcauthor_set", None) + if authors_data is not None: + # Construct unsaved instances from validated author data + new_authors = [] + for count, author_data in enumerate(authors_data): + new_authors.append( + RfcAuthor( + document=instance, + order=count + 1, + **author_data, + ) + ) + # Commit author changes + with transaction.atomic(): + instance.rfcauthor_set.all().delete() + for author in new_authors: + author.save() + return instance + class RfcPubSerializer(serializers.ModelSerializer): # publication-related fields published = serializers.DateTimeField(default_timezone=datetime.timezone.utc) diff --git a/ietf/api/views_rpc.py b/ietf/api/views_rpc.py index e41434ddc1..dedd27cb1f 100644 --- a/ietf/api/views_rpc.py +++ b/ietf/api/views_rpc.py @@ -32,6 +32,7 @@ RfcWithAuthorsSerializer, DraftWithAuthorsSerializer, NotificationAckSerializer, RfcPubSerializer, RfcFileSerializer, + EditableRfcSerializer, ) from ietf.doc.models import Document, DocHistory, RfcAuthor from ietf.doc.serializers import RfcAuthorSerializer @@ -269,9 +270,11 @@ def bulk_authors(self, request): responses=OriginalStreamSerializer(many=True), ) ) -class RfcViewSet(viewsets.GenericViewSet): +class RfcViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet): queryset = Document.objects.filter(type_id="rfc") api_key_endpoint = "ietf.api.views_rpc" + lookup_field = "rfc_number" + serializer_class = EditableRfcSerializer @action(detail=False, serializer_class=OriginalStreamSerializer) def rfc_original_stream(self, request): diff --git a/ietf/doc/serializers.py b/ietf/doc/serializers.py index ef47a43511..6c431a46f3 100644 --- a/ietf/doc/serializers.py +++ b/ietf/doc/serializers.py @@ -15,7 +15,6 @@ class RfcAuthorSerializer(serializers.ModelSerializer): """Serializer for a DocumentAuthor in a response""" - email = serializers.EmailField(source="email.address", required=False) datatracker_person_path = serializers.URLField( source="person.get_absolute_url", required=False, @@ -28,7 +27,7 @@ class Meta: "titlepage_name", "is_editor", "person", - "email", + "email", # relies on email.pk being email.address "affiliation", "country", "datatracker_person_path", @@ -54,6 +53,26 @@ def to_representation(self, instance): ) return super().to_representation(instance) + def validate(self, data): + email = data.get("email") + if email is not None: + person = data.get("person") + if person is None: + raise serializers.ValidationError( + { + "email": "cannot have an email without a person", + }, + code="email-without-person", + ) + if email.person_id != person.pk: + raise serializers.ValidationError( + { + "email": "email must belong to person", + }, + code="email-person-mismatch", + ) + return data + @dataclass class DocIdentifier: