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
58 changes: 40 additions & 18 deletions ietf/api/serializers_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,21 @@
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.utils import default_consensus, prettify_std_name, update_action_holders
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,
update_rfcauthors,
)
from ietf.group.models import Group
from ietf.name.models import StreamName, StdLevelName, FormalLanguageName
from ietf.person.models import Person
Expand Down Expand Up @@ -201,22 +213,32 @@ class Meta:
read_only_fields = ["id", "name"]


class AuthorSerializer(serializers.ModelSerializer):
"""Serialize an RfcAuthor record
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")

todo fix naming confusion with ietf.doc.serializers.RfcAuthorSerializer
"""
class Meta:
model = RfcAuthor
fields = [
"id",
"titlepage_name",
"is_editor",
"person",
"email",
"affiliation",
"country",
]
model = Document
fields = ["id", "authors"]

def update(self, instance, validated_data):
assert isinstance(instance, Document)
authors_data = validated_data.pop("rfcauthor_set", None)
if authors_data is not None:
# Construct unsaved instances from validated author data
new_authors = [RfcAuthor(**ad) for ad in authors_data]
# Update the RFC with the new author set
with transaction.atomic():
change_events = update_rfcauthors(instance, new_authors)
for event in change_events:
event.save()
return instance


class RfcPubSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -273,7 +295,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
Expand Down
29 changes: 8 additions & 21 deletions ietf/api/views_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from rest_framework.pagination import LimitOffsetPagination

from ietf.api.serializers_rpc import (
AuthorSerializer,
PersonSerializer,
FullDraftSerializer,
DraftSerializer,
Expand All @@ -33,8 +32,10 @@
RfcWithAuthorsSerializer,
DraftWithAuthorsSerializer,
NotificationAckSerializer, RfcPubSerializer, RfcFileSerializer,
EditableRfcSerializer,
)
from ietf.doc.models import Document, DocHistory, RfcAuthor
from ietf.doc.models import Document, DocHistory, RfcAuthor, EditedRfcAuthorsDocEvent
from ietf.doc.serializers import RfcAuthorSerializer
from ietf.person.models import Email, Person


Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -320,15 +323,15 @@ def post(self, request):
return Response(DraftSerializer(docs, many=True).data)


class RfcAuthorViewSet(viewsets.ModelViewSet):
class RfcAuthorViewSet(viewsets.ReadOnlyModelViewSet):
"""ViewSet for RfcAuthor model

Router needs to provide rfc_number as a kwarg
"""
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"

Expand All @@ -342,22 +345,6 @@ def get_queryset(self):
)
)

def perform_create(self, serializer):
rfc = Document.objects.filter(
type_id="rfc",
rfc_number=self.kwargs[self.rfc_number_param]
).first()
if rfc is None:
raise NotFound("RFC not found")
# Find the current highest order for this document
from django.db.models import Max
max_order = (
RfcAuthor.objects.filter(document=rfc)
.aggregate(max_order=Max("order", default=0))
.get("max_order")
)
serializer.save(document=rfc, order=max_order + 1)


class RfcPubNotificationView(APIView):
api_key_endpoint = "ietf.api.views_rpc"
Expand Down
4 changes: 3 additions & 1 deletion ietf/doc/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
ReviewAssignmentDocEvent, IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
BofreqEditorDocEvent, BofreqResponsibleDocEvent, StoredObject, RfcAuthor )
BofreqEditorDocEvent, BofreqResponsibleDocEvent, StoredObject, RfcAuthor,
EditedRfcAuthorsDocEvent)

from ietf.utils.validators import validate_external_resource_value

Expand Down Expand Up @@ -173,6 +174,7 @@ def short_desc(self, obj):
admin.site.register(TelechatDocEvent, DocEventAdmin)
admin.site.register(InitialReviewDocEvent, DocEventAdmin)
admin.site.register(EditedAuthorsDocEvent, DocEventAdmin)
admin.site.register(EditedRfcAuthorsDocEvent, DocEventAdmin)
admin.site.register(IanaExpertDocEvent, DocEventAdmin)

class BallotPositionDocEventAdmin(DocEventAdmin):
Expand Down
30 changes: 30 additions & 0 deletions ietf/doc/migrations/0029_editedrfcauthorsdocevent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright The IETF Trust 2025, All Rights Reserved

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("doc", "0028_rfcauthor"),
]

operations = [
migrations.CreateModel(
name="EditedRfcAuthorsDocEvent",
fields=[
(
"docevent_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="doc.docevent",
),
),
],
bases=("doc.docevent",),
),
]
5 changes: 5 additions & 0 deletions ietf/doc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1693,6 +1693,11 @@ class EditedAuthorsDocEvent(DocEvent):
"""
basis = models.CharField(help_text="What is the source or reasoning for the changes to the author list",max_length=255)


class EditedRfcAuthorsDocEvent(DocEvent):
"""Change to the RfcAuthor list for a document"""


class BofreqEditorDocEvent(DocEvent):
""" Capture the proponents of a BOF Request."""
editors = models.ManyToManyField('person.Person', blank=True)
Expand Down
30 changes: 28 additions & 2 deletions ietf/doc/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL,
IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
BofreqEditorDocEvent, BofreqResponsibleDocEvent, StoredObject, RfcAuthor)
IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
BofreqEditorDocEvent, BofreqResponsibleDocEvent, StoredObject, RfcAuthor,
EditedRfcAuthorsDocEvent)

from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
class BallotTypeResource(ModelResource):
Expand Down Expand Up @@ -650,6 +651,31 @@ class Meta:
api.doc.register(EditedAuthorsDocEventResource())



from ietf.person.resources import PersonResource
class EditedRfcAuthorsDocEventResource(ModelResource):
by = ToOneField(PersonResource, 'by')
doc = ToOneField(DocumentResource, 'doc')
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
class Meta:
queryset = EditedRfcAuthorsDocEvent.objects.all()
serializer = api.Serializer()
cache = SimpleCache()
#resource_name = 'editedrfcauthorsdocevent'
ordering = ['id', ]
filtering = {
"id": ALL,
"time": ALL,
"type": ALL,
"rev": ALL,
"desc": ALL,
"by": ALL_WITH_RELATIONS,
"doc": ALL_WITH_RELATIONS,
"docevent_ptr": ALL_WITH_RELATIONS,
}
api.doc.register(EditedRfcAuthorsDocEventResource())


from ietf.name.resources import DocUrlTagNameResource
class DocumentURLResource(ModelResource):
doc = ToOneField(DocumentResource, 'doc')
Expand Down
23 changes: 21 additions & 2 deletions ietf/doc/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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:
Expand Down
Loading