From f2c29579255a8e6e46eba6fad4b4a4698c4f0249 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Tue, 20 Jun 2023 15:28:16 -0300 Subject: [PATCH] refactor: Represent RFCs as their own DocType (#5835) * feat: Add RFC DocTypeName, StateType, and States * refactor: Rename rfc_number() to deprecated_rfc_number() * feat: Add rfc_number field to DocInfo * feat: Add DocRelationshipName "became-rfc" * chore: First-pass migration to create rfc Documents * chore: create_rfc_documents migration depends on new names * refactor: Rename variable * fix: Fix revname / downcase name for "became-rfc" DocRelationshipName * chore: Remove debugging print statements * feat: Point rfc aliases at rfc Documents * test: Refactor RFC factories * refactor: Rewrite is_rfc() in terms of type_id * test: Use RfcFactory as base for IndividualRfcFactory * refactor: Replace calls to deprecated_rfc_number() * refactor: Remove deprecated_rfc_number() method * test: Import WgRfcFactory --- ietf/api/tests.py | 6 +- ietf/api/views.py | 5 +- ietf/bin/rfc-editor-index-updates | 2 +- ietf/doc/factories.py | 126 +++++------------- ietf/doc/forms.py | 4 +- ietf/doc/migrations/0005_add_rfc_states.py | 27 ++++ ...chistory_rfc_number_document_rfc_number.py | 22 +++ .../migrations/0007_create_rfc_documents.py | 79 +++++++++++ ietf/doc/models.py | 33 ++--- ietf/doc/tests.py | 8 +- ietf/doc/utils.py | 12 +- ietf/doc/utils_search.py | 16 +-- ietf/doc/views_ballot.py | 4 +- ietf/doc/views_doc.py | 2 +- ietf/doc/views_downref.py | 4 +- ietf/doc/views_search.py | 4 +- ietf/group/views.py | 5 +- ietf/iesg/views.py | 4 +- ietf/name/fixtures/names.json | 57 ++++++++ .../name/migrations/0004_rfc_doctype_names.py | 30 +++++ ietf/name/models.py | 2 +- ietf/settings.py | 2 +- ietf/templates/doc/idnits2-state.txt | 6 +- 23 files changed, 301 insertions(+), 159 deletions(-) create mode 100644 ietf/doc/migrations/0005_add_rfc_states.py create mode 100644 ietf/doc/migrations/0006_dochistory_rfc_number_document_rfc_number.py create mode 100644 ietf/doc/migrations/0007_create_rfc_documents.py create mode 100644 ietf/name/migrations/0004_rfc_doctype_names.py diff --git a/ietf/api/tests.py b/ietf/api/tests.py index 2285fa153..f1333ccf7 100644 --- a/ietf/api/tests.py +++ b/ietf/api/tests.py @@ -24,7 +24,7 @@ import debug # pyflakes:ignore import ietf from ietf.doc.utils import get_unicode_document_content from ietf.doc.models import RelatedDocument, State -from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory +from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, WgRfcFactory from ietf.group.factories import RoleFactory from ietf.meeting.factories import MeetingFactory, SessionFactory from ietf.meeting.models import Session @@ -968,9 +968,9 @@ class RfcdiffSupportTests(TestCase): draft.set_state(State.objects.get(type_id='draft',slug='rfc')) draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub')) draft = reload_db_objects(draft) - rfc = draft + rfc = WgRfcFactory(group=draft.group) # todo link this with its pre-publication draft - number = rfc.rfc_number() + number = rfc.rfc_number received = self.getJson(dict(name=number)) self.assertEqual( received, diff --git a/ietf/api/views.py b/ietf/api/views.py index b56544c43..a6372be17 100644 --- a/ietf/api/views.py +++ b/ietf/api/views.py @@ -340,10 +340,7 @@ def rfcdiff_latest_json(request, name, rev=None): response['name']=doc.canonical_name() if doc.name != doc.canonical_name(): prev_rev = doc.rev - # not sure what to do if non-numeric values come back, so at least log it - log.assertion('doc.rfc_number().isdigit()') # .rfc_number() is expensive... - log.assertion('doc.rev.isdigit()') - if int(doc.rfc_number()) in HAS_TOMBSTONE and prev_rev != '00': + if doc.rfc_number in HAS_TOMBSTONE and prev_rev != '00': prev_rev = f'{(int(doc.rev)-1):02d}' response['previous'] = f'{doc.name}-{prev_rev}' response['previous_url'] = get_previous_url(doc.name, prev_rev) diff --git a/ietf/bin/rfc-editor-index-updates b/ietf/bin/rfc-editor-index-updates index dc7abe26b..356c78d22 100755 --- a/ietf/bin/rfc-editor-index-updates +++ b/ietf/bin/rfc-editor-index-updates @@ -84,7 +84,7 @@ for changes, doc, rfc_published in ietf.sync.rfceditor.update_docs_from_rfc_inde new_rfcs.append(doc) for c in changes: - log("RFC%s, %s: %s" % (doc.rfcnum, doc.name, c)) + log("RFC%s, %s: %s" % (doc.rfc_number, doc.name, c)) sys.exit(0) diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index 95fbedfaa..cdd2bc980 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -119,6 +119,29 @@ class DocumentFactory(BaseDocumentFactory): group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none') +class RfcFactory(BaseDocumentFactory): + type_id = "rfc" + rfc_number = factory.Sequence(lambda n: n + 1000) + name = factory.LazyAttribute(lambda o: f"rfc{o.rfc_number:04d}") + expires = None + + @factory.post_generation + def states(obj, create, extracted, **kwargs): + if not create: + return + if extracted: + for (state_type_id,state_slug) in extracted: + obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug)) + else: + obj.set_state(State.objects.get(type_id='rfc',slug='published')) + + @factory.post_generation + def reset_canonical_name(obj, create, extracted, **kwargs): + if hasattr(obj, '_canonical_name'): + del obj._canonical_name + return None + + class IndividualDraftFactory(BaseDocumentFactory): type_id = 'draft' @@ -137,28 +160,11 @@ class IndividualDraftFactory(BaseDocumentFactory): obj.set_state(State.objects.get(type_id='draft',slug='active')) obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) -class IndividualRfcFactory(IndividualDraftFactory): +class IndividualRfcFactory(RfcFactory): + group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none') - alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000))) - - @factory.post_generation - def states(obj, create, extracted, **kwargs): - if not create: - return - if extracted: - for (state_type_id,state_slug) in extracted: - obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug)) - else: - obj.set_state(State.objects.get(type_id='draft',slug='rfc')) - - @factory.post_generation - def reset_canonical_name(obj, create, extracted, **kwargs): - if hasattr(obj, '_canonical_name'): - del obj._canonical_name - return None class WgDraftFactory(BaseDocumentFactory): - type_id = 'draft' group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='wg') stream_id = 'ietf' @@ -177,30 +183,12 @@ class WgDraftFactory(BaseDocumentFactory): obj.set_state(State.objects.get(type_id='draft-stream-ietf',slug='wg-doc')) obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) -class WgRfcFactory(WgDraftFactory): - - alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000))) +class WgRfcFactory(RfcFactory): + group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='wg') + stream_id = 'ietf' std_level_id = 'ps' - @factory.post_generation - def states(obj, create, extracted, **kwargs): - if not create: - return - if extracted: - for (state_type_id,state_slug) in extracted: - obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug)) - if not obj.get_state('draft-iesg'): - obj.set_state(State.objects.get(type_id='draft-iesg', slug='pub')) - else: - obj.set_state(State.objects.get(type_id='draft',slug='rfc')) - obj.set_state(State.objects.get(type_id='draft-iesg', slug='pub')) - - @factory.post_generation - def reset_canonical_name(obj, create, extracted, **kwargs): - if hasattr(obj, '_canonical_name'): - del obj._canonical_name - return None class RgDraftFactory(BaseDocumentFactory): @@ -223,34 +211,11 @@ class RgDraftFactory(BaseDocumentFactory): obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) -class RgRfcFactory(RgDraftFactory): - - alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000))) - +class RgRfcFactory(RfcFactory): + group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='rg') + stream_id = 'irtf' std_level_id = 'inf' - @factory.post_generation - def states(obj, create, extracted, **kwargs): - if not create: - return - if extracted: - for (state_type_id,state_slug) in extracted: - obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug)) - if not obj.get_state('draft-stream-irtf'): - obj.set_state(State.objects.get(type_id='draft-stream-irtf', slug='pub')) - if not obj.get_state('draft-iesg'): - obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) - else: - obj.set_state(State.objects.get(type_id='draft',slug='rfc')) - obj.set_state(State.objects.get(type_id='draft-stream-irtf', slug='pub')) - obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) - - @factory.post_generation - def reset_canonical_name(obj, create, extracted, **kwargs): - if hasattr(obj, '_canonical_name'): - del obj._canonical_name - return None - class CharterFactory(BaseDocumentFactory): @@ -551,30 +516,5 @@ class EditorialDraftFactory(BaseDocumentFactory): obj.set_state(State.objects.get(type_id='draft-stream-editorial',slug='active')) obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) -class EditorialRfcFactory(RgDraftFactory): - - alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000))) - - std_level_id = 'inf' - - @factory.post_generation - def states(obj, create, extracted, **kwargs): - if not create: - return - if extracted: - for (state_type_id,state_slug) in extracted: - obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug)) - if not obj.get_state('draft-stream-editorial'): - obj.set_state(State.objects.get(type_id='draft-stream-editorial', slug='pub')) - if not obj.get_state('draft-iesg'): - obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) - else: - obj.set_state(State.objects.get(type_id='draft',slug='rfc')) - obj.set_state(State.objects.get(type_id='draft-stream-editorial', slug='pub')) - obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists')) - - @factory.post_generation - def reset_canonical_name(obj, create, extracted, **kwargs): - if hasattr(obj, '_canonical_name'): - del obj._canonical_name - return None +class EditorialRfcFactory(RgRfcFactory): + pass diff --git a/ietf/doc/forms.py b/ietf/doc/forms.py index c0c52571c..cac22e410 100644 --- a/ietf/doc/forms.py +++ b/ietf/doc/forms.py @@ -175,7 +175,7 @@ class AddDownrefForm(forms.Form): drafts = self.cleaned_data['drafts'] for da in drafts: if RelatedDocument.objects.filter(source=da.document, target=rfc, relationship_id='downref-approval'): - v_err_pairs.append(da.name + " --> RFC " + rfc.document.rfc_number()) + v_err_pairs.append(f"{da.name} --> RFC {rfc.document.rfc_number}") if v_err_pairs: raise forms.ValidationError("Downref is already in the registry: " + ", ".join(v_err_pairs)) @@ -189,7 +189,7 @@ class AddDownrefForm(forms.Form): else: v_err_refnorm = da.name if v_err_refnorm: - v_err_refnorm_prefix = "There does not seem to be a normative reference to RFC " + rfc.document.rfc_number() + " by " + v_err_refnorm_prefix = f"There does not seem to be a normative reference to RFC {rfc.document.rfc_number} by " raise forms.ValidationError(v_err_refnorm_prefix + v_err_refnorm) diff --git a/ietf/doc/migrations/0005_add_rfc_states.py b/ietf/doc/migrations/0005_add_rfc_states.py new file mode 100644 index 000000000..6fb73dcc4 --- /dev/null +++ b/ietf/doc/migrations/0005_add_rfc_states.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.2 on 2023-06-14 20:57 + +from django.db import migrations + + +def forward(apps, schema_editor): + StateType = apps.get_model("doc", "StateType") + rfc_statetype, _ = StateType.objects.get_or_create(slug="rfc", label="State") + + State = apps.get_model("doc", "State") + created_state, _ = State.objects.get_or_create( + type=rfc_statetype, slug="created", name="Created", used=True, order=1 + ) + published_state, _ = State.objects.get_or_create( + type=rfc_statetype, slug="published", name="Published", used=True, order=2 + ) + created_state.next_states.add(published_state) + + +class Migration(migrations.Migration): + dependencies = [ + ("doc", "0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more"), + ] + + operations = [ + migrations.RunPython(forward), + ] diff --git a/ietf/doc/migrations/0006_dochistory_rfc_number_document_rfc_number.py b/ietf/doc/migrations/0006_dochistory_rfc_number_document_rfc_number.py new file mode 100644 index 000000000..efb77ce70 --- /dev/null +++ b/ietf/doc/migrations/0006_dochistory_rfc_number_document_rfc_number.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.2 on 2023-06-14 22:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("doc", "0005_add_rfc_states"), + ] + + operations = [ + migrations.AddField( + model_name="dochistory", + name="rfc_number", + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name="document", + name="rfc_number", + field=models.PositiveIntegerField(blank=True, null=True), + ), + ] diff --git a/ietf/doc/migrations/0007_create_rfc_documents.py b/ietf/doc/migrations/0007_create_rfc_documents.py new file mode 100644 index 000000000..e2a47cb1a --- /dev/null +++ b/ietf/doc/migrations/0007_create_rfc_documents.py @@ -0,0 +1,79 @@ +# Generated by Django 4.2.2 on 2023-06-15 15:27 + +from django.db import migrations + + +def forward(apps, schema_editor): + Document = apps.get_model("doc", "Document") + DocAlias = apps.get_model("doc", "DocAlias") + DocumentAuthor = apps.get_model("doc", "DocumentAuthor") + + State = apps.get_model("doc", "State") + draft_rfc_state = State.objects.get(type_id="draft", slug="rfc") + rfc_published_state = State.objects.get(type_id="rfc", slug="published") + + DocTypeName = apps.get_model("name", "DocTypeName") + rfc_doctype = DocTypeName(slug="rfc") + + # Find draft Documents in the "rfc" state + found_by_state = Document.objects.filter(states=draft_rfc_state).distinct() + + # Find Documents with an "rfc..." alias and confirm they're the same set + rfc_docaliases = DocAlias.objects.filter(name__startswith="rfc") + found_by_name = Document.objects.filter(docalias__in=rfc_docaliases).distinct() + assert set(found_by_name) == set(found_by_state), "mismatch between rfcs identified by state and docalias" + + # As of 2023-06-15, there is one Document with two rfc aliases: rfc6312 and rfc6342 are the same Document. This + # was due to a publication error. We'll handle that specially. + + for rfc_alias in rfc_docaliases.order_by("name"): + assert rfc_alias.docs.count() == 1, f"DocAlias {rfc_alias} is linked to more than 1 Document" + draft = rfc_alias.docs.first() + if draft.name.startswith("rfc"): + rfc = draft + rfc.type = rfc_doctype + rfc.rfc_number = int(draft.name[3:]) + rfc.save() + rfc.states.set([rfc_published_state]) + # Alias already points at the rfc document + else: + rfc = Document.objects.create( + type=rfc_doctype, + name=rfc_alias.name, + rfc_number=int(rfc_alias.name[3:]), + title=draft.title, + abstract=draft.abstract, + pages=draft.pages, + words=draft.words, + std_level=draft.std_level, + external_url=draft.external_url, + uploaded_filename=draft.uploaded_filename, + note=draft.note, + ) + rfc.states.set([rfc_published_state]) + rfc.formal_languages.set(draft.formal_languages.all()) + + # Copy Authors + for da in draft.documentauthor_set.all(): + DocumentAuthor.objects.create( + document=rfc, + person=da.person, + email=da.email, + affiliation=da.affiliation, + country=da.country, + order=da.order, + ) + + # Point alias at the new rfc Document + rfc_alias.docs.set([rfc]) + + +class Migration(migrations.Migration): + dependencies = [ + ("doc", "0006_dochistory_rfc_number_document_rfc_number"), + ("name", "0004_rfc_doctype_names"), + ] + + operations = [ + migrations.RunPython(forward), + ] diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 547e40f4f..5492f42a4 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -124,6 +124,7 @@ class DocumentInfo(models.Model): uploaded_filename = models.TextField(blank=True) note = models.TextField(blank=True) internal_comments = models.TextField(blank=True) + rfc_number = models.PositiveIntegerField(blank=True, null=True) # only valid for type="rfc" def file_extension(self): if not hasattr(self, '_cached_extension'): @@ -334,7 +335,9 @@ class DocumentInfo(models.Model): if not state: return "Unknown state" - if self.type_id == 'draft': + if self.type_id == "rfc": + return f"RFC {self.rfc_number} ({self.std_level})" + elif self.type_id == 'draft': iesg_state = self.get_state("draft-iesg") iesg_state_summary = None if iesg_state: @@ -345,7 +348,13 @@ class DocumentInfo(models.Model): iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate) if state.slug == "rfc": - return "RFC %s (%s)" % (self.rfc_number(), self.std_level) + # todo check this once became-rfc relationships are actually created + rfcs = self.related_that("became-rfc") # should be only one + if len(rfcs) > 0: + rfc = rfcs[0] + return f"Became RFC {rfc.rfc_number} ({rfc.std_level})" + else: + return "Became RFC" elif state.slug == "repl": rs = self.related_that("replaces") if rs: @@ -376,25 +385,7 @@ class DocumentInfo(models.Model): return state.name def is_rfc(self): - if not hasattr(self, '_cached_is_rfc'): - self._cached_is_rfc = self.pk and self.type_id == 'draft' and self.states.filter(type='draft',slug='rfc').exists() - return self._cached_is_rfc - - def rfc_number(self): - if not hasattr(self, '_cached_rfc_number'): - self._cached_rfc_number = None - if self.is_rfc(): - n = self.canonical_name() - if n.startswith("rfc"): - self._cached_rfc_number = n[3:] - else: - if isinstance(self,Document): - logger.error("Document self.is_rfc() is True but self.canonical_name() is %s" % n) - return self._cached_rfc_number - - @property - def rfcnum(self): - return self.rfc_number() + return self.type_id == "rfc" def author_list(self): best_addresses = [] diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 47c4e146c..d60e817b0 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -792,12 +792,12 @@ Man Expires September 22, 2015 [Page 3] r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.canonical_name()))) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEqual(q('title').text(), f'RFC {rfc.rfc_number()} - {rfc.title}') + self.assertEqual(q('title').text(), f'RFC {rfc.rfc_number} - {rfc.title}') # synonyms for the rfc should be redirected to its canonical view - r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.rfc_number()))) + r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.rfc_number))) self.assertRedirects(r, urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.canonical_name()))) - r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=f'RFC {rfc.rfc_number()}'))) + r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=f'RFC {rfc.rfc_number}'))) self.assertRedirects(r, urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.canonical_name()))) # expired draft @@ -1954,7 +1954,7 @@ class DocTestCase(TestCase): std_level_id = 'ps', time = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE)), ) - num = rfc.rfc_number() + num = rfc.rfc_number DocEventFactory.create( doc=rfc, type='published_rfc', diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index 6c9409475..4d4e91ea7 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -1003,7 +1003,7 @@ def build_file_urls(doc: Union[Document, DocHistory]): if doc.type_id != 'draft': return [], [] - if doc.get_state_slug() == "rfc": + if doc.is_rfc(): name = doc.canonical_name() base_path = os.path.join(settings.RFC_PATH, name + ".") possible_types = settings.RFC_FILE_TYPES @@ -1022,7 +1022,7 @@ def build_file_urls(doc: Union[Document, DocHistory]): if "txt" in found_types: file_urls.append(("htmlized", urlreverse('ietf.doc.views_doc.document_html', kwargs=dict(name=name)))) if doc.tags.filter(slug="verified-errata").exists(): - file_urls.append(("with errata", settings.RFC_EDITOR_INLINE_ERRATA_URL.format(rfc_number=doc.rfc_number()))) + file_urls.append(("with errata", settings.RFC_EDITOR_INLINE_ERRATA_URL.format(rfc_number=doc.rfc_number))) file_urls.append(("bibtex", urlreverse('ietf.doc.views_doc.document_bibtex',kwargs=dict(name=name)))) elif doc.rev: base_path = os.path.join(settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR, doc.name + "-" + doc.rev + ".") @@ -1111,16 +1111,16 @@ def generate_idnits2_rfc_status(): 'unkn': 'U', } - rfcs = Document.objects.filter(type_id='draft',states__slug='rfc',states__type='draft') + rfcs = Document.objects.filter(type_id='rfc',states__slug='published',states__type='rfc') for rfc in rfcs: - offset = int(rfc.rfcnum)-1 + offset = int(rfc.rfc_number)-1 blob[offset] = symbols[rfc.std_level_id] if rfc.related_that('obs'): blob[offset] = 'O' # Workarounds for unusual states in the datatracker - # Document.get(docalias='rfc6312').rfcnum == 6342 + # Document.get(docalias='rfc6312').rfc_number == 6342 # 6312 was published with the wrong rfc number in it # weird workaround in the datatracker - there are two # DocAliases starting with rfc - the canonical name code @@ -1141,7 +1141,7 @@ def generate_idnits2_rfc_status(): def generate_idnits2_rfcs_obsoleted(): obsdict = defaultdict(list) for r in RelatedDocument.objects.filter(relationship_id='obs'): - obsdict[int(r.target.document.rfc_number())].append(int(r.source.rfc_number())) + obsdict[int(r.target.document.rfc_number)].append(int(r.source.rfc_number)) for k in obsdict: obsdict[k] = sorted(obsdict[k]) return render_to_string('doc/idnits2-rfcs-obsoleted.txt', context={'obsitems':sorted(obsdict.items())}) diff --git a/ietf/doc/utils_search.py b/ietf/doc/utils_search.py index 00046ed30..249a9cf7c 100644 --- a/ietf/doc/utils_search.py +++ b/ietf/doc/utils_search.py @@ -93,7 +93,7 @@ def fill_in_document_table_attributes(docs, have_telechat_date=False): # emulate canonical name which is used by a lot of the utils # d.canonical_name = wrap_value(rfc_aliases[d.pk] if d.pk in rfc_aliases else d.name) - if d.rfc_number() != None and d.latest_event_cache["published_rfc"]: + if d.is_rfc() and d.latest_event_cache["published_rfc"]: d.latest_revision_date = d.latest_event_cache["published_rfc"].time elif d.latest_event_cache["new_revision"]: d.latest_revision_date = d.latest_event_cache["new_revision"].time @@ -198,7 +198,7 @@ def prepare_document_table(request, docs, query=None, max_results=200): res = [] - rfc_num = d.rfc_number() + rfc_num = num(d.rfc_number) if d.rfc_number else None if d.type_id == "draft": res.append(num(["Active", "Expired", "Replaced", "Withdrawn", "RFC"].index(d.search_heading.split()[0]))) @@ -213,23 +213,23 @@ def prepare_document_table(request, docs, query=None, max_results=200): elif sort_key == "date": res.append(str(d.latest_revision_date.astimezone(ZoneInfo(settings.TIME_ZONE)))) elif sort_key == "status": - if rfc_num != None: - res.append(num(rfc_num)) + if rfc_num is not None: + res.append(rfc_num) else: res.append(num(d.get_state().order) if d.get_state() else None) elif sort_key == "ipr": res.append(len(d.ipr())) elif sort_key == "ad": - if rfc_num != None: - res.append(num(rfc_num)) + if rfc_num is not None: + res.append(rfc_num) elif d.get_state_slug() == "active": if d.get_state("draft-iesg"): res.append(d.get_state("draft-iesg").order) else: res.append(0) else: - if rfc_num != None: - res.append(num(rfc_num)) + if rfc_num is not None: + res.append(rfc_num) else: res.append(d.canonical_name()) diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index 86c30e22a..cc4420e56 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -968,12 +968,12 @@ def approve_downrefs(request, name): c = DocEvent(type="downref_approved", doc=rel.source, rev=rel.source.rev, by=login) c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % ( - rel.target.document.rfc_number(), rel.source, rel.source.rev) + rel.target.document.rfc_number, rel.source, rel.source.rev) c.save() c = DocEvent(type="downref_approved", doc=rel.target.document, rev=rel.target.document.rev, by=login) c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % ( - rel.target.document.rfc_number(), rel.source, rel.source.rev) + rel.target.document.rfc_number, rel.source, rel.source.rev) c.save() return HttpResponseRedirect(doc.get_absolute_url()) diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 39c8bf19b..0ea74e005 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -1074,7 +1074,7 @@ def document_bibtex(request, name, rev=None): # This needs to be replaced with a lookup, as the mapping may change # over time. Probably by updating ietf/sync/rfceditor.py to add the # as a DocAlias, and use a method on Document to retrieve it. - doi = "10.17487/RFC%04d" % int(doc.rfc_number()) + doi = f"10.17487/RFC{doc.rfc_number:04d}" else: doi = None diff --git a/ietf/doc/views_downref.py b/ietf/doc/views_downref.py index 1b7b51edb..1e4c987cb 100644 --- a/ietf/doc/views_downref.py +++ b/ietf/doc/views_downref.py @@ -44,12 +44,12 @@ def downref_registry_add(request): c = DocEvent(type="downref_approved", doc=da.document, rev=da.document.rev, by=login) c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % ( - rfc.document.rfc_number(), da.name, da.document.rev) + rfc.document.rfc_number, da.name, da.document.rev) c.save() c = DocEvent(type="downref_approved", doc=rfc.document, rev=rfc.document.rev, by=login) c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % ( - rfc.document.rfc_number(), da.name, da.document.rev) + rfc.document.rfc_number, da.name, da.document.rev) c.save() return HttpResponseRedirect(urlreverse('ietf.doc.views_downref.downref_registry')) diff --git a/ietf/doc/views_search.py b/ietf/doc/views_search.py index c500e702e..7b144051c 100644 --- a/ietf/doc/views_search.py +++ b/ietf/doc/views_search.py @@ -409,8 +409,8 @@ def shorten_group_name(name): def ad_dashboard_sort_key(doc): - if doc.type.slug=='draft' and doc.get_state_slug('draft') == 'rfc': - return "21%04d" % int(doc.rfc_number()) + if doc.type.slug=='rfc' and doc.get_state_slug('rfc') == 'published': + return "21%04d" % int(doc.rfc_number) if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'appr-sent': return "22%d" % 0 # TODO - get the date of the transition into this state here if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'): diff --git a/ietf/group/views.py b/ietf/group/views.py index 88b25a109..b6ec6cd04 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -534,9 +534,8 @@ def group_documents_txt(request, acronym, group_type=None): rows = [] for d in itertools.chain(docs, docs_related): - rfc_number = d.rfc_number() - if rfc_number != None: - name = rfc_number + if d.is_rfc(): + name = str(d.rfc_number) else: name = "%s-%s" % (d.name, d.rev) diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index cec22719d..2d83efbbd 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -151,8 +151,8 @@ def agenda_json(request, date=None): if doc.type_id == "draft": docinfo['rev'] = doc.rev docinfo['intended-std-level'] = str(doc.intended_std_level) - if doc.rfc_number(): - docinfo['rfc-number'] = doc.rfc_number() + if doc.is_rfc(): + docinfo['rfc-number'] = doc.rfc_number iana_state = doc.get_state("draft-iana-review") if iana_state and iana_state.slug in ("not-ok", "changed", "need-rev"): diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index 1fbdfa5e9..ce91cef22 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -2539,6 +2539,34 @@ "model": "doc.state", "pk": 174 }, + { + "fields": { + "desc": "", + "name": "Created", + "next_states": [ + 176 + ], + "order": 1, + "slug": "created", + "type": "rfc", + "used": true + }, + "model": "doc.state", + "pk": 175 + }, + { + "fields": { + "desc": "", + "name": "Published", + "next_states": [], + "order": 2, + "slug": "published", + "type": "rfc", + "used": true + }, + "model": "doc.state", + "pk": 176 + }, { "fields": { "label": "State" @@ -2721,6 +2749,13 @@ "model": "doc.statetype", "pk": "review" }, + { + "fields": { + "label": "State" + }, + "model": "doc.statetype", + "pk": "rfc" + }, { "fields": { "label": "Shepherd's Writeup State" @@ -9890,6 +9925,17 @@ "model": "name.dbtemplatetypename", "pk": "rst" }, + { + "fields": { + "desc": "", + "name": "Became RFC", + "order": 0, + "revname": "Became RFC as", + "used": true + }, + "model": "name.docrelationshipname", + "pk": "became-rfc" + }, { "fields": { "desc": "", @@ -10560,6 +10606,17 @@ "model": "name.doctypename", "pk": "review" }, + { + "fields": { + "desc": "", + "name": "RFC", + "order": 0, + "prefix": "rfc", + "used": true + }, + "model": "name.doctypename", + "pk": "rfc" + }, { "fields": { "desc": "", diff --git a/ietf/name/migrations/0004_rfc_doctype_names.py b/ietf/name/migrations/0004_rfc_doctype_names.py new file mode 100644 index 000000000..634a224c5 --- /dev/null +++ b/ietf/name/migrations/0004_rfc_doctype_names.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.2 on 2023-06-14 20:39 + +from django.db import migrations + + +def forward(apps, schema_editor): + DocTypeName = apps.get_model("name", "DocTypeName") + DocTypeName.objects.get_or_create( + slug="rfc", + name="RFC", + used=True, + prefix="rfc", + ) + + DocRelationshipName = apps.get_model("name", "DocRelationshipName") + DocRelationshipName.objects.get_or_create( + slug="became-rfc", + name="became RFC", + used=True, + revname="came from draft", + ) + +class Migration(migrations.Migration): + dependencies = [ + ("name", "0003_populate_telechatagendasectionname"), + ] + + operations = [ + migrations.RunPython(forward), + ] diff --git a/ietf/name/models.py b/ietf/name/models.py index b9e75e8f9..4b750a62a 100644 --- a/ietf/name/models.py +++ b/ietf/name/models.py @@ -42,7 +42,7 @@ class DocRelationshipName(NameModel): class DocTypeName(NameModel): """Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email, - Review, Issue, Wiki""" + Review, Issue, Wiki, RFC""" prefix = models.CharField(max_length=16, default="") class DocTagName(NameModel): """Waiting for Reference, IANA Coordination, Revised ID Needed, diff --git a/ietf/settings.py b/ietf/settings.py index 15ded9662..709038cbe 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -705,7 +705,7 @@ LIAISON_ATTACH_URL = 'https://www.ietf.org/lib/dt/documents/LIAISON/' # should e DOC_HREFS = { "charter": "https://www.ietf.org/charter/{doc.name}-{doc.rev}.txt", "draft": "https://www.ietf.org/archive/id/{doc.name}-{doc.rev}.txt", - "rfc": "https://www.rfc-editor.org/rfc/rfc{doc.rfcnum}.txt", + "rfc": "https://www.rfc-editor.org/rfc/rfc{doc.rfc_number}.txt", "slides": "https://www.ietf.org/slides/{doc.name}-{doc.rev}", "procmaterials": "https://www.ietf.org/procmaterials/{doc.name}-{doc.rev}", "conflrev": "https://www.ietf.org/cr/{doc.name}-{doc.rev}.txt", diff --git a/ietf/templates/doc/idnits2-state.txt b/ietf/templates/doc/idnits2-state.txt index 770654979..8b2cefdf0 100644 --- a/ietf/templates/doc/idnits2-state.txt +++ b/ietf/templates/doc/idnits2-state.txt @@ -1,7 +1,7 @@ {% load ietf_filters %}{% filter linebreaks_lf %}{% comment %} -{% endcomment %}Doc-tag: {{doc.name}};datatracker{% if doc.rfcnum %} -Doc-rfcnum: {{doc.rfcnum}}{% endif %} +{% endcomment %}Doc-tag: {{doc.name}};datatracker{% if doc.is_rfc %} +Doc-rfcnum: {{doc.rfc_number}}{% endif %} Doc-created: {{doc.created|date:"Y-m-d"}};datatracker{% if doc.deststatus %} Doc-deststatus: {{doc.deststatus}};datatracker{% endif %} Doc-rev: {{doc.rev}};datatracker -{% endfilter %} \ No newline at end of file +{% endfilter %}