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
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"
227 changes: 196 additions & 31 deletions ietf/api/serializers_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,32 +216,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 +275,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 +313,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 +504,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