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
2 changes: 1 addition & 1 deletion dev/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/ietf-tools/datatracker-app-base:20260211T1901
FROM ghcr.io/ietf-tools/datatracker-app-base:20260304T1633
LABEL maintainer="IETF Tools Team <tools-discuss@ietf.org>"

ENV DEBIAN_FRONTEND=noninteractive
Expand Down
2 changes: 1 addition & 1 deletion dev/build/TARGET_BASE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20260211T1901
20260304T1633
9 changes: 8 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ services:
# network_mode: service:db

depends_on:
- blobdb
- blobstore
- db
- mq
- blobstore

ipc: host

Expand Down Expand Up @@ -79,7 +80,10 @@ services:
command:
- '--loglevel=INFO'
depends_on:
- blobdb
- blobstore
- db
- mq
restart: unless-stopped
stop_grace_period: 1m
volumes:
Expand All @@ -102,7 +106,10 @@ services:
- '--concurrency=1'

depends_on:
- blobdb
- blobstore
- db
- mq
restart: unless-stopped
stop_grace_period: 1m
volumes:
Expand Down
4 changes: 4 additions & 0 deletions docker/configs/settings_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,7 @@
"ietf.api.red_api" : ["devtoken", "redtoken"], # Not a real secret
"ietf.api.views_rpc" : ["devtoken"], # Not a real secret
}

# Errata system api configuration
ERRATA_METADATA_NOTIFICATION_URL = "http://host.docker.internal:8808/api/rfc_metadata_update/"
ERRATA_METADATA_NOTIFICATION_API_KEY = "not a real secret"
230 changes: 199 additions & 31 deletions ietf/api/serializers_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
update_rfcauthors,
)
from ietf.group.models import Group
from ietf.group.serializers import AreaSerializer
from ietf.name.models import StreamName, StdLevelName
from ietf.person.models import Person
from ietf.utils import log
Expand Down Expand Up @@ -115,6 +116,7 @@ class FullDraftSerializer(serializers.ModelSerializer):
name = serializers.CharField(max_length=255)
title = serializers.CharField(max_length=255)
group = serializers.SlugRelatedField(slug_field="acronym", read_only=True)
area = AreaSerializer(read_only=True)

# Other fields we need to add / adjust
source_format = serializers.SerializerMethodField()
Expand All @@ -133,6 +135,7 @@ class Meta:
"stream",
"title",
"group",
"area",
"abstract",
"pages",
"source_format",
Expand Down Expand Up @@ -216,32 +219,24 @@ 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")
def _update_authors(rfc, authors_data):
# Construct unsaved instances from validated author data
new_authors = [RfcAuthor(**authdata) for authdata in authors_data]
# Update the RFC with the new author set
with transaction.atomic():
change_events = update_rfcauthors(rfc, new_authors)
for event in change_events:
event.save()
return change_events

class Meta:
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 SubseriesNameField(serializers.RegexField):

def __init__(self, **kwargs):
# pattern: no leading 0, finite length (arbitrarily set to 5 digits)
regex = r"^(bcp|std|fyi)[1-9][0-9]{0,4}$"
super().__init__(regex, **kwargs)



class RfcPubSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -283,13 +278,7 @@ class RfcPubSerializer(serializers.ModelSerializer):
slug_field="rfc_number",
queryset=Document.objects.filter(type_id="rfc"),
)
subseries = serializers.ListField(
child=serializers.RegexField(
required=False,
# pattern: no leading 0, finite length (arbitrarily set to 5 digits)
regex=r"^(bcp|std|fyi)[1-9][0-9]{0,4}$",
)
)
subseries = serializers.ListField(child=SubseriesNameField(required=False))
# N.b., authors is _not_ a field on Document!
authors = RfcAuthorSerializer(many=True)

Expand Down Expand Up @@ -327,6 +316,9 @@ def validate(self, data):
)
return data

def update(self, instance, validated_data):
raise RuntimeError("Cannot update with this serializer")

def create(self, validated_data):
"""Publish an RFC"""
published = validated_data.pop("published")
Expand Down Expand Up @@ -515,6 +507,182 @@ def _create_rfc(self, validated_data):
return rfc


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.
#
# Should also consider whether this and RfcPubSerializer should merge.
#
# Treats published and subseries fields as write-only. This isn't quite correct,
# but makes it easier and we don't currently use the serialized value except for
# debugging.
published = serializers.DateTimeField(
default_timezone=datetime.timezone.utc,
write_only=True,
)
authors = RfcAuthorSerializer(many=True, min_length=1, source="rfcauthor_set")
subseries = serializers.ListField(
child=SubseriesNameField(required=False),
write_only=True,
)

