10 files changed
@@ -143,22 +143,23 @@ def test_notify_rfc_published(self): | |||
| 143 | 143 | self.assertEqual(rfc.title, "RFC " + draft.title) | |
| 144 | 144 | self.assertEqual(rfc.documentauthor_set.count(), 0) | |
| 145 | 145 | 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 | + ], | ||
| 156 | 157 | [ | |
| 157 | 158 | { | |
| 158 | 159 | "titlepage_name": f"titlepage {author.name}", | |
| 159 | 160 | "is_editor": False, | |
| 160 | - "person": author.pk, | ||
| 161 | - "email": author.email_address(), | ||
| 161 | + "person": author, | ||
| 162 | + "email": author.email(), | ||
| 162 | 163 | "affiliation": "Some Affiliation", | |
| 163 | 164 | "country": "CA", | |
| 164 | 165 | } | |
@@ -242,6 +242,6 @@ def is_deleted(self, instance): | |||
| 242 | 242 | ||
| 243 | 243 | class RfcAuthorAdmin(admin.ModelAdmin): | |
| 244 | 244 | 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"] | ||
| 247 | 247 | admin.site.register(RfcAuthor, RfcAuthorAdmin) | |
@@ -42,13 +42,21 @@ class RfcLimitOffsetPagination(LimitOffsetPagination): | |||
| 42 | 42 | max_limit = 500 | |
| 43 | 43 | ||
| 44 | 44 | ||
| 45 | + class NumberInFilter(filters.BaseInFilter, filters.NumberFilter): | ||
| 46 | + """Filter against a comma-separated list of numbers""" | ||
| 47 | + pass | ||
| 48 | + | ||
| 49 | + | ||
| 45 | 50 | class RfcFilter(filters.FilterSet): | |
| 46 | 51 | published = filters.DateFromToRangeFilter() | |
| 47 | 52 | stream = filters.ModelMultipleChoiceFilter( | |
| 48 | 53 | queryset=StreamName.objects.filter(used=True) | |
| 49 | 54 | ) | |
| 55 | + number = NumberInFilter( | ||
| 56 | + field_name="rfc_number" | ||
| 57 | + ) | ||
| 50 | 58 | group = filters.ModelMultipleChoiceFilter( | |
| 51 | - queryset=Group.objects.wgs(), | ||
| 59 | + queryset=Group.objects.all(), | ||
| 52 | 60 | field_name="group__acronym", | |
| 53 | 61 | to_field_name="acronym", | |
| 54 | 62 | ) | |
@@ -391,7 +391,6 @@ class Meta: | |||
| 391 | 391 | lambda obj: " ".join([obj.person.initials(), obj.person.last_name()]) | |
| 392 | 392 | ) | |
| 393 | 393 | person = factory.SubFactory('ietf.person.factories.PersonFactory') | |
| 394 | - email = factory.LazyAttribute(lambda obj: obj.person.email()) | ||
| 395 | 394 | affiliation = factory.Faker('company') | |
| 396 | 395 | order = factory.LazyAttribute(lambda o: o.document.rfcauthor_set.count() + 1) | |
| 397 | 396 | ||
@@ -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 | + ] | ||
@@ -937,7 +937,6 @@ class RfcAuthor(models.Model): | |||
| 937 | 937 | titlepage_name = models.CharField(max_length=128, blank=False) | |
| 938 | 938 | is_editor = models.BooleanField(default=False) | |
| 939 | 939 | 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) | ||
| 941 | 940 | affiliation = models.CharField(max_length=100, blank=True, help_text="Organization/company used by author for submission") | |
| 942 | 941 | country = models.CharField(max_length=255, blank=True, help_text="Country used by author for submission") | |
| 943 | 942 | order = models.IntegerField(default=1) | |
@@ -951,6 +950,11 @@ class Meta: | |||
| 951 | 950 | models.Index(fields=["document", "order"]) | |
| 952 | 951 | ] | |
| 953 | 952 | ||
| 953 | + @property | ||
| 954 | + def email(self) -> Email | None: | ||
| 955 | + return self.person.email() if self.person else None | ||
| 956 | + | ||
| 957 | + | ||
| 954 | 958 | class DocumentAuthorInfo(models.Model): | |
| 955 | 959 | person = ForeignKey(Person) | |
| 956 | 960 | # email should only be null for some historic documents | |
@@ -9,14 +9,20 @@ | |||
| 9 | 9 | from drf_spectacular.utils import extend_schema_field | |
| 10 | 10 | from rest_framework import serializers | |
| 11 | 11 | ||
| 12 | - from ietf.group.serializers import GroupSerializer | ||
| 12 | + from ietf.group.serializers import ( | ||
| 13 | + AreaDirectorSerializer, | ||
| 14 | + AreaSerializer, | ||
| 15 | + GroupSerializer, | ||
| 16 | + ) | ||
| 13 | 17 | from ietf.name.serializers import StreamNameSerializer | |
| 18 | + from ietf.utils import log | ||
| 14 | 19 | from .models import Document, DocumentAuthor, RfcAuthor | |
| 15 | 20 | ||
| 16 | 21 | ||
| 17 | 22 | class RfcAuthorSerializer(serializers.ModelSerializer): | |
| 18 | 23 | """Serializer for an RfcAuthor / DocumentAuthor in a response""" | |
| 19 | 24 | ||
| 25 | + email = serializers.EmailField(source="email.address", read_only=True) | ||
| 20 | 26 | datatracker_person_path = serializers.URLField( | |
| 21 | 27 | source="person.get_absolute_url", | |
| 22 | 28 | required=False, | |
@@ -29,7 +35,7 @@ class Meta: | |||
| 29 | 35 | "titlepage_name", | |
| 30 | 36 | "is_editor", | |
| 31 | 37 | "person", | |
| 32 | - "email", # relies on email.pk being email.address | ||
| 38 | + "email", | ||
| 33 | 39 | "affiliation", | |
| 34 | 40 | "country", | |
| 35 | 41 | "datatracker_person_path", | |
@@ -48,7 +54,6 @@ def to_representation(self, instance): | |||
| 48 | 54 | titlepage_name=document_author.person.plain_name(), | |
| 49 | 55 | is_editor=False, | |
| 50 | 56 | person=document_author.person, | |
| 51 | - email=document_author.email, | ||
| 52 | 57 | affiliation=document_author.affiliation, | |
| 53 | 58 | country=document_author.country, | |
| 54 | 59 | order=document_author.order, | |
@@ -174,10 +179,16 @@ def to_representation(self, instance: Document): | |||
| 174 | 179 | return super().to_representation(instance=RfcStatus.from_document(instance)) | |
| 175 | 180 | ||
| 176 | 181 | ||
| 182 | + class ShepherdSerializer(serializers.Serializer): | ||
| 183 | + email = serializers.EmailField(source="email_address") | ||
| 184 | + | ||
| 185 | + | ||
| 177 | 186 | class RelatedDraftSerializer(serializers.Serializer): | |
| 178 | 187 | id = serializers.IntegerField(source="source.id") | |
| 179 | 188 | name = serializers.CharField(source="source.name") | |
| 180 | 189 | title = serializers.CharField(source="source.title") | |
| 190 | + shepherd = ShepherdSerializer(source="source.shepherd") | ||
| 191 | + ad = AreaDirectorSerializer(source="source.ad") | ||
| 181 | 192 | ||
| 182 | 193 | ||
| 183 | 194 | class RelatedRfcSerializer(serializers.Serializer): | |
@@ -205,15 +216,23 @@ class RfcFormatSerializer(serializers.Serializer): | |||
| 205 | 216 | ||
| 206 | 217 | ||
| 207 | 218 | 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 | + """ | ||
| 209 | 226 | ||
| 210 | 227 | number = serializers.IntegerField(source="rfc_number") | |
| 211 | 228 | published = serializers.DateField() | |
| 212 | 229 | status = RfcStatusSerializer(source="*") | |
| 213 | 230 | authors = serializers.SerializerMethodField() | |
| 214 | 231 | group = GroupSerializer() | |
| 215 | - area = GroupSerializer(source="group.area", required=False) | ||
| 232 | + area = AreaSerializer(source="group.area", required=False) | ||
| 216 | 233 | stream = StreamNameSerializer() | |
| 234 | + ad = AreaDirectorSerializer(read_only=True) | ||
| 235 | + group_list_email = serializers.EmailField(source="group.list_email", read_only=True) | ||
| 217 | 236 | identifiers = serializers.SerializerMethodField() | |
| 218 | 237 | draft = serializers.SerializerMethodField() | |
| 219 | 238 | obsoletes = RelatedRfcSerializer(many=True, read_only=True) | |
@@ -239,6 +258,8 @@ class Meta: | |||
| 239 | 258 | "group", | |
| 240 | 259 | "area", | |
| 241 | 260 | "stream", | |
| 261 | + "ad", | ||
| 262 | + "group_list_email", | ||
| 242 | 263 | "identifiers", | |
| 243 | 264 | "obsoletes", | |
| 244 | 265 | "obsoleted_by", | |
@@ -276,11 +297,20 @@ def get_identifiers(self, doc: Document): | |||
| 276 | 297 | return DocIdentifierSerializer(instance=identifiers, many=True).data | |
| 277 | 298 | ||
| 278 | 299 | @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() | ||
| 284 | 314 | return RelatedDraftSerializer(related_doc).data | |
| 285 | 315 | ||
| 286 | 316 | ||
@@ -1657,7 +1657,7 @@ def extract_name(s): | |||
| 1657 | 1657 | doc.rfcauthor_set | |
| 1658 | 1658 | if doc.type_id == "rfc" and doc.rfcauthor_set.exists() | |
| 1659 | 1659 | else doc.documentauthor_set | |
| 1660 | - ).select_related("person", "email").order_by("order") | ||
| 1660 | + ).select_related("person").prefetch_related("person__email_set").order_by("order") | ||
| 1661 | 1661 | data["authors"] = [ | |
| 1662 | 1662 | { | |
| 1663 | 1663 | "name": author.titlepage_name if hasattr(author, "titlepage_name") else author.person.name, | |
@@ -1,11 +1,37 @@ | |||
| 1 | - # Copyright The IETF Trust 2024, All Rights Reserved | ||
| 1 | + # Copyright The IETF Trust 2024-2026, All Rights Reserved | ||
| 2 | 2 | """django-rest-framework serializers""" | |
| 3 | + | ||
| 4 | + from drf_spectacular.utils import extend_schema_field | ||
| 3 | 5 | from rest_framework import serializers | |
| 4 | 6 | ||
| 5 | - from .models import Group | ||
| 7 | + from ietf.person.models import Email | ||
| 8 | + from .models import Group, Role | ||
| 6 | 9 | ||
| 7 | 10 | ||
| 8 | 11 | class GroupSerializer(serializers.ModelSerializer): | |
| 9 | 12 | class Meta: | |
| 10 | 13 | 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"] | ||
@@ -2528,7 +2528,6 @@ def test_get_qualified_author_queryset(self): | |||
| 2528 | 2528 | document=rfc, | |
| 2529 | 2529 | person=people[0], | |
| 2530 | 2530 | titlepage_name="P. Zero", | |
| 2531 | - email=people[0].email_set.first(), | ||
| 2532 | 2531 | ) | |
| 2533 | 2532 | self.assertCountEqual( | |
| 2534 | 2533 | get_qualified_author_queryset(base_qs, now - 5 * one_year, now), | |
0 commit comments