← 返回首页
feat: more API fields+filtering; drop RfcAuthor.email field (#10432) · ietf-tools/datatracker@c4be631 · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus

Commit c4be631

Browse files
feat: more API fields+filtering; drop RfcAuthor.email field (#10432)
* feat: RfcAuthorSerializer.email is current email * refactor: RfcAuthor email field -> property * feat: more RfcMetadataSerializer fields * shepherd email (with a fallback to the draft) * doc ad email * area ad emails * group list email * fix: filter RFCs by any group type * feat: filter by RFC numbers * fix: shepherd -> draft object in response JSON * fix: consistent filter naming * chore: migration * test: update test_notify_rfc_published * fix: RfcAuthor.email() -> Email, not str * fix: update RfcAuthorFactory * fix: consistent blank value in email() * fix: guard against non-prefetched queryset * test: fix nomcom test * refactor: name-addr -> addr for ad/shepherd Also falls back to current primary email for ad/shepherd if the email on record is inactive.
1 parent 619b2ae commit c4be631

10 files changed

Lines changed: 115 additions & 32 deletions

File tree

‎ietf/api/tests_views_rpc.py‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -143,22 +143,23 @@ def test_notify_rfc_published(self):
143143
self.assertEqual(rfc.title, "RFC " + draft.title)
144144
self.assertEqual(rfc.documentauthor_set.count(), 0)
145145
self.assertEqual(
146-
list(
147-
rfc.rfcauthor_set.values(
148-
"titlepage_name",
149-
"is_editor",
150-
"person",
151-
"email",
152-
"affiliation",
153-
"country",
154-
)
155-
),
146+
[
147+
{
148+
"titlepage_name": ra.titlepage_name,
149+
"is_editor": ra.is_editor,
150+
"person": ra.person,
151+
"email": ra.email,
152+
"affiliation": ra.affiliation,
153+
"country": ra.country,
154+
}
155+
for ra in rfc.rfcauthor_set.all()
156+
],
156157
[
157158
{
158159
"titlepage_name": f"titlepage {author.name}",
159160
"is_editor": False,
160-
"person": author.pk,
161-
"email": author.email_address(),
161+
"person": author,
162+
"email": author.email(),
162163
"affiliation": "Some Affiliation",
163164
"country": "CA",
164165
}

‎ietf/doc/admin.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,6 @@ def is_deleted(self, instance):
242242

243243
class RfcAuthorAdmin(admin.ModelAdmin):
244244
list_display = ['id', 'document', 'titlepage_name', 'person', 'email', 'affiliation', 'country', 'order']
245-
search_fields = ['document__name', 'titlepage_name', 'person__name', 'email__address', 'affiliation', 'country']
246-
raw_id_fields = ["document", "person", "email"]
245+
search_fields = ['document__name', 'titlepage_name', 'person__name', 'email', 'affiliation', 'country']
246+
raw_id_fields = ["document", "person"]
247247
admin.site.register(RfcAuthor, RfcAuthorAdmin)

‎ietf/doc/api.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,21 @@ class RfcLimitOffsetPagination(LimitOffsetPagination):
4242
max_limit = 500
4343

4444

45+
class NumberInFilter(filters.BaseInFilter, filters.NumberFilter):
46+
"""Filter against a comma-separated list of numbers"""
47+
pass
48+
49+
4550
class RfcFilter(filters.FilterSet):
4651
published = filters.DateFromToRangeFilter()
4752
stream = filters.ModelMultipleChoiceFilter(
4853
queryset=StreamName.objects.filter(used=True)
4954
)
55+
number = NumberInFilter(
56+
field_name="rfc_number"
57+
)
5058
group = filters.ModelMultipleChoiceFilter(
51-
queryset=Group.objects.wgs(),
59+
queryset=Group.objects.all(),
5260
field_name="group__acronym",
5361
to_field_name="acronym",
5462
)

‎ietf/doc/factories.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,6 @@ class Meta:
391391
lambda obj: " ".join([obj.person.initials(), obj.person.last_name()])
392392
)
393393
person = factory.SubFactory('ietf.person.factories.PersonFactory')
394-
email = factory.LazyAttribute(lambda obj: obj.person.email())
395394
affiliation = factory.Faker('company')
396395
order = factory.LazyAttribute(lambda o: o.document.rfcauthor_set.count() + 1)
397396

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright The IETF Trust 2026, All Rights Reserved
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("doc", "0030_alter_dochistory_title_alter_document_title"),
9+
]
10+
11+
operations = [
12+
migrations.RemoveField(
13+
model_name="rfcauthor",
14+
name="email",
15+
),
16+
]

‎ietf/doc/models.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,6 @@ class RfcAuthor(models.Model):
937937
titlepage_name = models.CharField(max_length=128, blank=False)
938938
is_editor = models.BooleanField(default=False)
939939
person = ForeignKey(Person, null=True, blank=True, on_delete=models.PROTECT)
940-
email = ForeignKey(Email, help_text="Email address used by author for submission", blank=True, null=True, on_delete=models.PROTECT)
941940
affiliation = models.CharField(max_length=100, blank=True, help_text="Organization/company used by author for submission")
942941
country = models.CharField(max_length=255, blank=True, help_text="Country used by author for submission")
943942
order = models.IntegerField(default=1)
@@ -951,6 +950,11 @@ class Meta:
951950
models.Index(fields=["document", "order"])
952951
]
953952

953+
@property
954+
def email(self) -> Email | None:
955+
return self.person.email() if self.person else None
956+
957+
954958
class DocumentAuthorInfo(models.Model):
955959
person = ForeignKey(Person)
956960
# email should only be null for some historic documents

‎ietf/doc/serializers.py‎

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99
from drf_spectacular.utils import extend_schema_field
1010
from rest_framework import serializers
1111

12-
from ietf.group.serializers import GroupSerializer
12+
from ietf.group.serializers import (
13+
AreaDirectorSerializer,
14+
AreaSerializer,
15+
GroupSerializer,
16+
)
1317
from ietf.name.serializers import StreamNameSerializer
18+
from ietf.utils import log
1419
from .models import Document, DocumentAuthor, RfcAuthor
1520

1621

1722
class RfcAuthorSerializer(serializers.ModelSerializer):
1823
"""Serializer for an RfcAuthor / DocumentAuthor in a response"""
1924

25+
email = serializers.EmailField(source="email.address", read_only=True)
2026
datatracker_person_path = serializers.URLField(
2127
source="person.get_absolute_url",
2228
required=False,
@@ -29,7 +35,7 @@ class Meta:
2935
"titlepage_name",
3036
"is_editor",
3137
"person",
32-
"email", # relies on email.pk being email.address
38+
"email",
3339
"affiliation",
3440
"country",
3541
"datatracker_person_path",
@@ -48,7 +54,6 @@ def to_representation(self, instance):
4854
titlepage_name=document_author.person.plain_name(),
4955
is_editor=False,
5056
person=document_author.person,
51-
email=document_author.email,
5257
affiliation=document_author.affiliation,
5358
country=document_author.country,
5459
order=document_author.order,
@@ -174,10 +179,16 @@ def to_representation(self, instance: Document):
174179
return super().to_representation(instance=RfcStatus.from_document(instance))
175180

176181

182+
class ShepherdSerializer(serializers.Serializer):
183+
email = serializers.EmailField(source="email_address")
184+
185+
177186
class RelatedDraftSerializer(serializers.Serializer):
178187
id = serializers.IntegerField(source="source.id")
179188
name = serializers.CharField(source="source.name")
180189
title = serializers.CharField(source="source.title")
190+
shepherd = ShepherdSerializer(source="source.shepherd")
191+
ad = AreaDirectorSerializer(source="source.ad")
181192

182193

183194
class RelatedRfcSerializer(serializers.Serializer):
@@ -205,15 +216,23 @@ class RfcFormatSerializer(serializers.Serializer):
205216

206217

207218
class RfcMetadataSerializer(serializers.ModelSerializer):
208-
"""Serialize metadata of an RFC"""
219+
"""Serialize metadata of an RFC
220+
221+
This needs to be called with a Document queryset that has been processed with
222+
api.augment_rfc_queryset() or it very likely will not work. Some of the typing
223+
refers to Document, but this should really be WithAnnotations[Document, ...].
224+
However, have not been able to make that work yet.
225+
"""
209226

210227
number = serializers.IntegerField(source="rfc_number")
211228
published = serializers.DateField()
212229
status = RfcStatusSerializer(source="*")
213230
authors = serializers.SerializerMethodField()
214231
group = GroupSerializer()
215-
area = GroupSerializer(source="group.area", required=False)
232+
area = AreaSerializer(source="group.area", required=False)
216233
stream = StreamNameSerializer()
234+
ad = AreaDirectorSerializer(read_only=True)
235+
group_list_email = serializers.EmailField(source="group.list_email", read_only=True)
217236
identifiers = serializers.SerializerMethodField()
218237
draft = serializers.SerializerMethodField()
219238
obsoletes = RelatedRfcSerializer(many=True, read_only=True)
@@ -239,6 +258,8 @@ class Meta:
239258
"group",
240259
"area",
241260
"stream",
261+
"ad",
262+
"group_list_email",
242263
"identifiers",
243264
"obsoletes",
244265
"obsoleted_by",
@@ -276,11 +297,20 @@ def get_identifiers(self, doc: Document):
276297
return DocIdentifierSerializer(instance=identifiers, many=True).data
277298

278299
@extend_schema_field(RelatedDraftSerializer)
279-
def get_draft(self, object):
280-
try:
281-
related_doc = object.drafts[0]
282-
except IndexError:
283-
return None
300+
def get_draft(self, doc: Document):
301+
if hasattr(doc, "drafts"):
302+
# This is the expected case - drafts is added by a Prefetch in
303+
# the augment_rfc_queryset() method.
304+
try:
305+
related_doc = doc.drafts[0]
306+
except IndexError:
307+
return None
308+
else:
309+
# Fallback in case augment_rfc_queryset() was not called
310+
log.log(
311+
f"Warning: {self.__class__}.get_draft() called without prefetched draft"
312+
)
313+
related_doc = doc.came_from_draft()
284314
return RelatedDraftSerializer(related_doc).data
285315

286316

‎ietf/doc/views_doc.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1657,7 +1657,7 @@ def extract_name(s):
16571657
doc.rfcauthor_set
16581658
if doc.type_id == "rfc" and doc.rfcauthor_set.exists()
16591659
else doc.documentauthor_set
1660-
).select_related("person", "email").order_by("order")
1660+
).select_related("person").prefetch_related("person__email_set").order_by("order")
16611661
data["authors"] = [
16621662
{
16631663
"name": author.titlepage_name if hasattr(author, "titlepage_name") else author.person.name,

‎ietf/group/serializers.py‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
1-
# Copyright The IETF Trust 2024, All Rights Reserved
1+
# Copyright The IETF Trust 2024-2026, All Rights Reserved
22
"""django-rest-framework serializers"""
3+
4+
from drf_spectacular.utils import extend_schema_field
35
from rest_framework import serializers
46

5-
from .models import Group
7+
from ietf.person.models import Email
8+
from .models import Group, Role
69

710

811
class GroupSerializer(serializers.ModelSerializer):
912
class Meta:
1013
model = Group
11-
fields = ["acronym", "name", "type"]
14+
fields = ["acronym", "name", "type", "list_email"]
15+
16+
17+
class AreaDirectorSerializer(serializers.Serializer):
18+
"""Serialize an area director
19+
20+
Works with Email or Role
21+
"""
22+
23+
email = serializers.SerializerMethodField()
24+
25+
@extend_schema_field(serializers.EmailField)
26+
def get_email(self, instance: Email | Role):
27+
if isinstance(instance, Role):
28+
return instance.email.email_address()
29+
return instance.email_address()
30+
31+
32+
class AreaSerializer(serializers.ModelSerializer):
33+
ads = AreaDirectorSerializer(many=True, read_only=True)
34+
35+
class Meta:
36+
model = Group
37+
fields = ["acronym", "name", "type", "ads"]

‎ietf/nomcom/tests.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2528,7 +2528,6 @@ def test_get_qualified_author_queryset(self):
25282528
document=rfc,
25292529
person=people[0],
25302530
titlepage_name="P. Zero",
2531-
email=people[0].email_set.first(),
25322531
)
25332532
self.assertCountEqual(
25342533
get_qualified_author_queryset(base_qs, now - 5 * one_year, now),

0 commit comments

Comments
 (0)

Footer

© 2026 GitHub, Inc.