class Meta:
model = Document
fields = [
"published",
"title",
"authors",
"stream",
"abstract",
"pages",
"std_level",
"subseries",
]

def create(self, validated_data):
raise RuntimeError("Cannot create with this serializer")

def update(self, instance, validated_data):
assert isinstance(instance, Document)
assert instance.type_id == "rfc"
rfc = instance # get better name

system_person = Person.objects.get(name="(System)")

# Remove data that needs special handling. Use a singleton object to detect
# missing values in case we ever support a value that needs None as an option.
omitted = object()
published = validated_data.pop("published", omitted)
subseries = validated_data.pop("subseries", omitted)
authors_data = validated_data.pop("rfcauthor_set", omitted)

# Transaction to clean up if something fails
with transaction.atomic():
# update the rfc Document itself
rfc_changes = []
rfc_events = []

for attr, new_value in validated_data.items():
old_value = getattr(rfc, attr)
if new_value != old_value:
rfc_changes.append(
f"changed {attr} to '{new_value}' from '{old_value}'"
)
setattr(rfc, attr, new_value)
if len(rfc_changes) > 0:
rfc_change_summary = f"{', '.join(rfc_changes)}"
rfc_events.append(
DocEvent.objects.create(
doc=rfc,
rev=rfc.rev,
by=system_person,
type="sync_from_rfc_editor",
desc=f"Changed metadata: {rfc_change_summary}",
)
)
if authors_data is not omitted:
rfc_events.extend(_update_authors(instance, authors_data))

if published is not omitted:
published_event = rfc.latest_event(type="published_rfc")
if published_event is None:
# unexpected, but possible in theory
rfc_events.append(
DocEvent.objects.create(
doc=rfc,
rev=rfc.rev,
type="published_rfc",
time=published,
by=system_person,
desc="RFC published",
)
)
rfc_events.append(
DocEvent.objects.create(
doc=rfc,
rev=rfc.rev,
type="sync_from_rfc_editor",
by=system_person,
desc=(
f"Set publication timestamp to {published.isoformat()}"
),
)
)
else:
original_pub_time = published_event.time
if published != original_pub_time:
published_event.time = published
published_event.save()
rfc_events.append(
DocEvent.objects.create(
doc=rfc,
rev=rfc.rev,
type="sync_from_rfc_editor",
by=system_person,
desc=(
f"Changed publication time to "
f"{published.isoformat()} from "
f"{original_pub_time.isoformat()}"
)
)
)

# update subseries relations
if subseries is not omitted:
for subseries_doc_name in subseries:
ss_slug = subseries_doc_name[:3]
subseries_doc, ss_doc_created = Document.objects.get_or_create(
type_id=ss_slug, name=subseries_doc_name
)
if ss_doc_created:
subseries_doc.docevent_set.create(
type=f"{ss_slug}_doc_created",
by=system_person,
desc=f"Created {subseries_doc_name} via update of {rfc.name}",
)
_, ss_rel_created = subseries_doc.relateddocument_set.get_or_create(
relationship_id="contains", target=rfc
)
if ss_rel_created:
subseries_doc.docevent_set.create(
type="sync_from_rfc_editor",
by=system_person,
desc=f"Added {rfc.name} to {subseries_doc.name}",
)
rfc_events.append(
rfc.docevent_set.create(
type="sync_from_rfc_editor",
by=system_person,
desc=f"Added {rfc.name} to {subseries_doc.name}",
)
)
# Delete subseries relations that are no longer current
stale_subseries_relations = rfc.relations_that("contains").exclude(
source__name__in=subseries
)
for stale_relation in stale_subseries_relations:
stale_subseries_doc = stale_relation.source
rfc_events.append(
rfc.docevent_set.create(
type="sync_from_rfc_editor",
by=system_person,
desc=f"Removed {rfc.name} from {stale_subseries_doc.name}",
)
)
stale_subseries_doc.docevent_set.create(
type="sync_from_rfc_editor",
by=system_person,
desc=f"Removed {rfc.name} from {stale_subseries_doc.name}",
)
stale_subseries_relations.delete()
if len(rfc_events) > 0:
rfc.save_with_history(rfc_events)
return rfc


class RfcFileSerializer(serializers.Serializer):
# The structure of this serializer is constrained by what openapi-generator-cli's
# python generator can correctly serialize as multipart/form-data. It does not
Expand Down
Loading
Loading