diff --git a/.github/workflows/ci-run-tests.yml b/.github/workflows/ci-run-tests.yml
index 346dd97b43..9121bf8aea 100644
--- a/.github/workflows/ci-run-tests.yml
+++ b/.github/workflows/ci-run-tests.yml
@@ -4,6 +4,7 @@ on:
pull_request:
branches:
- 'main'
+ - 'feat/rfc'
paths:
- 'client/**'
- 'ietf/**'
diff --git a/dev/deploy-to-container/cli.js b/dev/deploy-to-container/cli.js
index 53c9ababab..1c3d466286 100644
--- a/dev/deploy-to-container/cli.js
+++ b/dev/deploy-to-container/cli.js
@@ -23,7 +23,7 @@ async function main () {
throw new Error('Missing --branch argument!')
}
if (branch.indexOf('/') >= 0) {
- branch = branch.split('/').shift().join('-')
+ branch = branch.split('/').slice(1).join('-')
}
branch = slugify(branch, { lower: true, strict: true })
if (branch.length < 1) {
diff --git a/dev/deploy-to-container/start.sh b/dev/deploy-to-container/start.sh
index 5621c68fa5..271c54a43e 100644
--- a/dev/deploy-to-container/start.sh
+++ b/dev/deploy-to-container/start.sh
@@ -36,7 +36,10 @@ echo "Running Datatracker checks..."
# Migrate, adjusting to what the current state of the underlying database might be:
echo "Running Datatracker migrations..."
-/usr/local/bin/python ./ietf/manage.py migrate --fake-initial --settings=settings_local
+/usr/local/bin/python ./ietf/manage.py migrate --settings=settings_local
+
+echo "Syncing with the rfc-index"
+./ietf/bin/rfc-editor-index-updates -d 1969-01-01
echo "Starting Datatracker..."
./ietf/manage.py runserver 0.0.0.0:8000 --settings=settings_local
diff --git a/bin/add-old-drafts-from-archive.py b/dev/legacy/add-old-drafts-from-archive.py
similarity index 100%
rename from bin/add-old-drafts-from-archive.py
rename to dev/legacy/add-old-drafts-from-archive.py
diff --git a/ietf/bin/recalculate-rfc-authors-snapshot b/dev/legacy/recalculate-rfc-authors-snapshot
similarity index 100%
rename from ietf/bin/recalculate-rfc-authors-snapshot
rename to dev/legacy/recalculate-rfc-authors-snapshot
diff --git a/ietf/api/tests.py b/ietf/api/tests.py
index 24d76a6a96..e61069b3f5 100644
--- a/ietf/api/tests.py
+++ b/ietf/api/tests.py
@@ -24,7 +24,7 @@
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
@@ -944,7 +944,7 @@ def do_draft_test(self, name):
self.assertNotIn('previous', received, 'Rev 00 has no previous name when not replacing a draft')
replaced = IndividualDraftFactory()
- RelatedDocument.objects.create(relationship_id='replaces',source=draft,target=replaced.docalias.first())
+ RelatedDocument.objects.create(relationship_id='replaces',source=draft,target=replaced)
received = self.getJson(dict(name=draft.name, rev='00'))
self.assertEqual(received['previous'], f'{replaced.name}-{replaced.rev}',
'Rev 00 has a previous name when replacing a draft')
@@ -974,19 +974,19 @@ def test_draft_with_broken_history(self):
def do_rfc_test(self, draft_name):
draft = WgDraftFactory(name=draft_name, create_revisions=range(0,2))
- draft.docalias.create(name=f'rfc{self.next_rfc_number():04}')
+ rfc = WgRfcFactory(group=draft.group, rfc_number=self.next_rfc_number())
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
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
+ draft, rfc = reload_db_objects(draft, rfc)
- number = rfc.rfc_number()
+ number = rfc.rfc_number
received = self.getJson(dict(name=number))
self.assertEqual(
received,
dict(
content_url=rfc.get_href(),
- name=rfc.canonical_name(),
+ name=rfc.name,
previous=f'{draft.name}-{draft.rev}',
previous_url= draft.history_set.get(rev=draft.rev).get_href(),
),
@@ -994,7 +994,7 @@ def do_rfc_test(self, draft_name):
)
num_received = received
- received = self.getJson(dict(name=rfc.canonical_name()))
+ received = self.getJson(dict(name=rfc.name))
self.assertEqual(num_received, received, 'RFC by canonical name gives same result as by number')
received = self.getJson(dict(name=f'RfC {number}'))
@@ -1026,30 +1026,30 @@ def test_rfc(self):
def test_rfc_with_tombstone(self):
draft = WgDraftFactory(create_revisions=range(0,2))
- draft.docalias.create(name='rfc3261') # See views_doc.HAS_TOMBSTONE
+ rfc = WgRfcFactory(rfc_number=3261,group=draft.group)# See views_doc.HAS_TOMBSTONE
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
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
# Some old rfcs had tombstones that shouldn't be used for comparisons
- received = self.getJson(dict(name=rfc.canonical_name()))
+ received = self.getJson(dict(name=rfc.name))
self.assertTrue(received['previous'].endswith('00'))
def do_rfc_with_broken_history_test(self, draft_name):
draft = WgDraftFactory(rev='10', name=draft_name)
- draft.docalias.create(name=f'rfc{self.next_rfc_number():04}')
+ rfc = WgRfcFactory(group=draft.group, rfc_number=self.next_rfc_number())
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
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
received = self.getJson(dict(name=draft.name))
self.assertEqual(
received,
dict(
content_url=rfc.get_href(),
- name=rfc.canonical_name(),
+ name=rfc.name,
previous=f'{draft.name}-10',
previous_url= f'{settings.IETF_ID_ARCHIVE_URL}{draft.name}-10.txt',
),
@@ -1080,3 +1080,9 @@ def test_rfc_with_broken_history(self):
# tricky draft names
self.do_rfc_with_broken_history_test(draft_name='draft-gizmo-01')
self.do_rfc_with_broken_history_test(draft_name='draft-oh-boy-what-a-draft-02-03')
+
+ def test_no_such_document(self):
+ for name in ['rfc0000', 'draft-ftei-oof-rab-00']:
+ url = urlreverse(self.target_view, kwargs={'name': name})
+ r = self.client.get(url)
+ self.assertEqual(r.status_code, 404)
diff --git a/ietf/api/views.py b/ietf/api/views.py
index f6221b5e2e..9d832f6fa5 100644
--- a/ietf/api/views.py
+++ b/ietf/api/views.py
@@ -317,12 +317,9 @@ def get_previous_url(name, rev=None):
previous_url = ''
if condition in ('historic version', 'current version'):
doc = history if history else document
- if found_rev:
- doc.is_rfc = lambda: False
previous_url = doc.get_href()
elif condition == 'version dochistory not found':
document.rev = found_rev
- document.is_rfc = lambda: False
previous_url = document.get_href()
return previous_url
@@ -330,32 +327,38 @@ def get_previous_url(name, rev=None):
def rfcdiff_latest_json(request, name, rev=None):
response = dict()
condition, document, history, found_rev = find_doc_for_rfcdiff(name, rev)
-
+ if document and document.type_id == "rfc":
+ draft = document.came_from_draft()
if condition == 'no such document':
raise Http404
elif condition in ('historic version', 'current version'):
doc = history if history else document
- if not found_rev and doc.is_rfc():
- response['content_url'] = doc.get_href()
- response['name']=doc.canonical_name()
- if doc.name != doc.canonical_name():
+ if doc.type_id == "rfc":
+ response['content_url'] = doc.get_href()
+ response['name']=doc.name
+ if draft:
+ prev_rev = draft.rev
+ if doc.rfc_number in HAS_TOMBSTONE and prev_rev != '00':
+ prev_rev = f'{(int(draft.rev)-1):02d}'
+ response['previous'] = f'{draft.name}-{prev_rev}'
+ response['previous_url'] = get_previous_url(draft.name, prev_rev)
+ elif doc.type_id == "draft" and not found_rev and doc.relateddocument_set.filter(relationship_id="became_rfc").exists():
+ rfc = doc.related_that_doc("became_rfc")[0]
+ response['content_url'] = rfc.get_href()
+ response['name']=rfc.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 rfc.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)
else:
- doc.is_rfc = lambda: False
response['content_url'] = doc.get_href()
response['rev'] = doc.rev
response['name'] = doc.name
if doc.rev == '00':
replaces_docs = (history.doc if condition=='historic version' else doc).related_that_doc('replaces')
if replaces_docs:
- replaces = replaces_docs[0].document
+ replaces = replaces_docs[0]
response['previous'] = f'{replaces.name}-{replaces.rev}'
response['previous_url'] = get_previous_url(replaces.name, replaces.rev)
else:
@@ -374,7 +377,6 @@ def rfcdiff_latest_json(request, name, rev=None):
response['name'] = document.name
response['rev'] = found_rev
document.rev = found_rev
- document.is_rfc = lambda: False
response['content_url'] = document.get_href()
# not sure what to do if non-numeric values come back, so at least log it
log.assertion('found_rev.isdigit()')
diff --git a/ietf/bin/rfc-editor-index-updates b/ietf/bin/rfc-editor-index-updates
index dc7abe26bb..c3e8f1f462 100755
--- a/ietf/bin/rfc-editor-index-updates
+++ b/ietf/bin/rfc-editor-index-updates
@@ -79,12 +79,12 @@ if len(errata_data) < ietf.sync.rfceditor.MIN_ERRATA_RESULTS:
sys.exit(1)
new_rfcs = []
-for changes, doc, rfc_published in ietf.sync.rfceditor.update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=skip_date):
+for rfc_number, changes, doc, rfc_published in ietf.sync.rfceditor.update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=skip_date):
if rfc_published:
new_rfcs.append(doc)
for c in changes:
- log("RFC%s, %s: %s" % (doc.rfcnum, doc.name, c))
+ log("RFC%s, %s: %s" % (rfc_number, doc.name, c))
sys.exit(0)
@@ -99,7 +99,7 @@ if newpid == 0:
pipe("%s -a %s %s" % (settings.RSYNC_BINARY,settings.RFC_TEXT_RSYNC_SOURCE,settings.RFC_PATH))
for rfc in new_rfcs:
rebuild_reference_relations(rfc)
- log("Updated references for %s"%rfc.canonical_name())
+ log("Updated references for %s"%rfc.name)
except:
subject = "Exception in updating references for new rfcs: %s : %s" % (sys.exc_info()[0],sys.exc_info()[1])
msg = "%s\n%s\n----\n%s"%(sys.exc_info()[0],sys.exc_info()[1],traceback.format_tb(sys.exc_info()[2]))
diff --git a/ietf/community/forms.py b/ietf/community/forms.py
index 8d72ce0d70..ad85708968 100644
--- a/ietf/community/forms.py
+++ b/ietf/community/forms.py
@@ -30,6 +30,8 @@ def __init__(self, clist, rule_type, *args, **kwargs):
super(SearchRuleForm, self).__init__(*args, **kwargs)
def restrict_state(state_type, slug=None):
+ if "state" not in self.fields:
+ raise RuntimeError(f"Rule type {rule_type} cannot include state filtering")
f = self.fields['state']
f.queryset = f.queryset.filter(used=True).filter(type=state_type)
if slug:
@@ -38,11 +40,15 @@ def restrict_state(state_type, slug=None):
f.initial = f.queryset[0].pk
f.widget = forms.HiddenInput()
+ if rule_type.endswith("_rfc"):
+ del self.fields["state"] # rfc rules must not look at document states
+
if rule_type in ["group", "group_rfc", "area", "area_rfc", "group_exp"]:
if rule_type == "group_exp":
restrict_state("draft", "expired")
else:
- restrict_state("draft", "rfc" if rule_type.endswith("rfc") else "active")
+ if not rule_type.endswith("_rfc"):
+ restrict_state("draft", "active")
if rule_type.startswith("area"):
self.fields["group"].label = "Area"
@@ -70,7 +76,8 @@ def restrict_state(state_type, slug=None):
del self.fields["text"]
elif rule_type in ["author", "author_rfc", "shepherd", "ad"]:
- restrict_state("draft", "rfc" if rule_type.endswith("rfc") else "active")
+ if not rule_type.endswith("_rfc"):
+ restrict_state("draft", "active")
if rule_type.startswith("author"):
self.fields["person"].label = "Author"
@@ -84,7 +91,8 @@ def restrict_state(state_type, slug=None):
del self.fields["text"]
elif rule_type == "name_contains":
- restrict_state("draft", "rfc" if rule_type.endswith("rfc") else "active")
+ if not rule_type.endswith("_rfc"):
+ restrict_state("draft", "active")
del self.fields["person"]
del self.fields["group"]
diff --git a/ietf/community/migrations/0003_track_rfcs.py b/ietf/community/migrations/0003_track_rfcs.py
new file mode 100644
index 0000000000..3c2d04097d
--- /dev/null
+++ b/ietf/community/migrations/0003_track_rfcs.py
@@ -0,0 +1,50 @@
+# Generated by Django 4.2.3 on 2023-07-07 18:33
+
+from django.db import migrations
+
+
+def forward(apps, schema_editor):
+ """Track any RFCs that were created from tracked drafts"""
+ CommunityList = apps.get_model("community", "CommunityList")
+ RelatedDocument = apps.get_model("doc", "RelatedDocument")
+
+ # Handle individually tracked documents
+ for cl in CommunityList.objects.all():
+ for rfc in set(
+ RelatedDocument.objects.filter(
+ source__in=cl.added_docs.all(),
+ relationship__slug="became_rfc",
+ ).values_list("target__docs", flat=True)
+ ):
+ cl.added_docs.add(rfc)
+
+ # Handle rules - rules ending with _rfc should no longer filter by state.
+ # There are 9 CommunityLists with invalid author_rfc rules that are filtering
+ # by (draft, active) instead of (draft, rfc) state before migration. All but one
+ # also includes an author rule for (draft, active), so these will start following
+ # RFCs as well. The one exception will start tracking RFCs instead of I-Ds, which
+ # is probably what was intended, but will be a change in their user experience.
+ SearchRule = apps.get_model("community", "SearchRule")
+ rfc_rules = SearchRule.objects.filter(rule_type__endswith="_rfc")
+ rfc_rules.update(state=None)
+
+def reverse(apps, schema_editor):
+ Document = apps.get_model("doc", "Document")
+ for rfc in Document.objects.filter(type__slug="rfc"):
+ rfc.communitylist_set.clear()
+
+ # See the comment above regarding author_rfc
+ SearchRule = apps.get_model("community", "SearchRule")
+ State = apps.get_model("doc", "State")
+ SearchRule.objects.filter(rule_type__endswith="_rfc").update(
+ state=State.objects.get(type_id="draft", slug="rfc")
+ )
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("community", "0002_auto_20230320_1222"),
+ ("doc", "0014_move_rfc_docaliases"),
+ ]
+
+ operations = [migrations.RunPython(forward, reverse)]
diff --git a/ietf/community/tests.py b/ietf/community/tests.py
index 3dd86f70e3..ee5827ee25 100644
--- a/ietf/community/tests.py
+++ b/ietf/community/tests.py
@@ -41,7 +41,7 @@ def test_rule_matching(self):
clist = CommunityList.objects.create(user=User.objects.get(username="plain"))
rule_group = SearchRule.objects.create(rule_type="group", group=draft.group, state=State.objects.get(type="draft", slug="active"), community_list=clist)
- rule_group_rfc = SearchRule.objects.create(rule_type="group_rfc", group=draft.group, state=State.objects.get(type="draft", slug="rfc"), community_list=clist)
+ rule_group_rfc = SearchRule.objects.create(rule_type="group_rfc", group=draft.group, state=State.objects.get(type="rfc", slug="published"), community_list=clist)
rule_area = SearchRule.objects.create(rule_type="area", group=draft.group.parent, state=State.objects.get(type="draft", slug="active"), community_list=clist)
rule_state_iesg = SearchRule.objects.create(rule_type="state_iesg", state=State.objects.get(type="draft-iesg", slug="lc"), community_list=clist)
@@ -151,7 +151,7 @@ def test_manage_personal_list(self):
"action": "add_rule",
"rule_type": "author_rfc",
"author_rfc-person": Person.objects.filter(documentauthor__document=draft).first().pk,
- "author_rfc-state": State.objects.get(type="draft", slug="rfc").pk,
+ "author_rfc-state": State.objects.get(type="rfc", slug="published").pk,
})
self.assertEqual(r.status_code, 302)
clist = CommunityList.objects.get(user__username="plain")
@@ -408,4 +408,4 @@ def test_notification(self):
self.assertEqual(len(outbox), mailbox_before + 1)
self.assertTrue(draft.name in outbox[-1]["Subject"])
-
\ No newline at end of file
+
diff --git a/ietf/community/utils.py b/ietf/community/utils.py
index 8130954b92..f411af6a5f 100644
--- a/ietf/community/utils.py
+++ b/ietf/community/utils.py
@@ -60,7 +60,7 @@ def reset_name_contains_index_for_rule(rule):
if not rule.rule_type == "name_contains":
return
- rule.name_contains_index.set(Document.objects.filter(docalias__name__regex=rule.text))
+ rule.name_contains_index.set(Document.objects.filter(name__regex=rule.text))
def update_name_contains_indexes_with_new_doc(doc):
for r in SearchRule.objects.filter(rule_type="name_contains"):
@@ -71,71 +71,104 @@ def update_name_contains_indexes_with_new_doc(doc):
if re.search(r.text, doc.name) and not doc in r.name_contains_index.all():
r.name_contains_index.add(doc)
+
def docs_matching_community_list_rule(rule):
docs = Document.objects.all()
+
+ if rule.rule_type.endswith("_rfc"):
+ docs = docs.filter(type_id="rfc") # rule.state is ignored for RFCs
+ else:
+ docs = docs.filter(type_id="draft", states=rule.state)
+
if rule.rule_type in ['group', 'area', 'group_rfc', 'area_rfc']:
- return docs.filter(Q(group=rule.group_id) | Q(group__parent=rule.group_id), states=rule.state)
+ return docs.filter(Q(group=rule.group_id) | Q(group__parent=rule.group_id))
elif rule.rule_type in ['group_exp']:
- return docs.filter(group=rule.group_id, states=rule.state)
+ return docs.filter(group=rule.group_id)
elif rule.rule_type.startswith("state_"):
- return docs.filter(states=rule.state)
+ return docs
elif rule.rule_type in ["author", "author_rfc"]:
- return docs.filter(states=rule.state, documentauthor__person=rule.person)
+ return docs.filter(documentauthor__person=rule.person)
elif rule.rule_type == "ad":
- return docs.filter(states=rule.state, ad=rule.person)
+ return docs.filter(ad=rule.person)
elif rule.rule_type == "shepherd":
- return docs.filter(states=rule.state, shepherd__person=rule.person)
+ return docs.filter(shepherd__person=rule.person)
elif rule.rule_type == "name_contains":
- return docs.filter(states=rule.state, searchrule=rule)
+ return docs.filter(searchrule=rule)
raise NotImplementedError
-def community_list_rules_matching_doc(doc):
- states = list(doc.states.values_list("pk", flat=True))
+def community_list_rules_matching_doc(doc):
rules = SearchRule.objects.none()
+ if doc.type_id not in ["draft", "rfc"]:
+ return rules # none
+ states = list(doc.states.values_list("pk", flat=True))
+ # group and area rules
if doc.group_id:
groups = [doc.group_id]
if doc.group.parent_id:
groups.append(doc.group.parent_id)
+ rules_to_add = SearchRule.objects.filter(group__in=groups)
+ if doc.type_id == "rfc":
+ rules_to_add = rules_to_add.filter(rule_type__in=["group_rfc", "area_rfc"])
+ else:
+ rules_to_add = rules_to_add.filter(
+ rule_type__in=["group", "area", "group_exp"],
+ state__in=states,
+ )
+ rules |= rules_to_add
+
+ # state rules (only relevant for I-Ds)
+ if doc.type_id == "draft":
rules |= SearchRule.objects.filter(
- rule_type__in=['group', 'area', 'group_rfc', 'area_rfc', 'group_exp'],
+ rule_type__in=[
+ "state_iab",
+ "state_iana",
+ "state_iesg",
+ "state_irtf",
+ "state_ise",
+ "state_rfceditor",
+ "state_ietf",
+ ],
state__in=states,
- group__in=groups
)
- rules |= SearchRule.objects.filter(
- rule_type__in=['state_iab', 'state_iana', 'state_iesg', 'state_irtf', 'state_ise', 'state_rfceditor', 'state_ietf'],
- state__in=states,
- )
-
- rules |= SearchRule.objects.filter(
- rule_type__in=["author", "author_rfc"],
- state__in=states,
- person__in=list(Person.objects.filter(documentauthor__document=doc)),
- )
-
- if doc.ad_id:
+ # author rules
+ if doc.type_id == "rfc":
+ rules |= SearchRule.objects.filter(
+ rule_type="author_rfc",
+ person__in=list(Person.objects.filter(documentauthor__document=doc)),
+ )
+ else:
rules |= SearchRule.objects.filter(
- rule_type="ad",
+ rule_type="author",
state__in=states,
- person=doc.ad_id,
+ person__in=list(Person.objects.filter(documentauthor__document=doc)),
)
- if doc.shepherd_id:
+ # Other draft-only rules rules
+ if doc.type_id == "draft":
+ if doc.ad_id:
+ rules |= SearchRule.objects.filter(
+ rule_type="ad",
+ state__in=states,
+ person=doc.ad_id,
+ )
+
+ if doc.shepherd_id:
+ rules |= SearchRule.objects.filter(
+ rule_type="shepherd",
+ state__in=states,
+ person__email=doc.shepherd_id,
+ )
+
rules |= SearchRule.objects.filter(
- rule_type="shepherd",
+ rule_type="name_contains",
state__in=states,
- person__email=doc.shepherd_id,
+ name_contains_index=doc, # search our materialized index to avoid full scan
)
- rules |= SearchRule.objects.filter(
- rule_type="name_contains",
- state__in=states,
- name_contains_index=doc, # search our materialized index to avoid full scan
- )
-
return rules
@@ -146,7 +179,11 @@ def docs_tracked_by_community_list(clist):
# in theory, we could use an OR query, but databases seem to have
# trouble with OR queries and complicated joins so do the OR'ing
# manually
- doc_ids = set(clist.added_docs.values_list("pk", flat=True))
+ doc_ids = set()
+ for doc in clist.added_docs.all():
+ doc_ids.add(doc.pk)
+ doc_ids.update(rfc.pk for rfc in doc.related_that_doc("became_rfc"))
+
for rule in clist.searchrule_set.all():
doc_ids = doc_ids | set(docs_matching_community_list_rule(rule).values_list("pk", flat=True))
diff --git a/ietf/community/views.py b/ietf/community/views.py
index 1dbbfcaf0e..fdaaffec0b 100644
--- a/ietf/community/views.py
+++ b/ietf/community/views.py
@@ -79,19 +79,18 @@ def manage_list(request, username=None, acronym=None, group_type=None):
rule_type_form = SearchRuleTypeForm(request.POST)
if rule_type_form.is_valid():
rule_type = rule_type_form.cleaned_data['rule_type']
-
- if rule_type:
- rule_form = SearchRuleForm(clist, rule_type, request.POST)
- if rule_form.is_valid():
- if clist.pk is None:
- clist.save()
-
- rule = rule_form.save(commit=False)
- rule.community_list = clist
- rule.rule_type = rule_type
- rule.save()
- if rule.rule_type == "name_contains":
- reset_name_contains_index_for_rule(rule)
+ if rule_type:
+ rule_form = SearchRuleForm(clist, rule_type, request.POST)
+ if rule_form.is_valid():
+ if clist.pk is None:
+ clist.save()
+
+ rule = rule_form.save(commit=False)
+ rule.community_list = clist
+ rule.rule_type = rule_type
+ rule.save()
+ if rule.rule_type == "name_contains":
+ reset_name_contains_index_for_rule(rule)
return HttpResponseRedirect("")
else:
@@ -130,7 +129,7 @@ def manage_list(request, username=None, acronym=None, group_type=None):
@login_required
def track_document(request, name, username=None, acronym=None):
- doc = get_object_or_404(Document, docalias__name=name)
+ doc = get_object_or_404(Document, name=name)
if request.method == "POST":
clist = lookup_community_list(username, acronym)
@@ -154,7 +153,7 @@ def track_document(request, name, username=None, acronym=None):
@login_required
def untrack_document(request, name, username=None, acronym=None):
- doc = get_object_or_404(Document, docalias__name=name)
+ doc = get_object_or_404(Document, name=name)
clist = lookup_community_list(username, acronym)
if not can_manage_community_list(request.user, clist):
permission_denied(request, "You do not have permission to access this view")
diff --git a/ietf/doc/admin.py b/ietf/doc/admin.py
index 64b9d9eff8..3ad4bee2af 100644
--- a/ietf/doc/admin.py
+++ b/ietf/doc/admin.py
@@ -7,7 +7,7 @@
from django import forms
from .models import (StateType, State, RelatedDocument, DocumentAuthor, Document, RelatedDocHistory,
- DocHistoryAuthor, DocHistory, DocAlias, DocReminder, DocEvent, NewRevisionDocEvent,
+ DocHistoryAuthor, DocHistory, DocReminder, DocEvent, NewRevisionDocEvent,
StateDocEvent, ConsensusDocEvent, BallotType, BallotDocEvent, WriteupDocEvent, LastCallDocEvent,
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
@@ -27,10 +27,6 @@ class StateAdmin(admin.ModelAdmin):
filter_horizontal = ["next_states"]
admin.site.register(State, StateAdmin)
-# class DocAliasInline(admin.TabularInline):
-# model = DocAlias
-# extra = 1
-
class DocAuthorInline(admin.TabularInline):
model = DocumentAuthor
raw_id_fields = ['person', 'email']
@@ -43,8 +39,9 @@ class DocActionHolderInline(admin.TabularInline):
class RelatedDocumentInline(admin.TabularInline):
model = RelatedDocument
+ fk_name= 'source'
def this(self, instance):
- return instance.source.canonical_name()
+ return instance.source.name
readonly_fields = ['this', ]
fields = ['this', 'relationship', 'target', ]
raw_id_fields = ['target']
@@ -70,7 +67,7 @@ class Meta:
class DocumentAuthorAdmin(admin.ModelAdmin):
list_display = ['id', 'document', 'person', 'email', 'affiliation', 'country', 'order']
- search_fields = ['document__docalias__name', 'person__name', 'email__address', 'affiliation', 'country']
+ search_fields = ['document__name', 'person__name', 'email__address', 'affiliation', 'country']
raw_id_fields = ["document", "person", "email"]
admin.site.register(DocumentAuthor, DocumentAuthorAdmin)
@@ -108,14 +105,6 @@ def state(self, instance):
admin.site.register(DocHistory, DocHistoryAdmin)
-class DocAliasAdmin(admin.ModelAdmin):
- list_display = ['name', 'targets']
- search_fields = ['name', 'docs__name']
- raw_id_fields = ['docs']
- def targets(self, obj):
- return ', '.join([o.name for o in obj.docs.all()])
-admin.site.register(DocAlias, DocAliasAdmin)
-
class DocReminderAdmin(admin.ModelAdmin):
list_display = ['id', 'event', 'type', 'due', 'active']
list_filter = ['type', 'due', 'active']
@@ -125,7 +114,7 @@ class DocReminderAdmin(admin.ModelAdmin):
class RelatedDocumentAdmin(admin.ModelAdmin):
list_display = ['source', 'target', 'relationship', ]
list_filter = ['relationship', ]
- search_fields = ['source__name', 'target__name', 'target__docs__name', ]
+ search_fields = ['source__name', 'target__name', ]
raw_id_fields = ['source', 'target', ]
admin.site.register(RelatedDocument, RelatedDocumentAdmin)
diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py
index 3ea9f2b8fa..50fba50c42 100644
--- a/ietf/doc/factories.py
+++ b/ietf/doc/factories.py
@@ -12,7 +12,7 @@
from django.conf import settings
from django.utils import timezone
-from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
+from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, State, DocumentAuthor,
StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent,
DocumentActionHolder, BofreqEditorDocEvent, BofreqResponsibleDocEvent, DocExtResource )
from ietf.group.models import Group
@@ -51,16 +51,11 @@ class Meta:
def name(self, n):
return draft_name_generator(self.type_id,self.group,n)
- newrevisiondocevent = factory.RelatedFactory('ietf.doc.factories.NewRevisionDocEventFactory','doc')
-
@factory.post_generation
- def other_aliases(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
- alias = DocAliasFactory(name=obj.name)
- alias.docs.add(obj)
- if create and extracted:
- for name in extracted:
- alias = DocAliasFactory(name=name)
- alias.docs.add(obj)
+ def newrevisiondocevent(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
+ if create:
+ if obj.type_id != "rfc":
+ NewRevisionDocEventFactory(doc=obj)
@factory.post_generation
def states(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
@@ -83,13 +78,7 @@ def authors(obj, create, extracted, **kwargs): # pylint: disable=no-self-argumen
def relations(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
if create and extracted:
for (rel_id, doc) in extracted:
- if isinstance(doc, Document):
- docalias = doc.docalias.first()
- elif isinstance(doc, DocAlias):
- docalias = doc
- else:
- continue
- obj.relateddocument_set.create(relationship_id=rel_id, target=docalias)
+ obj.relateddocument_set.create(relationship_id=rel_id, target=doc)
@factory.post_generation
def create_revisions(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
@@ -119,10 +108,12 @@ class DocumentFactory(BaseDocumentFactory):
group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none')
-class IndividualDraftFactory(BaseDocumentFactory):
-
- type_id = 'draft'
- group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none')
+class RfcFactory(BaseDocumentFactory):
+ type_id = "rfc"
+ rev = ""
+ rfc_number = factory.Sequence(lambda n: n + 1000)
+ name = factory.LazyAttribute(lambda o: f"rfc{o.rfc_number:d}")
+ expires = None
@factory.post_generation
def states(obj, create, extracted, **kwargs):
@@ -131,15 +122,14 @@ def states(obj, create, extracted, **kwargs):
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='idexists'))
else:
- obj.set_state(State.objects.get(type_id='draft',slug='active'))
- obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
+ obj.set_state(State.objects.get(type_id='rfc',slug='published'))
-class IndividualRfcFactory(IndividualDraftFactory):
- alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000)))
+class IndividualDraftFactory(BaseDocumentFactory):
+
+ type_id = 'draft'
+ group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none')
@factory.post_generation
def states(obj, create, extracted, **kwargs):
@@ -148,17 +138,17 @@ def states(obj, create, extracted, **kwargs):
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='idexists'))
else:
- obj.set_state(State.objects.get(type_id='draft',slug='rfc'))
+ obj.set_state(State.objects.get(type_id='draft',slug='active'))
+ 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 IndividualRfcFactory(RfcFactory):
+ group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none')
-class WgDraftFactory(BaseDocumentFactory):
+class WgDraftFactory(BaseDocumentFactory):
type_id = 'draft'
group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='wg')
stream_id = 'ietf'
@@ -177,30 +167,12 @@ def states(obj, create, extracted, **kwargs):
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 +195,11 @@ def states(obj, create, extracted, **kwargs):
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):
@@ -279,7 +228,7 @@ def changes_status_of(obj, create, extracted, **kwargs):
for (rel, target) in extracted:
obj.relateddocument_set.create(relationship_id=rel,target=target)
else:
- obj.relateddocument_set.create(relationship_id='tobcp', target=WgRfcFactory().docalias.first())
+ obj.relateddocument_set.create(relationship_id='tobcp', target=WgRfcFactory())
@factory.post_generation
def states(obj, create, extracted, **kwargs):
@@ -306,9 +255,9 @@ def review_of(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
- obj.relateddocument_set.create(relationship_id='conflrev',target=extracted.docalias.first())
+ obj.relateddocument_set.create(relationship_id='conflrev',target=extracted)
else:
- obj.relateddocument_set.create(relationship_id='conflrev',target=DocumentFactory(name=obj.name.replace('conflict-review-','draft-'),type_id='draft',group=Group.objects.get(type_id='individ')).docalias.first())
+ obj.relateddocument_set.create(relationship_id='conflrev',target=DocumentFactory(name=obj.name.replace('conflict-review-','draft-'),type_id='draft',group=Group.objects.get(type_id='individ')))
@factory.post_generation
@@ -327,24 +276,6 @@ class ReviewFactory(BaseDocumentFactory):
name = factory.LazyAttribute(lambda o: 'review-doesnotexist-00-%s-%s'%(o.group.acronym,date_today().isoformat()))
group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='review')
-class DocAliasFactory(factory.django.DjangoModelFactory):
- class Meta:
- model = DocAlias
- skip_postgeneration_save = True
-
- @factory.post_generation
- def document(self, create, extracted, **kwargs):
- if create and extracted:
- self.docs.add(extracted)
-
- @factory.post_generation
- def docs(self, create, extracted, **kwargs):
- if create and extracted:
- for doc in extracted:
- if not doc in self.docs.all():
- self.docs.add(doc)
-
-
class DocEventFactory(factory.django.DjangoModelFactory):
class Meta:
model = DocEvent
@@ -557,33 +488,8 @@ def states(obj, create, extracted, **kwargs):
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
class StatementFactory(BaseDocumentFactory):
type_id = "statement"
@@ -611,3 +517,31 @@ def states(obj, create, extracted, **kwargs):
obj.set_state(State.objects.get(type_id=state_type_id, slug=state_slug))
else:
obj.set_state(State.objects.get(type_id="statement", slug="active"))
+
+class SubseriesFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = Document
+ skip_postgeneration_save = True
+
+ @factory.lazy_attribute_sequence
+ def name(self, n):
+ return f"{self.type_id}{n}"
+
+ @factory.post_generation
+ def contains(obj, create, extracted, **kwargs):
+ if not create:
+ return
+ if extracted:
+ for doc in extracted:
+ obj.relateddocument_set.create(relationship_id="contains",target=doc)
+ else:
+ obj.relateddocument_set.create(relationship_id="contains", target=RfcFactory())
+
+class BcpFactory(SubseriesFactory):
+ type_id="bcp"
+
+class StdFactory(SubseriesFactory):
+ type_id="std"
+
+class FyiFactory(SubseriesFactory):
+ type_id="fyi"
diff --git a/ietf/doc/feeds.py b/ietf/doc/feeds.py
index c5bb467e9b..500ed3cb18 100644
--- a/ietf/doc/feeds.py
+++ b/ietf/doc/feeds.py
@@ -36,7 +36,7 @@ class DocumentChangesFeed(Feed):
feed_type = Atom1Feed
def get_object(self, request, name):
- return Document.objects.get(docalias__name=name)
+ return Document.objects.get(name=name)
def title(self, obj):
return "Changes for %s" % obj.display_name()
@@ -46,7 +46,7 @@ def link(self, obj):
raise FeedDoesNotExist
return urlreverse(
"ietf.doc.views_doc.document_history",
- kwargs=dict(name=obj.canonical_name()),
+ kwargs=dict(name=obj.name),
)
def subtitle(self, obj):
@@ -86,7 +86,7 @@ def item_link(self, item):
return (
urlreverse(
"ietf.doc.views_doc.document_history",
- kwargs=dict(name=item.doc.canonical_name()),
+ kwargs=dict(name=item.doc.name),
)
+ "#history-%s" % item.pk
)
@@ -208,13 +208,13 @@ def items(self):
return [doc for doc, time in results]
def item_title(self, item):
- return "%s : %s" % (item.canonical_name(), item.title)
+ return "%s : %s" % (item.name, item.title)
def item_description(self, item):
return item.abstract
def item_link(self, item):
- return "https://rfc-editor.org/info/%s" % item.canonical_name()
+ return "https://rfc-editor.org/info/%s" % item.name
def item_pubdate(self, item):
return item.publication_time
@@ -224,20 +224,20 @@ def item_extra_kwargs(self, item):
extra.update({"dcterms_accessRights": "gratis"})
extra.update({"dcterms_format": "text/html"})
media_contents = []
- if int(item.rfc_number()) < 8650:
- if int(item.rfc_number()) not in [8, 9, 51, 418, 500, 530, 589]:
+ if item.rfc_number < 8650:
+ if item.rfc_number not in [8, 9, 51, 418, 500, 530, 589]:
for fmt, media_type in [("txt", "text/plain"), ("html", "text/html")]:
media_contents.append(
{
- "url": f"https://rfc-editor.org/rfc/{item.canonical_name()}.{fmt}",
+ "url": f"https://rfc-editor.org/rfc/{item.name}.{fmt}",
"media_type": media_type,
"is_format_of": self.item_link(item),
}
)
- if int(item.rfc_number()) not in [571, 587]:
+ if item.rfc_number not in [571, 587]:
media_contents.append(
{
- "url": f"https://www.rfc-editor.org/rfc/pdfrfc/{item.canonical_name()}.txt.pdf",
+ "url": f"https://www.rfc-editor.org/rfc/pdfrfc/{item.name}.txt.pdf",
"media_type": "application/pdf",
"is_format_of": self.item_link(item),
}
@@ -245,7 +245,7 @@ def item_extra_kwargs(self, item):
else:
media_contents.append(
{
- "url": f"https://www.rfc-editor.org/rfc/{item.canonical_name()}.xml",
+ "url": f"https://www.rfc-editor.org/rfc/{item.name}.xml",
"media_type": "application/rfc+xml",
}
)
@@ -256,16 +256,16 @@ def item_extra_kwargs(self, item):
]:
media_contents.append(
{
- "url": f"https://rfc-editor.org/rfc/{item.canonical_name()}.{fmt}",
+ "url": f"https://rfc-editor.org/rfc/{item.name}.{fmt}",
"media_type": media_type,
- "is_format_of": f"https://www.rfc-editor.org/rfc/{item.canonical_name()}.xml",
+ "is_format_of": f"https://www.rfc-editor.org/rfc/{item.name}.xml",
}
)
extra.update({"media_contents": media_contents})
- extra.update({"doi": "10.17487/%s" % item.canonical_name().upper()})
+ extra.update({"doi": "10.17487/%s" % item.name.upper()})
extra.update(
- {"doiuri": "http://dx.doi.org/10.17487/%s" % item.canonical_name().upper()}
+ {"doiuri": "http://dx.doi.org/10.17487/%s" % item.name.upper()}
)
# R104 Publisher (Mandatory - but we need a string from them first)
diff --git a/ietf/doc/fields.py b/ietf/doc/fields.py
index fde5199509..4a6922bf34 100644
--- a/ietf/doc/fields.py
+++ b/ietf/doc/fields.py
@@ -13,7 +13,7 @@
import debug # pyflakes:ignore
-from ietf.doc.models import Document, DocAlias
+from ietf.doc.models import Document
from ietf.doc.utils import uppercase_std_abbreviated_name
from ietf.utils.fields import SearchableField
@@ -69,19 +69,3 @@ def ajax_url(self):
class SearchableDocumentField(SearchableDocumentsField):
"""Specialized to only return one Document"""
max_entries = 1
-
-
-class SearchableDocAliasesField(SearchableDocumentsField):
- """Search DocAliases instead of Documents"""
- model = DocAlias # type: Type[models.Model]
-
- def doc_type_filter(self, queryset):
- """Filter to include only desired doc type
-
- For DocAlias, pass through to the docs to check type.
- """
- return queryset.filter(docs__type=self.doc_type)
-
-class SearchableDocAliasField(SearchableDocAliasesField):
- """Specialized to only return one DocAlias"""
- max_entries = 1
\ No newline at end of file
diff --git a/ietf/doc/forms.py b/ietf/doc/forms.py
index c0c52571c2..554451c564 100644
--- a/ietf/doc/forms.py
+++ b/ietf/doc/forms.py
@@ -8,7 +8,7 @@
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import validate_email
-from ietf.doc.fields import SearchableDocAliasesField, SearchableDocAliasField
+from ietf.doc.fields import SearchableDocumentField, SearchableDocumentsField
from ietf.doc.models import RelatedDocument, DocExtResource
from ietf.iesg.models import TelechatDate
from ietf.iesg.utils import telechat_page_count
@@ -134,11 +134,12 @@ class ActionHoldersForm(forms.Form):
IESG_APPROVED_STATE_LIST = ("ann", "rfcqueue", "pub")
class AddDownrefForm(forms.Form):
- rfc = SearchableDocAliasField(
+ rfc = SearchableDocumentField(
label="Referenced RFC",
help_text="The RFC that is approved for downref",
- required=True)
- drafts = SearchableDocAliasesField(
+ required=True,
+ doc_type="rfc")
+ drafts = SearchableDocumentsField(
label="Internet-Drafts that makes the reference",
help_text="The Internet-Drafts that approve the downref in their Last Call",
required=True)
@@ -148,7 +149,7 @@ def clean_rfc(self):
raise forms.ValidationError("Please provide a referenced RFC and a referencing Internet-Draft")
rfc = self.cleaned_data['rfc']
- if not rfc.document.is_rfc():
+ if rfc.type_id != "rfc":
raise forms.ValidationError("Cannot find the RFC: " + rfc.name)
return rfc
@@ -158,10 +159,10 @@ def clean_drafts(self):
v_err_names = []
drafts = self.cleaned_data['drafts']
- for da in drafts:
- state = da.document.get_state("draft-iesg")
+ for d in drafts:
+ state = d.get_state("draft-iesg")
if not state or state.slug not in IESG_APPROVED_STATE_LIST:
- v_err_names.append(da.name)
+ v_err_names.append(d.name)
if v_err_names:
raise forms.ValidationError("Internet-Draft is not yet approved: " + ", ".join(v_err_names))
return drafts
@@ -173,23 +174,23 @@ def clean(self):
v_err_pairs = []
rfc = self.cleaned_data['rfc']
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())
+ for d in drafts:
+ if RelatedDocument.objects.filter(source=d, target=rfc, relationship_id='downref-approval'):
+ v_err_pairs.append(f"{d.name} --> RFC {rfc.rfc_number}")
if v_err_pairs:
raise forms.ValidationError("Downref is already in the registry: " + ", ".join(v_err_pairs))
if 'save_downref_anyway' not in self.data:
# this check is skipped if the save_downref_anyway button is used
v_err_refnorm = ""
- for da in drafts:
- if not RelatedDocument.objects.filter(source=da.document, target=rfc, relationship_id='refnorm'):
+ for d in drafts:
+ if not RelatedDocument.objects.filter(source=d, target=rfc, relationship_id='refnorm'):
if v_err_refnorm:
- v_err_refnorm = v_err_refnorm + " or " + da.name
+ v_err_refnorm = v_err_refnorm + " or " + d.name
else:
- v_err_refnorm = da.name
+ v_err_refnorm = d.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.rfc_number} by "
raise forms.ValidationError(v_err_refnorm_prefix + v_err_refnorm)
diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py
index 8f5d0eb678..c1e2074bc0 100644
--- a/ietf/doc/mails.py
+++ b/ietf/doc/mails.py
@@ -19,7 +19,7 @@
from ietf.utils import log
from ietf.utils.mail import send_mail, send_mail_text
from ietf.ipr.utils import iprs_from_docs, related_docs
-from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent
+from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, ConsensusDocEvent
from ietf.doc.utils import needed_ballot_positions
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
from ietf.group.models import Role
@@ -54,7 +54,7 @@ def email_ad_approved_doc(request, doc, text):
def email_ad_approved_conflict_review(request, review, ok_to_publish):
"""Email notification when AD approves a conflict review"""
- conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document
+ conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target
(to, cc) = gather_address_lists("ad_approved_conflict_review")
frm = request.user.person.formatted_email()
send_mail(request,
@@ -202,7 +202,7 @@ def generate_last_call_announcement(request, doc):
doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3)
- iprs = iprs_from_docs(related_docs(DocAlias.objects.get(name=doc.canonical_name())))
+ iprs = iprs_from_docs(related_docs(Document.objects.get(name=doc.name)))
if iprs:
ipr_links = [ urlreverse("ietf.ipr.views.show", kwargs=dict(id=i.id)) for i in iprs]
ipr_links = [ settings.IDTRACKER_BASE_URL+url if not url.startswith("http") else url for url in ipr_links ]
@@ -670,7 +670,7 @@ def send_review_possibly_replaces_request(request, doc, submitter_info):
to = set(addrs.to)
cc = set(addrs.cc)
- possibly_replaces = Document.objects.filter(name__in=[alias.name for alias in doc.related_that_doc("possibly-replaces")])
+ possibly_replaces = Document.objects.filter(name__in=[related.name for related in doc.related_that_doc("possibly-replaces")])
for other_doc in possibly_replaces:
(other_to, other_cc) = gather_address_lists('doc_replacement_suggested',doc=other_doc)
to.update(other_to)
diff --git a/ietf/doc/management/commands/generate_draft_aliases.py b/ietf/doc/management/commands/generate_draft_aliases.py
index 88f4aa98cb..6d42a66a18 100755
--- a/ietf/doc/management/commands/generate_draft_aliases.py
+++ b/ietf/doc/management/commands/generate_draft_aliases.py
@@ -24,6 +24,7 @@
from ietf.group.utils import get_group_role_emails, get_group_ad_emails
from ietf.utils.aliases import dump_sublist
from utils.mail import parseaddr
+from ietf.utils import log
DEFAULT_YEARS = 2
@@ -120,16 +121,18 @@ def handle(self, *args, **options):
vfile.write("%s anything\n" % settings.DRAFT_VIRTUAL_DOMAIN)
# Internet-Drafts with active status or expired within DEFAULT_YEARS
- drafts = Document.objects.filter(name__startswith='draft-')
+ drafts = Document.objects.filter(type_id="draft")
active_drafts = drafts.filter(states__slug='active')
inactive_recent_drafts = drafts.exclude(states__slug='active').filter(expires__gte=show_since)
interesting_drafts = active_drafts | inactive_recent_drafts
alias_domains = ['ietf.org', ]
for draft in interesting_drafts.distinct().iterator():
- # Omit RFCs, unless they were published in the last DEFAULT_YEARS
- if draft.docalias.filter(name__startswith='rfc'):
- if draft.latest_event(type='published_rfc').time < show_since:
+ # Omit drafts that became RFCs, unless they were published in the last DEFAULT_YEARS
+ if draft.get_state_slug()=="rfc":
+ rfc = draft.became_rfc()
+ log.assertion("rfc is not None")
+ if rfc.latest_event(type='published_rfc').time < show_since:
continue
alias = draft.name
diff --git a/ietf/doc/migrations/0009_add_rfc_states.py b/ietf/doc/migrations/0009_add_rfc_states.py
new file mode 100644
index 0000000000..07a6ac0205
--- /dev/null
+++ b/ietf/doc/migrations/0009_add_rfc_states.py
@@ -0,0 +1,23 @@
+# 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")
+ State.objects.get_or_create(
+ type=rfc_statetype, slug="published", name="Published", used=True, order=1
+ )
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0008_alter_docevent_type"),
+ ]
+
+ operations = [
+ migrations.RunPython(forward),
+ ]
diff --git a/ietf/doc/migrations/0010_dochistory_rfc_number_document_rfc_number.py b/ietf/doc/migrations/0010_dochistory_rfc_number_document_rfc_number.py
new file mode 100644
index 0000000000..26b2a85c62
--- /dev/null
+++ b/ietf/doc/migrations/0010_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", "0009_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/0011_create_rfc_documents.py b/ietf/doc/migrations/0011_create_rfc_documents.py
new file mode 100644
index 0000000000..466ff81bb0
--- /dev/null
+++ b/ietf/doc/migrations/0011_create_rfc_documents.py
@@ -0,0 +1,76 @@
+# 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")
+
+ # 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. Because we go alias-by-alias, no special handling is needed in this migration.
+
+ 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_id = "rfc"
+ rfc.rfc_number = int(draft.name[3:])
+ rfc.save()
+ rfc.states.set([rfc_published_state])
+ else:
+ rfc = Document.objects.create(
+ type_id="rfc",
+ name=rfc_alias.name,
+ rfc_number=int(rfc_alias.name[3:]),
+ time=draft.time,
+ title=draft.title,
+ stream=draft.stream,
+ group=draft.group,
+ abstract=draft.abstract,
+ pages=draft.pages,
+ words=draft.words,
+ std_level=draft.std_level,
+ ad=draft.ad,
+ 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,
+ )
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0010_dochistory_rfc_number_document_rfc_number"),
+ ("name", "0010_rfc_doctype_names"),
+ ]
+
+ operations = [
+ migrations.RunPython(forward),
+ ]
diff --git a/ietf/doc/migrations/0012_move_rfc_docevents.py b/ietf/doc/migrations/0012_move_rfc_docevents.py
new file mode 100644
index 0000000000..9969a8f0ad
--- /dev/null
+++ b/ietf/doc/migrations/0012_move_rfc_docevents.py
@@ -0,0 +1,88 @@
+# Generated by Django 4.2.2 on 2023-06-20 18:36
+
+from django.db import migrations
+from django.db.models import Q
+
+
+def forward(apps, schema_editor):
+ """Move RFC events from the draft to the rfc Document"""
+ DocAlias = apps.get_model("doc", "DocAlias")
+ DocEvent = apps.get_model("doc", "DocEvent")
+ Document = apps.get_model("doc", "Document")
+
+ # queryset with events migrated regardless of whether before or after the "published_rfc" event
+ events_always_migrated = DocEvent.objects.filter(
+ Q(
+ type__in=[
+ "published_rfc", # do not remove this one!
+ ]
+ )
+ )
+
+ # queryset with events migrated only after the "published_rfc" event
+ events_migrated_after_pub = DocEvent.objects.exclude(
+ type__in=[
+ "created_ballot",
+ "closed_ballot",
+ "sent_ballot_announcement",
+ "changed_ballot_position",
+ "changed_ballot_approval_text",
+ "changed_ballot_writeup_text",
+ ]
+ ).exclude(
+ type="added_comment",
+ desc__contains="ballot set", # excludes 311 comments that all apply to drafts
+ )
+
+ # special case for rfc 6312/6342 draft, which has two published_rfc events
+ ignore = ["rfc6312", "rfc6342"] # do not reprocess these later
+ rfc6312 = Document.objects.get(name="rfc6312")
+ rfc6342 = Document.objects.get(name="rfc6342")
+ draft = DocAlias.objects.get(name="rfc6312").docs.first()
+ assert draft == DocAlias.objects.get(name="rfc6342").docs.first()
+ published_events = list(
+ DocEvent.objects.filter(doc=draft, type="published_rfc").order_by("time")
+ )
+ assert len(published_events) == 2
+ (
+ pub_event_6312,
+ pub_event_6342,
+ ) = published_events # order matches pub dates at rfc-editor.org
+
+ pub_event_6312.doc = rfc6312
+ pub_event_6312.save()
+ events_migrated_after_pub.filter(
+ doc=draft,
+ time__gte=pub_event_6312.time,
+ time__lt=pub_event_6342.time,
+ ).update(doc=rfc6312)
+
+ pub_event_6342.doc = rfc6342
+ pub_event_6342.save()
+ events_migrated_after_pub.filter(
+ doc=draft,
+ time__gte=pub_event_6342.time,
+ ).update(doc=rfc6342)
+
+ # Now handle all the rest
+ for rfc in Document.objects.filter(type_id="rfc").exclude(name__in=ignore):
+ draft = DocAlias.objects.get(name=rfc.name).docs.first()
+ assert draft is not None
+ published_event = DocEvent.objects.get(doc=draft, type="published_rfc")
+ events_always_migrated.filter(
+ doc=draft,
+ ).update(doc=rfc)
+ events_migrated_after_pub.filter(
+ doc=draft,
+ time__gte=published_event.time,
+ ).update(doc=rfc)
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0011_create_rfc_documents"),
+ ]
+
+ operations = [
+ migrations.RunPython(forward),
+ ]
diff --git a/ietf/doc/migrations/0013_rfc_relateddocuments.py b/ietf/doc/migrations/0013_rfc_relateddocuments.py
new file mode 100644
index 0000000000..9baddaebdb
--- /dev/null
+++ b/ietf/doc/migrations/0013_rfc_relateddocuments.py
@@ -0,0 +1,45 @@
+# Generated by Django 4.2.3 on 2023-07-05 22:40
+
+from django.db import migrations
+
+
+def forward(apps, schema_editor):
+ DocAlias = apps.get_model("doc", "DocAlias")
+ Document = apps.get_model("doc", "Document")
+ RelatedDocument = apps.get_model("doc", "RelatedDocument")
+ for rfc_alias in DocAlias.objects.filter(name__startswith="rfc").exclude(
+ docs__type_id="rfc"
+ ):
+ # Move these over to the RFC
+ RelatedDocument.objects.filter(
+ relationship__slug__in=(
+ "tobcp",
+ "toexp",
+ "tohist",
+ "toinf",
+ "tois",
+ "tops",
+ "obs",
+ "updates",
+ ),
+ source__docalias=rfc_alias,
+ ).update(source=Document.objects.get(name=rfc_alias.name))
+ # Duplicate references on the RFC but keep the ones on the draft as well
+ originals = list(
+ RelatedDocument.objects.filter(
+ relationship__slug__in=("refinfo", "refnorm", "refold", "refunk"),
+ source__docalias=rfc_alias,
+ )
+ )
+ for o in originals:
+ o.pk = None
+ o.source = Document.objects.get(name=rfc_alias.name)
+ RelatedDocument.objects.bulk_create(originals)
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0012_move_rfc_docevents"),
+ ]
+
+ operations = [migrations.RunPython(forward)]
diff --git a/ietf/doc/migrations/0014_move_rfc_docaliases.py b/ietf/doc/migrations/0014_move_rfc_docaliases.py
new file mode 100644
index 0000000000..c82a98e052
--- /dev/null
+++ b/ietf/doc/migrations/0014_move_rfc_docaliases.py
@@ -0,0 +1,38 @@
+# Generated by Django 4.2.2 on 2023-06-20 18:36
+
+from django.db import migrations
+
+
+def forward(apps, schema_editor):
+ """Point "rfc..." DocAliases at the rfc-type Document
+
+ Creates a became_rfc RelatedDocument to preserve the connection between the draft and the rfc.
+ """
+ DocAlias = apps.get_model("doc", "DocAlias")
+ Document = apps.get_model("doc", "Document")
+ RelatedDocument = apps.get_model("doc", "RelatedDocument")
+
+ for rfc_alias in DocAlias.objects.filter(name__startswith="rfc"):
+ rfc = Document.objects.get(name=rfc_alias.name)
+ aliased_doc = rfc_alias.docs.get() # implicitly confirms only one value in rfc_alias.docs
+ if aliased_doc != rfc:
+ # If the DocAlias was not already pointing at the rfc, it was pointing at the draft
+ # it came from. Create the relationship between draft and rfc Documents.
+ assert aliased_doc.type_id == "draft", f"Alias for {rfc.name} should be pointing at a draft"
+ RelatedDocument.objects.create(
+ source=aliased_doc,
+ target=rfc_alias,
+ relationship_id="became_rfc",
+ )
+ # Now move the alias from the draft to the rfc
+ rfc_alias.docs.set([rfc])
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0013_rfc_relateddocuments"),
+ ]
+
+ operations = [
+ migrations.RunPython(forward),
+ ]
diff --git a/ietf/doc/migrations/0015_relate_no_aliases.py b/ietf/doc/migrations/0015_relate_no_aliases.py
new file mode 100644
index 0000000000..4ba3dd9607
--- /dev/null
+++ b/ietf/doc/migrations/0015_relate_no_aliases.py
@@ -0,0 +1,84 @@
+# Generated by Django 4.2.2 on 2023-06-16 13:40
+
+from django.db import migrations
+import django.db.models.deletion
+from django.db.models import F, Subquery, OuterRef, CharField
+import ietf.utils.models
+
+def forward(apps, schema_editor):
+ RelatedDocument = apps.get_model("doc", "RelatedDocument")
+ DocAlias = apps.get_model("doc", "DocAlias")
+ target_subquery = Subquery(DocAlias.objects.filter(pk=OuterRef("deprecated_target")).values("docs")[:1])
+ name_subquery = Subquery(DocAlias.objects.filter(pk=OuterRef("deprecated_target")).values("name")[:1])
+ RelatedDocument.objects.annotate(firstdoc=target_subquery).annotate(aliasname=name_subquery).update(target=F("firstdoc"),originaltargetaliasname=F("aliasname"))
+
+def reverse(apps, schema_editor):
+ pass
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0014_move_rfc_docaliases"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='relateddocument',
+ name='target',
+ field=ietf.utils.models.ForeignKey(
+ db_index=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.docalias',
+ ),
+ ),
+ migrations.RenameField(
+ model_name="relateddocument",
+ old_name="target",
+ new_name="deprecated_target"
+ ),
+ migrations.AlterField(
+ model_name='relateddocument',
+ name='deprecated_target',
+ field=ietf.utils.models.ForeignKey(
+ db_index=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.docalias',
+ ),
+ ),
+ migrations.AddField(
+ model_name="relateddocument",
+ name="target",
+ field=ietf.utils.models.ForeignKey(
+ default=1, # A lie, but a convenient one - no relations point here.
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="targets_related",
+ to="doc.document",
+ db_index=False,
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="relateddocument",
+ name="originaltargetaliasname",
+ field=CharField(max_length=255,null=True,blank=True),
+ preserve_default=True,
+ ),
+ migrations.RunPython(forward, reverse),
+ migrations.AlterField(
+ model_name="relateddocument",
+ name="target",
+ field=ietf.utils.models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="targets_related",
+ to="doc.document",
+ db_index=True,
+ ),
+ ),
+ migrations.RemoveField(
+ model_name="relateddocument",
+ name="deprecated_target",
+ field=ietf.utils.models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.DocAlias',
+ ),
+ ),
+ ]
diff --git a/ietf/doc/migrations/0016_relate_hist_no_aliases.py b/ietf/doc/migrations/0016_relate_hist_no_aliases.py
new file mode 100644
index 0000000000..df5fb3c325
--- /dev/null
+++ b/ietf/doc/migrations/0016_relate_hist_no_aliases.py
@@ -0,0 +1,87 @@
+# Generated by Django 4.2.2 on 2023-06-16 13:40
+
+from django.db import migrations
+import django.db.models.deletion
+from django.db.models import F, Subquery, OuterRef, CharField
+import ietf.utils.models
+
+def forward(apps, schema_editor):
+ RelatedDocHistory = apps.get_model("doc", "RelatedDocHistory")
+ DocAlias = apps.get_model("doc", "DocAlias")
+ target_subquery = Subquery(DocAlias.objects.filter(pk=OuterRef("deprecated_target")).values("docs")[:1])
+ name_subquery = Subquery(DocAlias.objects.filter(pk=OuterRef("deprecated_target")).values("name")[:1])
+ RelatedDocHistory.objects.annotate(firstdoc=target_subquery).annotate(aliasname=name_subquery).update(target=F("firstdoc"),originaltargetaliasname=F("aliasname"))
+
+def reverse(apps, schema_editor):
+ pass
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0015_relate_no_aliases"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='relateddochistory',
+ name='target',
+ field=ietf.utils.models.ForeignKey(
+ db_index=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.docalias',
+ related_name='reversely_related_document_history_set',
+ ),
+ ),
+ migrations.RenameField(
+ model_name="relateddochistory",
+ old_name="target",
+ new_name="deprecated_target"
+ ),
+ migrations.AlterField(
+ model_name='relateddochistory',
+ name='deprecated_target',
+ field=ietf.utils.models.ForeignKey(
+ db_index=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.docalias',
+ related_name='deprecated_reversely_related_document_history_set',
+ ),
+ ),
+ migrations.AddField(
+ model_name="relateddochistory",
+ name="target",
+ field=ietf.utils.models.ForeignKey(
+ default=1, # A lie, but a convenient one - no relations point here.
+ on_delete=django.db.models.deletion.CASCADE,
+ to="doc.document",
+ db_index=False,
+ related_name='reversely_related_document_history_set',
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="relateddochistory",
+ name="originaltargetaliasname",
+ field=CharField(max_length=255,null=True,blank=True),
+ preserve_default=True,
+ ),
+ migrations.RunPython(forward, reverse),
+ migrations.AlterField(
+ model_name="relateddochistory",
+ name="target",
+ field=ietf.utils.models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="doc.document",
+ db_index=True,
+ related_name='reversely_related_document_history_set',
+ ),
+ ),
+ migrations.RemoveField(
+ model_name="relateddochistory",
+ name="deprecated_target",
+ field=ietf.utils.models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.DocAlias',
+ related_name='deprecated_reversely_related_document_history_set',
+ ),
+ ),
+ ]
diff --git a/ietf/doc/migrations/0017_delete_docalias.py b/ietf/doc/migrations/0017_delete_docalias.py
new file mode 100644
index 0000000000..207ca81e15
--- /dev/null
+++ b/ietf/doc/migrations/0017_delete_docalias.py
@@ -0,0 +1,16 @@
+# Copyright The IETF Trust 2023, All Rights Reserved
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("ipr", "0002_iprdocrel_no_aliases"),
+ ("doc", "0016_relate_hist_no_aliases"),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name="DocAlias",
+ ),
+ ]
diff --git a/ietf/doc/migrations/0018_move_dochistory.py b/ietf/doc/migrations/0018_move_dochistory.py
new file mode 100644
index 0000000000..0bc29b0bc4
--- /dev/null
+++ b/ietf/doc/migrations/0018_move_dochistory.py
@@ -0,0 +1,45 @@
+# Generated by Django 4.2.5 on 2023-09-11 17:52
+
+from django.db import migrations
+
+from django.db.models import Subquery, OuterRef, F
+
+
+def forward(apps, schema_editor):
+ DocHistory = apps.get_model("doc", "DocHistory")
+ RelatedDocument = apps.get_model("doc", "RelatedDocument")
+ Document = apps.get_model("doc", "Document")
+ DocHistory.objects.filter(type_id="draft", doc__type_id="rfc").update(type_id="rfc")
+ DocHistory.objects.filter(
+ type_id="draft", doc__type_id="draft", name__startswith="rfc"
+ ).annotate(
+ rfc_id=Subquery(
+ RelatedDocument.objects.filter(
+ source_id=OuterRef("doc_id"), relationship_id="became_rfc"
+ ).values_list("target_id", flat=True)[:1]
+ )
+ ).update(
+ doc_id=F("rfc_id"), type_id="rfc"
+ )
+ DocHistory.objects.filter(type_id="rfc").annotate(
+ rfcno=Subquery(
+ Document.objects.filter(pk=OuterRef("doc_id")).values_list(
+ "rfc_number", flat=True
+ )[:1]
+ )
+ ).update(rfc_number=F("rfcno"))
+ assert not DocHistory.objects.filter(
+ name__startswith="rfc", type_id="draft"
+ ).exists()
+ assert not DocHistory.objects.filter(
+ type_id="rfc", rfc_number__isnull=True
+ ).exists()
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0017_delete_docalias"),
+ ]
+
+ # There is no going back
+ operations = [migrations.RunPython(forward)]
diff --git a/ietf/doc/migrations/0019_subseries.py b/ietf/doc/migrations/0019_subseries.py
new file mode 100644
index 0000000000..be2c612ac0
--- /dev/null
+++ b/ietf/doc/migrations/0019_subseries.py
@@ -0,0 +1,21 @@
+# Copyright The IETF Trust 2023, All Rights Reserved
+from django.db import migrations
+
+
+def forward(apps, schema_editor):
+ StateType = apps.get_model("doc", "StateType")
+ for slug in ["bcp", "std", "fyi"]:
+ StateType.objects.create(slug=slug, label=f"{slug} state")
+
+
+def reverse(apps, schema_editor):
+ StateType = apps.get_model("doc", "StateType")
+ StateType.objects.filter(slug__in=["bcp", "std", "fyi"]).delete()
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0018_move_dochistory"),
+ ]
+
+ operations = [migrations.RunPython(forward, reverse)]
diff --git a/ietf/doc/migrations/0020_move_errata_tags.py b/ietf/doc/migrations/0020_move_errata_tags.py
new file mode 100644
index 0000000000..897b88f467
--- /dev/null
+++ b/ietf/doc/migrations/0020_move_errata_tags.py
@@ -0,0 +1,29 @@
+# Copyright The IETF Trust 2023, All Rights Reserved
+
+from django.db import migrations
+
+from django.db.models import Subquery, OuterRef, F
+
+
+def forward(apps, schema_editor):
+ Document = apps.get_model("doc", "Document")
+ RelatedDocument = apps.get_model("doc", "RelatedDocument")
+ Document.tags.through.objects.filter(
+ doctagname_id__in=["errata", "verified-errata"], document__type_id="draft"
+ ).annotate(
+ rfcdoc=Subquery(
+ RelatedDocument.objects.filter(
+ relationship_id="became_rfc", source_id=OuterRef("document__pk")
+ ).values_list("target__pk", flat=True)[:1]
+ )
+ ).update(
+ document_id=F("rfcdoc")
+ )
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0019_subseries"),
+ ]
+
+ operations = [migrations.RunPython(forward)]
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index 30d95fbf50..4d7a4f805c 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -37,7 +37,6 @@
from ietf.person.models import Email, Person
from ietf.person.utils import get_active_balloters
from ietf.utils import log
-from ietf.utils.admin import admin_link
from ietf.utils.decorators import memoize
from ietf.utils.validators import validate_no_control_chars
from ietf.utils.mail import formataddr
@@ -124,6 +123,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'):
@@ -136,18 +136,17 @@ def file_extension(self):
def get_file_path(self):
if not hasattr(self, '_cached_file_path'):
- if self.type_id == "draft":
+ if self.type_id == "rfc":
+ self._cached_file_path = settings.RFC_PATH
+ elif self.type_id == "draft":
if self.is_dochistory():
self._cached_file_path = settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR
else:
- if self.get_state_slug() == "rfc":
- self._cached_file_path = settings.RFC_PATH
+ draft_state = self.get_state('draft')
+ if draft_state and draft_state.slug == 'active':
+ self._cached_file_path = settings.INTERNET_DRAFT_PATH
else:
- draft_state = self.get_state('draft')
- if draft_state and draft_state.slug == 'active':
- self._cached_file_path = settings.INTERNET_DRAFT_PATH
- else:
- self._cached_file_path = settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR
+ self._cached_file_path = settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR
elif self.meeting_related() and self.type_id in (
"agenda", "minutes", "slides", "bluesheets", "procmaterials", "chatlog", "polls"
):
@@ -172,17 +171,16 @@ def get_base_name(self):
if not hasattr(self, '_cached_base_name'):
if self.uploaded_filename:
self._cached_base_name = self.uploaded_filename
+ elif self.type_id == 'rfc':
+ self._cached_base_name = "%s.txt" % self.name
elif self.type_id == 'draft':
if self.is_dochistory():
self._cached_base_name = "%s-%s.txt" % (self.doc.name, self.rev)
else:
- if self.get_state_slug() == 'rfc':
- self._cached_base_name = "%s.txt" % self.canonical_name()
- else:
- self._cached_base_name = "%s-%s.txt" % (self.name, self.rev)
+ self._cached_base_name = "%s-%s.txt" % (self.name, self.rev)
elif self.type_id in ["slides", "agenda", "minutes", "bluesheets", "procmaterials", ] and self.meeting_related():
ext = 'pdf' if self.type_id == 'procmaterials' else 'txt'
- self._cached_base_name = f'{self.canonical_name()}-{self.rev}.{ext}'
+ self._cached_base_name = f'{self.name}-{self.rev}.{ext}'
elif self.type_id == 'review':
# TODO: This will be wrong if a review is updated on the same day it was created (or updated more than once on the same day)
self._cached_base_name = "%s.txt" % self.name
@@ -190,9 +188,9 @@ def get_base_name(self):
self._cached_base_name = "%s-%s.md" % (self.name, self.rev)
else:
if self.rev:
- self._cached_base_name = "%s-%s.txt" % (self.canonical_name(), self.rev)
+ self._cached_base_name = "%s-%s.txt" % (self.name, self.rev)
else:
- self._cached_base_name = "%s.txt" % (self.canonical_name(), )
+ self._cached_base_name = "%s.txt" % (self.name, )
return self._cached_base_name
def get_file_name(self):
@@ -200,17 +198,28 @@ def get_file_name(self):
self._cached_file_name = os.path.join(self.get_file_path(), self.get_base_name())
return self._cached_file_name
- def revisions(self):
+
+ def revisions_by_dochistory(self):
revisions = []
- doc = self.doc if isinstance(self, DocHistory) else self
- for e in doc.docevent_set.filter(type='new_revision').distinct():
- if e.rev and not e.rev in revisions:
- revisions.append(e.rev)
- if not doc.rev in revisions:
- revisions.append(doc.rev)
- revisions.sort()
+ if self.type_id != "rfc":
+ for h in self.history_set.order_by("time", "id"):
+ if h.rev and not h.rev in revisions:
+ revisions.append(h.rev)
+ if not self.rev in revisions:
+ revisions.append(self.rev)
return revisions
+ def revisions_by_newrevisionevent(self):
+ revisions = []
+ if self.type_id != "rfc":
+ doc = self.doc if isinstance(self, DocHistory) else self
+ for e in doc.docevent_set.filter(type='new_revision').distinct():
+ if e.rev and not e.rev in revisions:
+ revisions.append(e.rev)
+ if not doc.rev in revisions:
+ revisions.append(doc.rev)
+ revisions.sort()
+ return revisions
def get_href(self, meeting=None):
return self._get_ref(meeting=meeting,meeting_doc_refs=settings.MEETING_DOC_HREFS)
@@ -244,7 +253,7 @@ def _get_ref(self, meeting=None, meeting_doc_refs=settings.MEETING_DOC_HREFS):
format = settings.DOC_HREFS[self.type_id]
elif self.type_id in settings.DOC_HREFS:
self.is_meeting_related = False
- if self.is_rfc():
+ if self.type_id == "rfc":
format = settings.DOC_HREFS['rfc']
else:
format = settings.DOC_HREFS[self.type_id]
@@ -334,7 +343,9 @@ def friendly_state(self):
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:
@@ -343,13 +354,15 @@ def friendly_state(self):
iesg_state_summary = iesg_state.name
if iesg_substate:
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)
+
+ rfc = self.became_rfc()
+ if rfc:
+ return f"Became RFC {rfc.rfc_number} ({rfc.std_level})"
+
elif state.slug == "repl":
rs = self.related_that("replaces")
if rs:
- return mark_safe("Replaced by " + ", ".join("%s" % (urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=alias.document.name)), alias.document) for alias in rs))
+ return mark_safe("Replaced by " + ", ".join("%s" % (urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=related.name)), related) for related in rs))
else:
return "Replaced"
elif state.slug == "active":
@@ -375,27 +388,6 @@ def friendly_state(self):
else:
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()
-
def author_list(self):
best_addresses = []
for author in self.documentauthor_set.all():
@@ -468,9 +460,9 @@ def relations_that(self, relationship):
if not isinstance(relationship, tuple):
raise TypeError("Expected a string or tuple, received %s" % type(relationship))
if isinstance(self, Document):
- return RelatedDocument.objects.filter(target__docs=self, relationship__in=relationship).select_related('source')
+ return RelatedDocument.objects.filter(target=self, relationship__in=relationship).select_related('source')
elif isinstance(self, DocHistory):
- return RelatedDocHistory.objects.filter(target__docs=self.doc, relationship__in=relationship).select_related('source')
+ return RelatedDocHistory.objects.filter(target=self.doc, relationship__in=relationship).select_related('source')
else:
raise TypeError("Expected method called on Document or DocHistory")
@@ -504,15 +496,14 @@ def all_relations_that_doc(self, relationship, related=None):
for r in rels:
if not r in related:
related += ( r, )
- for doc in r.target.docs.all():
- related = doc.all_relations_that_doc(relationship, related)
+ related = r.target.all_relations_that_doc(relationship, related)
return related
def related_that(self, relationship):
- return list(set([x.source.docalias.get(name=x.source.name) for x in self.relations_that(relationship)]))
+ return list(set([x.source for x in self.relations_that(relationship)]))
def all_related_that(self, relationship, related=None):
- return list(set([x.source.docalias.get(name=x.source.name) for x in self.all_relations_that(relationship)]))
+ return list(set([x.source for x in self.all_relations_that(relationship)]))
def related_that_doc(self, relationship):
return list(set([x.target for x in self.relations_that_doc(relationship)]))
@@ -521,12 +512,7 @@ def all_related_that_doc(self, relationship, related=None):
return list(set([x.target for x in self.all_relations_that_doc(relationship)]))
def replaces(self):
- return set([ d for r in self.related_that_doc("replaces") for d in r.docs.all() ])
-
- def replaces_canonical_name(self):
- s = set([ r.document for r in self.related_that_doc("replaces")])
- first = list(s)[0] if s else None
- return None if first is None else first.filename_with_rev()
+ return self.related_that_doc("replaces")
def replaced_by(self):
return set([ r.document for r in self.related_that("replaces") ])
@@ -553,10 +539,10 @@ def text_or_error(self):
return self.text() or "Error; cannot read '%s'"%self.get_base_name()
def html_body(self, classes=""):
- if self.get_state_slug() == "rfc":
+ if self.type_id == "rfc":
try:
html = Path(
- os.path.join(settings.RFC_PATH, self.canonical_name() + ".html")
+ os.path.join(settings.RFC_PATH, self.name + ".html")
).read_text()
except (IOError, UnicodeDecodeError):
return None
@@ -656,10 +642,38 @@ def references(self):
return self.relations_that_doc(('refnorm','refinfo','refunk','refold'))
def referenced_by(self):
- return self.relations_that(('refnorm','refinfo','refunk','refold')).filter(source__states__type__slug='draft',source__states__slug__in=['rfc','active'])
-
+ return self.relations_that(("refnorm", "refinfo", "refunk", "refold")).filter(
+ models.Q(
+ source__type__slug="draft",
+ source__states__type__slug="draft",
+ source__states__slug="active",
+ )
+ | models.Q(source__type__slug="rfc")
+ )
+
+
def referenced_by_rfcs(self):
- return self.relations_that(('refnorm','refinfo','refunk','refold')).filter(source__states__type__slug='draft',source__states__slug='rfc')
+ return self.relations_that(("refnorm", "refinfo", "refunk", "refold")).filter(
+ source__type__slug="rfc"
+ )
+
+ def became_rfc(self):
+ if not hasattr(self, "_cached_became_rfc"):
+ doc = self if isinstance(self, Document) else self.doc
+ self._cached_became_rfc = next(iter(doc.related_that_doc("became_rfc")), None)
+ return self._cached_became_rfc
+
+ def came_from_draft(self):
+ if not hasattr(self, "_cached_came_from_draft"):
+ doc = self if isinstance(self, Document) else self.doc
+ self._cached_came_from_draft = next(iter(doc.related_that("became_rfc")), None)
+ return self._cached_came_from_draft
+
+ def contains(self):
+ return self.related_that_doc("contains")
+
+ def part_of(self):
+ return self.related_that("contains")
class Meta:
abstract = True
@@ -668,42 +682,40 @@ class Meta:
class RelatedDocument(models.Model):
source = ForeignKey('Document')
- target = ForeignKey('DocAlias')
+ target = ForeignKey('Document', related_name='targets_related')
relationship = ForeignKey(DocRelationshipName)
+ originaltargetaliasname = models.CharField(max_length=255, null=True, blank=True)
def action(self):
return self.relationship.name
def __str__(self):
return u"%s %s %s" % (self.source.name, self.relationship.name.lower(), self.target.name)
def is_downref(self):
- if self.source.type.slug != "draft" or self.relationship.slug not in [
+ if self.source.type_id not in ["draft","rfc"] or self.relationship.slug not in [
"refnorm",
"refold",
"refunk",
]:
return None
- state = self.source.get_state()
- if state and state.slug == "rfc":
- source_lvl = self.source.std_level.slug if self.source.std_level else None
- elif self.source.intended_std_level:
- source_lvl = self.source.intended_std_level.slug
+ if self.source.type_id == "rfc":
+ source_lvl = self.source.std_level_id
else:
- source_lvl = None
+ source_lvl = self.source.intended_std_level_id
if source_lvl not in ["bcp", "ps", "ds", "std", "unkn"]:
return None
- if self.target.document.get_state().slug == "rfc":
- if not self.target.document.std_level:
- target_lvl = "unkn"
+ if self.target.type_id == 'rfc':
+ if not self.target.std_level:
+ target_lvl = 'unkn'
else:
- target_lvl = self.target.document.std_level.slug
+ target_lvl = self.target.std_level_id
else:
- if not self.target.document.intended_std_level:
- target_lvl = "unkn"
+ if not self.target.intended_std_level:
+ target_lvl = 'unkn'
else:
- target_lvl = self.target.document.intended_std_level.slug
+ target_lvl = self.target.intended_std_level_id
if self.relationship.slug not in ["refnorm", "refunk"]:
return None
@@ -712,7 +724,7 @@ def is_downref(self):
return None
pos_downref = (
- "Downref" if self.relationship.slug != "refunk" else "Possible Downref"
+ "Downref" if self.relationship_id != "refunk" else "Possible Downref"
)
if source_lvl in ["bcp", "ps", "ds", "std"] and target_lvl in ["inf", "exp"]:
@@ -734,8 +746,8 @@ def is_downref(self):
def is_approved_downref(self):
- if self.target.document.get_state().slug == 'rfc':
- if RelatedDocument.objects.filter(relationship_id='downref-approval', target=self.target):
+ if self.target.type_id == 'rfc':
+ if RelatedDocument.objects.filter(relationship_id='downref-approval', target=self.target).exists():
return "Approved Downref"
return False
@@ -831,7 +843,7 @@ def get_absolute_url(self):
name = self.name
url = None
if self.type_id == "draft" and self.get_state_slug() == "rfc":
- name = self.canonical_name()
+ name = self.name
url = urlreverse('ietf.doc.views_doc.document_main', kwargs={ 'name': name }, urlconf="ietf.urls")
elif self.type_id in ('slides','bluesheets','recording'):
session = self.session_set.first()
@@ -869,28 +881,8 @@ def latest_event(self, *args, **filter_args):
e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time', '-id').first()
return e
- def canonical_name(self):
- if not hasattr(self, '_canonical_name'):
- name = self.name
- if self.type_id == "draft" and self.get_state_slug() == "rfc":
- a = self.docalias.filter(name__startswith="rfc").order_by('-name').first()
- if a:
- name = a.name
- elif self.type_id == "charter":
- from ietf.doc.utils_charter import charter_name_for_group # Imported locally to avoid circular imports
- try:
- name = charter_name_for_group(self.chartered_group)
- except Group.DoesNotExist:
- pass
- self._canonical_name = name
- return self._canonical_name
-
-
- def canonical_docalias(self):
- return self.docalias.get(name=self.name)
-
def display_name(self):
- name = self.canonical_name()
+ name = self.name
if name.startswith('rfc'):
name = name.upper()
return name
@@ -985,17 +977,27 @@ def displayname_with_link(self):
def ipr(self,states=settings.PUBLISH_IPR_STATES):
"""Returns the IPR disclosures against this document (as a queryset over IprDocRel)."""
- from ietf.ipr.models import IprDocRel
- return IprDocRel.objects.filter(document__docs=self, disclosure__state__in=states)
+ # from ietf.ipr.models import IprDocRel
+ # return IprDocRel.objects.filter(document__docs=self, disclosure__state__in=states) # TODO - clear these comments away
+ return self.iprdocrel_set.filter(disclosure__state__in=states)
def related_ipr(self):
"""Returns the IPR disclosures against this document and those documents this
document directly or indirectly obsoletes or replaces
"""
from ietf.ipr.models import IprDocRel
- iprs = IprDocRel.objects.filter(document__in=list(self.docalias.all())+self.all_related_that_doc(('obs','replaces'))).filter(disclosure__state__in=settings.PUBLISH_IPR_STATES).values_list('disclosure', flat=True).distinct()
+ iprs = (
+ IprDocRel.objects.filter(
+ document__in=[self]
+ + self.all_related_that_doc(("obs", "replaces"))
+ )
+ .filter(disclosure__state__in=settings.PUBLISH_IPR_STATES)
+ .values_list("disclosure", flat=True)
+ .distinct()
+ )
return iprs
+
def future_presentations(self):
""" returns related SessionPresentation objects for meetings that
have not yet ended. This implementation allows for 2 week meetings """
@@ -1030,7 +1032,7 @@ def pub_date(self):
This is the rfc publication date for RFCs, and the new-revision date for other documents.
"""
- if self.get_state_slug() == "rfc":
+ if self.type_id == "rfc":
# As of Sept 2022, in ietf.sync.rfceditor.update_docs_from_rfc_index() `published_rfc` events are
# created with a timestamp whose date *in the PST8PDT timezone* is the official publication date
# assigned by the RFC editor.
@@ -1132,8 +1134,9 @@ class DocExtResource(ExtResource):
class RelatedDocHistory(models.Model):
source = ForeignKey('DocHistory')
- target = ForeignKey('DocAlias', related_name="reversely_related_document_history_set")
+ target = ForeignKey('Document', related_name="reversely_related_document_history_set")
relationship = ForeignKey(DocRelationshipName)
+ originaltargetaliasname = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
return u"%s %s %s" % (self.source.doc.name, self.relationship.name.lower(), self.target.name)
@@ -1147,10 +1150,7 @@ def __str__(self):
class DocHistory(DocumentInfo):
doc = ForeignKey(Document, related_name="history_set")
- # the name here is used to capture the canonical name at the time
- # - it would perhaps be more elegant to simply call the attribute
- # canonical_name and replace the function on Document with a
- # property
+
name = models.CharField(max_length=255)
def __str__(self):
@@ -1162,11 +1162,6 @@ def get_related_session(self):
def get_related_proceedings_material(self):
return self.doc.get_related_proceedings_material()
- def canonical_name(self):
- if hasattr(self, '_canonical_name'):
- return self._canonical_name
- return self.name
-
def latest_event(self, *args, **kwargs):
kwargs["time__lte"] = self.time
return self.doc.latest_event(*args, **kwargs)
@@ -1181,10 +1176,6 @@ def last_presented(self):
def groupmilestone_set(self):
return self.doc.groupmilestone_set
- @property
- def docalias(self):
- return self.doc.docalias
-
def is_dochistory(self):
return True
@@ -1202,25 +1193,6 @@ class Meta:
verbose_name = "document history"
verbose_name_plural = "document histories"
-class DocAlias(models.Model):
- """This is used for documents that may appear under multiple names,
- and in particular for RFCs, which for continuity still keep the
- same immutable Document.name, in the tables, but will be referred
- to by RFC number, primarily, after achieving RFC status.
- """
- name = models.CharField(max_length=255, unique=True)
- docs = models.ManyToManyField(Document, related_name='docalias')
-
- @property
- def document(self):
- return self.docs.first()
-
- def __str__(self):
- return u"%s-->%s" % (self.name, ','.join([force_str(d.name) for d in self.docs.all() if isinstance(d, Document) ]))
- document_link = admin_link("document")
- class Meta:
- verbose_name = "document alias"
- verbose_name_plural = "document aliases"
class DocReminder(models.Model):
event = ForeignKey('DocEvent')
diff --git a/ietf/doc/resources.py b/ietf/doc/resources.py
index 99e26ac33d..6bb6ffa281 100644
--- a/ietf/doc/resources.py
+++ b/ietf/doc/resources.py
@@ -12,7 +12,7 @@
from ietf import api
from ietf.doc.models import (BallotType, DeletedEvent, StateType, State, Document,
- DocumentAuthor, DocEvent, StateDocEvent, DocHistory, ConsensusDocEvent, DocAlias,
+ DocumentAuthor, DocEvent, StateDocEvent, DocHistory, ConsensusDocEvent,
TelechatDocEvent, DocReminder, LastCallDocEvent, NewRevisionDocEvent, WriteupDocEvent,
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
@@ -286,21 +286,6 @@ class Meta:
}
api.doc.register(ConsensusDocEventResource())
-class DocAliasResource(ModelResource):
- document = ToOneField(DocumentResource, 'document')
- class Meta:
- cache = SimpleCache()
- queryset = DocAlias.objects.all()
- serializer = api.Serializer()
- detail_uri_name = 'name'
- #resource_name = 'docalias'
- ordering = ['id', ]
- filtering = {
- "name": ALL,
- "document": ALL_WITH_RELATIONS,
- }
-api.doc.register(DocAliasResource())
-
from ietf.person.resources import PersonResource
class TelechatDocEventResource(ModelResource):
by = ToOneField(PersonResource, 'by')
@@ -490,7 +475,7 @@ class Meta:
from ietf.name.resources import DocRelationshipNameResource
class RelatedDocumentResource(ModelResource):
source = ToOneField(DocumentResource, 'source')
- target = ToOneField(DocAliasResource, 'target')
+ target = ToOneField(DocumentResource, 'target')
relationship = ToOneField(DocRelationshipNameResource, 'relationship')
class Meta:
cache = SimpleCache()
@@ -509,7 +494,7 @@ class Meta:
from ietf.name.resources import DocRelationshipNameResource
class RelatedDocHistoryResource(ModelResource):
source = ToOneField(DocHistoryResource, 'source')
- target = ToOneField(DocAliasResource, 'target')
+ target = ToOneField(DocumentResource, 'target')
relationship = ToOneField(DocRelationshipNameResource, 'relationship')
class Meta:
cache = SimpleCache()
diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
index c0ea94ab71..8d9336b536 100644
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -22,7 +22,7 @@
import debug # pyflakes:ignore
-from ietf.doc.models import BallotDocEvent, DocAlias
+from ietf.doc.models import BallotDocEvent, Document
from ietf.doc.models import ConsensusDocEvent
from ietf.ietfauth.utils import can_request_rfc_publication as utils_can_request_rfc_publication
from ietf.utils.html import sanitize_fragment
@@ -139,15 +139,16 @@ def rfceditor_info_url(rfcnum : str):
return urljoin(settings.RFC_EDITOR_INFO_BASE_URL, f'rfc{rfcnum}')
-def doc_canonical_name(name):
+def doc_name(name):
"""Check whether a given document exists, and return its canonical name"""
def find_unique(n):
key = hash(n)
found = cache.get(key)
if not found:
- exact = DocAlias.objects.filter(name=n).first()
+ exact = Document.objects.filter(name=n).first()
found = exact.name if exact else "_"
+ # TODO review this cache policy (and the need for these entire function)
cache.set(key, found, timeout=60*60*24) # cache for one day
return None if found == "_" else found
@@ -173,7 +174,7 @@ def find_unique(n):
def link_charter_doc_match(match):
- if not doc_canonical_name(match[0]):
+ if not doc_name(match[0]):
return match[0]
url = urlreverse(
"ietf.doc.views_doc.document_main",
@@ -186,7 +187,7 @@ def link_non_charter_doc_match(match):
name = match[0]
# handle "I-D.*"" reference-style matches
name = re.sub(r"^i-d\.(.*)", r"draft-\1", name, flags=re.IGNORECASE)
- cname = doc_canonical_name(name)
+ cname = doc_name(name)
if not cname:
return match[0]
if name == cname:
@@ -201,7 +202,7 @@ def link_non_charter_doc_match(match):
url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=cname))
return f'{match[0]}'
- cname = doc_canonical_name(name)
+ cname = doc_name(name)
if not cname:
return match[0]
if name == cname:
@@ -221,12 +222,11 @@ def link_non_charter_doc_match(match):
def link_other_doc_match(match):
doc = match[2].strip().lower()
rev = match[3]
- if not doc_canonical_name(doc + rev):
+ if not doc_name(doc + rev):
return match[0]
url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc + rev))
return f'{match[1]}'
-
@register.filter(name="urlize_ietf_docs", is_safe=True, needs_autoescape=True)
def urlize_ietf_docs(string, autoescape=None):
"""
@@ -255,6 +255,7 @@ def urlize_ietf_docs(string, autoescape=None):
string,
flags=re.IGNORECASE | re.ASCII,
)
+
return mark_safe(string)
@@ -267,7 +268,7 @@ def urlize_related_source_list(related, document_html=False):
names = set()
titles = set()
for rel in related:
- name=rel.source.canonical_name()
+ name=rel.source.name
title = rel.source.title
if name in names and title in titles:
continue
@@ -288,8 +289,8 @@ def urlize_related_target_list(related, document_html=False):
"""Convert a list of RelatedDocuments into list of links using the target document's canonical name"""
links = []
for rel in related:
- name=rel.target.document.canonical_name()
- title = rel.target.document.title
+ name=rel.target.name
+ title = rel.target.title
url = urlreverse('ietf.doc.views_doc.document_main' if document_html is False else 'ietf.doc.views_doc.document_html', kwargs=dict(name=name))
name = escape(name)
title = escape(title)
@@ -556,7 +557,7 @@ def consensus(doc):
@register.filter
def std_level_to_label_format(doc):
"""Returns valid Bootstrap classes to label a status level badge."""
- if doc.is_rfc():
+ if doc.type_id == "rfc":
if doc.related_that("obs"):
return "obs"
else:
diff --git a/ietf/doc/templatetags/tests_ietf_filters.py b/ietf/doc/templatetags/tests_ietf_filters.py
index f791d61530..72796abeb2 100644
--- a/ietf/doc/templatetags/tests_ietf_filters.py
+++ b/ietf/doc/templatetags/tests_ietf_filters.py
@@ -3,12 +3,12 @@
from django.conf import settings
from ietf.doc.factories import (
- WgDraftFactory,
+ WgRfcFactory,
IndividualDraftFactory,
CharterFactory,
NewRevisionDocEventFactory,
)
-from ietf.doc.models import State, DocEvent, DocAlias
+from ietf.doc.models import DocEvent
from ietf.doc.templatetags.ietf_filters import urlize_ietf_docs, is_valid_url
from ietf.person.models import Person
from ietf.utils.test_utils import TestCase
@@ -25,23 +25,21 @@ def test_is_valid_url(self):
self.assertEqual(is_valid_url(url), result)
def test_urlize_ietf_docs(self):
- wg_id = WgDraftFactory()
- wg_id.set_state(State.objects.get(type="draft", slug="rfc"))
- wg_id.std_level_id = "bcp"
- wg_id.save_with_history(
+ rfc = WgRfcFactory(rfc_number=123456,std_level_id="bcp")
+ rfc.save_with_history(
[
DocEvent.objects.create(
- doc=wg_id,
- rev=wg_id.rev,
+ doc=rfc,
+ rev=rfc.rev,
type="published_rfc",
by=Person.objects.get(name="(System)"),
)
]
)
- DocAlias.objects.create(name="rfc123456").docs.add(wg_id)
- DocAlias.objects.create(name="bcp123456").docs.add(wg_id)
- DocAlias.objects.create(name="std123456").docs.add(wg_id)
- DocAlias.objects.create(name="fyi123456").docs.add(wg_id)
+ # TODO - bring these into existance when subseries are well modeled
+ # DocAlias.objects.create(name="bcp123456").docs.add(rfc)
+ # DocAlias.objects.create(name="std123456").docs.add(rfc)
+ # DocAlias.objects.create(name="fyi123456").docs.add(rfc)
id = IndividualDraftFactory(name="draft-me-rfc123456bis")
id_num = IndividualDraftFactory(name="draft-rosen-rfcefdp-update-2026")
@@ -59,15 +57,17 @@ def test_urlize_ietf_docs(self):
cases = [
("no change", "no change"),
- ("bCp123456", 'bCp123456'),
- ("Std 00123456", 'Std 00123456'),
- (
- "FyI 0123456 changes std 00123456",
- 'FyI 0123456 changes std 00123456',
- ),
+
+ # TODO: rework subseries when we add them
+ # ("bCp123456", 'bCp123456'),
+ # ("Std 00123456", 'Std 00123456'),
+ # (
+ # "FyI 0123456 changes std 00123456",
+ # 'FyI 0123456 changes std 00123456',
+ # ),
("rfc123456", 'rfc123456'),
("Rfc 0123456", 'Rfc 0123456'),
- (wg_id.name, f'{wg_id.name}'),
+ (rfc.name, f'{rfc.name}'),
(
f"{id.name}-{id.rev}.txt",
f'{id.name}-{id.rev}.txt',
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
index ace55a0d79..f14b5b1af0 100644
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -33,7 +33,7 @@
import debug # pyflakes:ignore
-from ietf.doc.models import ( Document, DocAlias, DocRelationshipName, RelatedDocument, State,
+from ietf.doc.models import ( Document, DocRelationshipName, RelatedDocument, State,
DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, NewRevisionDocEvent, BallotType,
EditedAuthorsDocEvent )
from ietf.doc.factories import ( DocumentFactory, DocEventFactory, CharterFactory,
@@ -66,6 +66,7 @@ class SearchTests(TestCase):
def test_search(self):
draft = WgDraftFactory(name='draft-ietf-mars-test',group=GroupFactory(acronym='mars',parent=Group.objects.get(acronym='farfut')),authors=[PersonFactory()],ad=PersonFactory())
+ rfc = WgRfcFactory()
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="pub-req"))
old_draft = IndividualDraftFactory(name='draft-foo-mars-test',authors=[PersonFactory()],title="Optimizing Martian Network Topologies")
old_draft.set_state(State.objects.get(used=True, type="draft", slug="expired"))
@@ -97,11 +98,12 @@ def test_search(self):
self.assertEqual(r.status_code, 200)
self.assertContains(r, "draft-foo-mars-test")
- # find by rfc/active/inactive
- draft.set_state(State.objects.get(type="draft", slug="rfc"))
- r = self.client.get(base_url + "?rfcs=on&name=%s" % draft.name)
+ # find by RFC
+ r = self.client.get(base_url + "?rfcs=on&name=%s" % rfc.name)
self.assertEqual(r.status_code, 200)
- self.assertContains(r, draft.title)
+ self.assertContains(r, rfc.title)
+
+ # find by active/inactive
draft.set_state(State.objects.get(type="draft", slug="active"))
r = self.client.get(base_url + "?activedrafts=on&name=%s" % draft.name)
@@ -340,9 +342,7 @@ def test_docs_for_ad(self):
draft = IndividualDraftFactory(ad=ad)
draft.action_holders.set([PersonFactory()])
draft.set_state(State.objects.get(type='draft-iesg', slug='lc'))
- rfc = IndividualDraftFactory(ad=ad)
- rfc.set_state(State.objects.get(type='draft', slug='rfc'))
- DocAlias.objects.create(name='rfc6666').docs.add(rfc)
+ rfc = IndividualRfcFactory(ad=ad)
conflrev = DocumentFactory(type_id='conflrev',ad=ad)
conflrev.set_state(State.objects.get(type='conflrev', slug='iesgeval'))
statchg = DocumentFactory(type_id='statchg',ad=ad)
@@ -366,7 +366,7 @@ def test_docs_for_ad(self):
self.assertEqual(r.status_code, 200)
self.assertContains(r, draft.name)
self.assertContains(r, escape(draft.action_holders.first().name))
- self.assertContains(r, rfc.canonical_name())
+ self.assertContains(r, rfc.name)
self.assertContains(r, conflrev.name)
self.assertContains(r, statchg.name)
self.assertContains(r, charter.name)
@@ -414,16 +414,17 @@ def test_indexes(self):
r = self.client.get(urlreverse('ietf.doc.views_search.index_all_drafts'))
self.assertEqual(r.status_code, 200)
self.assertContains(r, draft.name)
- self.assertContains(r, rfc.canonical_name().upper())
+ self.assertContains(r, rfc.name.upper())
r = self.client.get(urlreverse('ietf.doc.views_search.index_active_drafts'))
self.assertEqual(r.status_code, 200)
self.assertContains(r, draft.title)
def test_ajax_search_docs(self):
- draft = IndividualDraftFactory()
+ draft = IndividualDraftFactory(name="draft-ietf-rfc1234bis")
+ rfc = IndividualRfcFactory(rfc_number=1234)
+ bcp = IndividualRfcFactory(name="bcp12345", type_id="bcp")
- # Document
url = urlreverse('ietf.doc.views_search.ajax_select2_search_docs', kwargs={
"model_name": "document",
"doc_type": "draft",
@@ -433,18 +434,27 @@ def test_ajax_search_docs(self):
data = r.json()
self.assertEqual(data[0]["id"], draft.pk)
- # DocAlias
- doc_alias = draft.docalias.first()
-
url = urlreverse('ietf.doc.views_search.ajax_select2_search_docs', kwargs={
- "model_name": "docalias",
- "doc_type": "draft",
+ "model_name": "document",
+ "doc_type": "rfc",
})
+ r = self.client.get(url, dict(q=rfc.name))
+ self.assertEqual(r.status_code, 200)
+ data = r.json()
+ self.assertEqual(data[0]["id"], rfc.pk)
- r = self.client.get(url, dict(q=doc_alias.name))
+ url = urlreverse('ietf.doc.views_search.ajax_select2_search_docs', kwargs={
+ "model_name": "document",
+ "doc_type": "all",
+ })
+ r = self.client.get(url, dict(q="1234"))
self.assertEqual(r.status_code, 200)
data = r.json()
- self.assertEqual(data[0]["id"], doc_alias.pk)
+ self.assertEqual(len(data), 3)
+ pks = set([data[i]["id"] for i in range(3)])
+ self.assertEqual(pks, set([bcp.pk, rfc.pk, draft.pk]))
+
+
def test_recent_drafts(self):
# Three drafts to show with various warnings
@@ -648,23 +658,22 @@ def setUp(self):
def test_document_draft(self):
draft = WgDraftFactory(name='draft-ietf-mars-test',rev='01', create_revisions=range(0,2))
-
HolderIprDisclosureFactory(docs=[draft])
# Docs for testing relationships. Does not test 'possibly-replaces'. The 'replaced_by' direction
# is tested separately below.
replaced = IndividualDraftFactory()
- draft.relateddocument_set.create(relationship_id='replaces',source=draft,target=replaced.docalias.first())
+ draft.relateddocument_set.create(relationship_id='replaces',source=draft,target=replaced)
obsoleted = IndividualDraftFactory()
- draft.relateddocument_set.create(relationship_id='obs',source=draft,target=obsoleted.docalias.first())
+ draft.relateddocument_set.create(relationship_id='obs',source=draft,target=obsoleted)
obsoleted_by = IndividualDraftFactory()
- obsoleted_by.relateddocument_set.create(relationship_id='obs',source=obsoleted_by,target=draft.docalias.first())
+ obsoleted_by.relateddocument_set.create(relationship_id='obs',source=obsoleted_by,target=draft)
updated = IndividualDraftFactory()
- draft.relateddocument_set.create(relationship_id='updates',source=draft,target=updated.docalias.first())
+ draft.relateddocument_set.create(relationship_id='updates',source=draft,target=updated)
updated_by = IndividualDraftFactory()
- updated_by.relateddocument_set.create(relationship_id='updates',source=obsoleted_by,target=draft.docalias.first())
+ updated_by.relateddocument_set.create(relationship_id='updates',source=obsoleted_by,target=draft)
- external_resource = DocExtResourceFactory(doc=draft)
+ DocExtResourceFactory(doc=draft)
# these tests aren't testing all attributes yet, feel free to
# expand them
@@ -675,69 +684,32 @@ def test_document_draft(self):
if settings.USER_PREFERENCE_DEFAULTS['full_draft'] == 'off':
self.assertContains(r, "Show full document")
self.assertNotContains(r, "Deimos street")
- self.assertContains(r, replaced.canonical_name())
+ self.assertContains(r, replaced.name)
self.assertContains(r, replaced.title)
- # obs/updates not included until draft is RFC
- self.assertNotContains(r, obsoleted.canonical_name())
- self.assertNotContains(r, obsoleted.title)
- self.assertNotContains(r, obsoleted_by.canonical_name())
- self.assertNotContains(r, obsoleted_by.title)
- self.assertNotContains(r, updated.canonical_name())
- self.assertNotContains(r, updated.title)
- self.assertNotContains(r, updated_by.canonical_name())
- self.assertNotContains(r, updated_by.title)
- self.assertContains(r, external_resource.value)
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)) + "?include_text=0")
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Active Internet-Draft")
self.assertContains(r, "Show full document")
self.assertNotContains(r, "Deimos street")
- self.assertContains(r, replaced.canonical_name())
+ self.assertContains(r, replaced.name)
self.assertContains(r, replaced.title)
- # obs/updates not included until draft is RFC
- self.assertNotContains(r, obsoleted.canonical_name())
- self.assertNotContains(r, obsoleted.title)
- self.assertNotContains(r, obsoleted_by.canonical_name())
- self.assertNotContains(r, obsoleted_by.title)
- self.assertNotContains(r, updated.canonical_name())
- self.assertNotContains(r, updated.title)
- self.assertNotContains(r, updated_by.canonical_name())
- self.assertNotContains(r, updated_by.title)
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)) + "?include_text=foo")
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Active Internet-Draft")
self.assertNotContains(r, "Show full document")
self.assertContains(r, "Deimos street")
- self.assertContains(r, replaced.canonical_name())
+ self.assertContains(r, replaced.name)
self.assertContains(r, replaced.title)
- # obs/updates not included until draft is RFC
- self.assertNotContains(r, obsoleted.canonical_name())
- self.assertNotContains(r, obsoleted.title)
- self.assertNotContains(r, obsoleted_by.canonical_name())
- self.assertNotContains(r, obsoleted_by.title)
- self.assertNotContains(r, updated.canonical_name())
- self.assertNotContains(r, updated.title)
- self.assertNotContains(r, updated_by.canonical_name())
- self.assertNotContains(r, updated_by.title)
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)) + "?include_text=1")
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Active Internet-Draft")
self.assertNotContains(r, "Show full document")
self.assertContains(r, "Deimos street")
- self.assertContains(r, replaced.canonical_name())
+ self.assertContains(r, replaced.name)
self.assertContains(r, replaced.title)
- # obs/updates not included until draft is RFC
- self.assertNotContains(r, obsoleted.canonical_name())
- self.assertNotContains(r, obsoleted.title)
- self.assertNotContains(r, obsoleted_by.canonical_name())
- self.assertNotContains(r, obsoleted_by.title)
- self.assertNotContains(r, updated.canonical_name())
- self.assertNotContains(r, updated.title)
- self.assertNotContains(r, updated_by.canonical_name())
- self.assertNotContains(r, updated_by.title)
self.client.cookies = SimpleCookie({str('full_draft'): str('on')})
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)))
@@ -745,17 +717,8 @@ def test_document_draft(self):
self.assertContains(r, "Active Internet-Draft")
self.assertNotContains(r, "Show full document")
self.assertContains(r, "Deimos street")
- self.assertContains(r, replaced.canonical_name())
+ self.assertContains(r, replaced.name)
self.assertContains(r, replaced.title)
- # obs/updates not included until draft is RFC
- self.assertNotContains(r, obsoleted.canonical_name())
- self.assertNotContains(r, obsoleted.title)
- self.assertNotContains(r, obsoleted_by.canonical_name())
- self.assertNotContains(r, obsoleted_by.title)
- self.assertNotContains(r, updated.canonical_name())
- self.assertNotContains(r, updated.title)
- self.assertNotContains(r, updated_by.canonical_name())
- self.assertNotContains(r, updated_by.title)
self.client.cookies = SimpleCookie({str('full_draft'): str('off')})
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)))
@@ -763,17 +726,8 @@ def test_document_draft(self):
self.assertContains(r, "Active Internet-Draft")
self.assertContains(r, "Show full document")
self.assertNotContains(r, "Deimos street")
- self.assertContains(r, replaced.canonical_name())
+ self.assertContains(r, replaced.name)
self.assertContains(r, replaced.title)
- # obs/updates not included until draft is RFC
- self.assertNotContains(r, obsoleted.canonical_name())
- self.assertNotContains(r, obsoleted.title)
- self.assertNotContains(r, obsoleted_by.canonical_name())
- self.assertNotContains(r, obsoleted_by.title)
- self.assertNotContains(r, updated.canonical_name())
- self.assertNotContains(r, updated.title)
- self.assertNotContains(r, updated_by.canonical_name())
- self.assertNotContains(r, updated_by.title)
self.client.cookies = SimpleCookie({str('full_draft'): str('foo')})
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)))
@@ -782,17 +736,8 @@ def test_document_draft(self):
if settings.USER_PREFERENCE_DEFAULTS['full_draft'] == 'off':
self.assertContains(r, "Show full document")
self.assertNotContains(r, "Deimos street")
- self.assertContains(r, replaced.canonical_name())
+ self.assertContains(r, replaced.name)
self.assertContains(r, replaced.title)
- # obs/updates not included until draft is RFC
- self.assertNotContains(r, obsoleted.canonical_name())
- self.assertNotContains(r, obsoleted.title)
- self.assertNotContains(r, obsoleted_by.canonical_name())
- self.assertNotContains(r, obsoleted_by.title)
- self.assertNotContains(r, updated.canonical_name())
- self.assertNotContains(r, updated.title)
- self.assertNotContains(r, updated_by.canonical_name())
- self.assertNotContains(r, updated_by.title)
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=draft.name)))
self.assertEqual(r.status_code, 200)
@@ -818,16 +763,16 @@ def test_document_draft(self):
rfc = WgRfcFactory()
rfc.save_with_history([DocEventFactory(doc=rfc)])
(Path(settings.RFC_PATH) / rfc.get_base_name()).touch()
- r = self.client.get(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=rfc.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())))
- 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()}')))
- 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=rfc.rfc_number)))
+ self.assertRedirects(r, urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.name)))
+ 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.name)))
# expired draft
draft.set_state(State.objects.get(type="draft", slug="expired"))
@@ -848,46 +793,49 @@ def test_document_draft(self):
shepherd_id=draft.shepherd_id, ad_id=draft.ad_id, expires=draft.expires,
notify=draft.notify)
rel = RelatedDocument.objects.create(source=replacement,
- target=draft.docalias.get(name__startswith="draft"),
+ target=draft,
relationship_id="replaces")
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)))
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Replaced Internet-Draft")
- self.assertContains(r, replacement.canonical_name())
+ self.assertContains(r, replacement.name)
self.assertContains(r, replacement.title)
rel.delete()
# draft published as RFC
draft.set_state(State.objects.get(type="draft", slug="rfc"))
- draft.std_level_id = "bcp"
- draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="published_rfc", by=Person.objects.get(name="(System)"))])
+ draft.std_level_id = "ps"
+
+ rfc = WgRfcFactory(group=draft.group, name="rfc123456")
+ rfc.save_with_history([DocEvent.objects.create(doc=rfc, rev=None, type="published_rfc", by=Person.objects.get(name="(System)"))])
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
- rfc_alias = DocAlias.objects.create(name="rfc123456")
- rfc_alias.docs.add(draft)
- bcp_alias = DocAlias.objects.create(name="bcp123456")
- bcp_alias.docs.add(draft)
+ obsoleted = IndividualRfcFactory()
+ rfc.relateddocument_set.create(relationship_id='obs',target=obsoleted)
+ obsoleted_by = IndividualRfcFactory()
+ obsoleted_by.relateddocument_set.create(relationship_id='obs',target=rfc)
+ updated = IndividualRfcFactory()
+ rfc.relateddocument_set.create(relationship_id='updates',target=updated)
+ updated_by = IndividualRfcFactory()
+ updated_by.relateddocument_set.create(relationship_id='updates',target=rfc)
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name)))
self.assertEqual(r.status_code, 302)
- r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=bcp_alias.name)))
- self.assertEqual(r.status_code, 302)
- r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_alias.name)))
+ r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc.name)))
self.assertEqual(r.status_code, 200)
self.assertContains(r, "RFC 123456")
self.assertContains(r, draft.name)
- self.assertContains(r, replaced.canonical_name())
- self.assertContains(r, replaced.title)
# obs/updates included with RFC
- self.assertContains(r, obsoleted.canonical_name())
+ self.assertContains(r, obsoleted.name)
self.assertContains(r, obsoleted.title)
- self.assertContains(r, obsoleted_by.canonical_name())
+ self.assertContains(r, obsoleted_by.name)
self.assertContains(r, obsoleted_by.title)
- self.assertContains(r, updated.canonical_name())
+ self.assertContains(r, updated.name)
self.assertContains(r, updated.title)
- self.assertContains(r, updated_by.canonical_name())
+ self.assertContains(r, updated_by.name)
self.assertContains(r, updated_by.title)
# naked RFC - also weird that we test a PS from the ISE
@@ -920,7 +868,7 @@ def test_draft_status_changes(self):
draft = WgRfcFactory()
status_change_doc = StatusChangeFactory(
group=draft.group,
- changes_status_of=[('tops', draft.docalias.first())],
+ changes_status_of=[('tops', draft)],
)
status_change_url = urlreverse(
'ietf.doc.views_doc.document_main',
@@ -928,7 +876,7 @@ def test_draft_status_changes(self):
)
proposed_status_change_doc = StatusChangeFactory(
group=draft.group,
- changes_status_of=[('tobcp', draft.docalias.first())],
+ changes_status_of=[('tobcp', draft)],
states=[State.objects.get(slug='needshep', type='statchg')],
)
proposed_status_change_url = urlreverse(
@@ -939,7 +887,7 @@ def test_draft_status_changes(self):
r = self.client.get(
urlreverse(
'ietf.doc.views_doc.document_main',
- kwargs={'name': draft.canonical_name()},
+ kwargs={'name': draft.name},
)
)
self.assertEqual(r.status_code, 200)
@@ -1519,11 +1467,11 @@ def test_draft_group_link(self):
self.assertEqual(r.status_code, 200)
self.assert_correct_wg_group_link(r, group)
- rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group)
+ rfc = WgRfcFactory(group=group)
+ draft = WgDraftFactory(group=group)
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
DocEventFactory.create(doc=rfc, type='published_rfc', time=event_datetime)
- # get the rfc name to avoid a redirect
- rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name
- r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name)))
+ r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc.name)))
self.assertEqual(r.status_code, 200)
self.assert_correct_wg_group_link(r, group)
@@ -1534,11 +1482,11 @@ def test_draft_group_link(self):
self.assertEqual(r.status_code, 200)
self.assert_correct_non_wg_group_link(r, group)
- rfc = WgRfcFactory(name='draft-rfc-document-%s' % group_type_id, group=group)
+ rfc = WgRfcFactory(group=group)
+ draft = WgDraftFactory(name='draft-rfc-document-%s'% group_type_id, group=group)
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
DocEventFactory.create(doc=rfc, type='published_rfc', time=event_datetime)
- # get the rfc name to avoid a redirect
- rfc_name = rfc.docalias.filter(name__startswith='rfc').first().name
- r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc_name)))
+ r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=rfc.name)))
self.assertEqual(r.status_code, 200)
self.assert_correct_non_wg_group_link(r, group)
@@ -1639,8 +1587,8 @@ def test_status_change(self):
statchg = StatusChangeFactory()
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=statchg.name)))
self.assertEqual(r.status_code, 200)
- r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=statchg.relateddocument_set.first().target.document)))
- self.assertEqual(r.status_code, 302)
+ r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=statchg.relateddocument_set.first().target)))
+ self.assertEqual(r.status_code, 200)
def test_document_charter(self):
CharterFactory(name='charter-ietf-mars')
@@ -1804,8 +1752,8 @@ def test_document_ballot_needed_positions(self):
self.assertNotContains(r, 'more YES or NO')
# status change
- DocAlias.objects.create(name='rfc9998').docs.add(IndividualDraftFactory())
- DocAlias.objects.create(name='rfc9999').docs.add(IndividualDraftFactory())
+ Document.objects.create(name='rfc9998')
+ Document.objects.create(name='rfc9999')
doc = DocumentFactory(type_id='statchg',name='status-change-imaginary-mid-review')
iesgeval_pk = str(State.objects.get(slug='iesgeval',type__slug='statchg').pk)
empty_outbox()
@@ -1818,12 +1766,12 @@ def test_document_ballot_needed_positions(self):
self.assertIn('iesg-secretary',outbox[0]['To'])
self.assertIn('drafts-eval',outbox[1]['To'])
- doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
+ doc.relateddocument_set.create(target=Document.objects.get(name='rfc9998'),relationship_id='tohist')
r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name)))
self.assertNotContains(r, 'Needs a YES')
self.assertNotContains(r, 'more YES or NO')
- doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
+ doc.relateddocument_set.create(target=Document.objects.get(name='rfc9999'),relationship_id='tois')
r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name)))
self.assertContains(r, 'more YES or NO')
@@ -1886,15 +1834,14 @@ def test_history(self):
self.assertContains(r, e.desc)
def test_history_bis_00(self):
- rfcname='rfc9090'
- rfc = WgRfcFactory(alias2=rfcname)
- bis_draft = WgDraftFactory(name='draft-ietf-{}-{}bis'.format(rfc.group.acronym,rfcname))
+ rfc = WgRfcFactory(rfc_number=9090)
+ bis_draft = WgDraftFactory(name='draft-ietf-{}-{}bis'.format(rfc.group.acronym,rfc.name))
url = urlreverse('ietf.doc.views_doc.document_history', kwargs=dict(name=bis_draft.name))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(unicontent(r))
- attr1='value="{}"'.format(rfcname)
+ attr1='value="{}"'.format(rfc.name)
self.assertEqual(len(q('option['+attr1+'][selected="selected"]')), 1)
@@ -1944,7 +1891,7 @@ def test_last_call_feed(self):
self.assertContains(r, doc.name)
def test_rfc_feed(self):
- rfc = WgRfcFactory(alias2__name="rfc9000")
+ rfc = WgRfcFactory(rfc_number=9000)
DocEventFactory(doc=rfc, type="published_rfc")
r = self.client.get("/feed/rfc/")
self.assertTrue(r.status_code, 200)
@@ -2002,75 +1949,84 @@ def _parse_bibtex_response(self, response) -> dict:
@override_settings(RFC_EDITOR_INFO_BASE_URL='https://www.rfc-editor.ietf.org/info/')
def test_document_bibtex(self):
rfc = WgRfcFactory.create(
- #other_aliases = ['rfc6020',],
- states = [('draft','rfc'),('draft-iesg','pub')],
- std_level_id = 'ps',
- time = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE)),
- )
- num = rfc.rfc_number()
+ time=datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE))
+ )
+ num = rfc.rfc_number
DocEventFactory.create(
doc=rfc,
- type='published_rfc',
+ type="published_rfc",
time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO),
)
#
- url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name))
+ url = urlreverse("ietf.doc.views_doc.document_bibtex", kwargs=dict(name=rfc.name))
r = self.client.get(url)
- entry = self._parse_bibtex_response(r)["rfc%s"%num]
- self.assertEqual(entry['series'], 'Request for Comments')
- self.assertEqual(entry['number'], num)
- self.assertEqual(entry['doi'], '10.17487/RFC%s'%num)
- self.assertEqual(entry['year'], '2010')
- self.assertEqual(entry['month'].lower()[0:3], 'oct')
- self.assertEqual(entry['url'], f'https://www.rfc-editor.ietf.org/info/rfc{num}')
+ entry = self._parse_bibtex_response(r)["rfc%s" % num]
+ self.assertEqual(entry["series"], "Request for Comments")
+ self.assertEqual(int(entry["number"]), num)
+ self.assertEqual(entry["doi"], "10.17487/RFC%s" % num)
+ self.assertEqual(entry["year"], "2010")
+ self.assertEqual(entry["month"].lower()[0:3], "oct")
+ self.assertEqual(entry["url"], f"https://www.rfc-editor.ietf.org/info/rfc{num}")
#
- self.assertNotIn('day', entry)
-
+ self.assertNotIn("day", entry)
+
# test for incorrect case - revision for RFC
rfc = WgRfcFactory(name="rfc0000")
- url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name, rev='00'))
+ url = urlreverse(
+ "ietf.doc.views_doc.document_bibtex", kwargs=dict(name=rfc.name, rev="00")
+ )
r = self.client.get(url)
self.assertEqual(r.status_code, 404)
-
+
april1 = IndividualRfcFactory.create(
- stream_id = 'ise',
- states = [('draft','rfc'),('draft-iesg','pub')],
- std_level_id = 'inf',
- time = datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo(settings.TIME_ZONE)),
- )
- num = april1.rfc_number()
+ stream_id="ise",
+ std_level_id="inf",
+ time=datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo(settings.TIME_ZONE)),
+ )
+ num = april1.rfc_number
DocEventFactory.create(
doc=april1,
- type='published_rfc',
+ type="published_rfc",
time=datetime.datetime(1990, 4, 1, tzinfo=RPC_TZINFO),
)
#
- url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=april1.name))
+ url = urlreverse(
+ "ietf.doc.views_doc.document_bibtex", kwargs=dict(name=april1.name)
+ )
r = self.client.get(url)
- self.assertEqual(r.get('Content-Type'), 'text/plain; charset=utf-8')
- entry = self._parse_bibtex_response(r)["rfc%s"%num]
- self.assertEqual(entry['series'], 'Request for Comments')
- self.assertEqual(entry['number'], num)
- self.assertEqual(entry['doi'], '10.17487/RFC%s'%num)
- self.assertEqual(entry['year'], '1990')
- self.assertEqual(entry['month'].lower()[0:3], 'apr')
- self.assertEqual(entry['day'], '1')
- self.assertEqual(entry['url'], f'https://www.rfc-editor.ietf.org/info/rfc{num}')
-
+ self.assertEqual(r.get("Content-Type"), "text/plain; charset=utf-8")
+ entry = self._parse_bibtex_response(r)["rfc%s" % num]
+ self.assertEqual(entry["series"], "Request for Comments")
+ self.assertEqual(int(entry["number"]), num)
+ self.assertEqual(entry["doi"], "10.17487/RFC%s" % num)
+ self.assertEqual(entry["year"], "1990")
+ self.assertEqual(entry["month"].lower()[0:3], "apr")
+ self.assertEqual(entry["day"], "1")
+ self.assertEqual(entry["url"], f"https://www.rfc-editor.ietf.org/info/rfc{num}")
+
draft = IndividualDraftFactory.create()
- docname = '%s-%s' % (draft.name, draft.rev)
- bibname = docname[6:] # drop the 'draft-' prefix
- url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=draft.name))
+ docname = "%s-%s" % (draft.name, draft.rev)
+ bibname = docname[6:] # drop the 'draft-' prefix
+ url = urlreverse("ietf.doc.views_doc.document_bibtex", kwargs=dict(name=draft.name))
r = self.client.get(url)
entry = self._parse_bibtex_response(r)[bibname]
- self.assertEqual(entry['note'], 'Work in Progress')
- self.assertEqual(entry['number'], docname)
- self.assertEqual(entry['year'], str(draft.pub_date().year))
- self.assertEqual(entry['month'].lower()[0:3], draft.pub_date().strftime('%b').lower())
- self.assertEqual(entry['day'], str(draft.pub_date().day))
- self.assertEqual(entry['url'], settings.IDTRACKER_BASE_URL + urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name, rev=draft.rev)))
+ self.assertEqual(entry["note"], "Work in Progress")
+ self.assertEqual(entry["number"], docname)
+ self.assertEqual(entry["year"], str(draft.pub_date().year))
+ self.assertEqual(
+ entry["month"].lower()[0:3], draft.pub_date().strftime("%b").lower()
+ )
+ self.assertEqual(entry["day"], str(draft.pub_date().day))
+ self.assertEqual(
+ entry["url"],
+ settings.IDTRACKER_BASE_URL
+ + urlreverse(
+ "ietf.doc.views_doc.document_main",
+ kwargs=dict(name=draft.name, rev=draft.rev),
+ ),
+ )
#
- self.assertNotIn('doi', entry)
+ self.assertNotIn("doi", entry)
def test_document_bibxml(self):
draft = IndividualDraftFactory.create()
@@ -2147,7 +2103,7 @@ class ReferencesTest(TestCase):
def test_references(self):
doc1 = WgDraftFactory(name='draft-ietf-mars-test')
- doc2 = IndividualDraftFactory(name='draft-imaginary-independent-submission').docalias.first()
+ doc2 = IndividualDraftFactory(name='draft-imaginary-independent-submission')
RelatedDocument.objects.get_or_create(source=doc1,target=doc2,relationship=DocRelationshipName.objects.get(slug='refnorm'))
url = urlreverse('ietf.doc.views_doc.document_references', kwargs=dict(name=doc1.name))
r = self.client.get(url)
@@ -2159,124 +2115,168 @@ def test_references(self):
self.assertContains(r, doc1.name)
class GenerateDraftAliasesTests(TestCase):
- def setUp(self):
- super().setUp()
- self.doc_aliases_file = NamedTemporaryFile(delete=False, mode='w+')
- self.doc_aliases_file.close()
- self.doc_virtual_file = NamedTemporaryFile(delete=False, mode='w+')
- self.doc_virtual_file.close()
- self.saved_draft_aliases_path = settings.DRAFT_ALIASES_PATH
- self.saved_draft_virtual_path = settings.DRAFT_VIRTUAL_PATH
- settings.DRAFT_ALIASES_PATH = self.doc_aliases_file.name
- settings.DRAFT_VIRTUAL_PATH = self.doc_virtual_file.name
-
- def tearDown(self):
- settings.DRAFT_ALIASES_PATH = self.saved_draft_aliases_path
- settings.DRAFT_VIRTUAL_PATH = self.saved_draft_virtual_path
- os.unlink(self.doc_aliases_file.name)
- os.unlink(self.doc_virtual_file.name)
- super().tearDown()
-
- def testManagementCommand(self):
- a_month_ago = (timezone.now() - datetime.timedelta(30)).astimezone(RPC_TZINFO)
- a_month_ago = a_month_ago.replace(hour=0, minute=0, second=0, microsecond=0)
- ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person
- shepherd = PersonFactory()
- author1 = PersonFactory()
- author2 = PersonFactory()
- author3 = PersonFactory()
- author4 = PersonFactory()
- author5 = PersonFactory()
- author6 = PersonFactory()
- mars = GroupFactory(type_id='wg', acronym='mars')
- marschairman = PersonFactory(user__username='marschairman')
- mars.role_set.create(name_id='chair', person=marschairman, email=marschairman.email())
- doc1 = IndividualDraftFactory(authors=[author1], shepherd=shepherd.email(), ad=ad)
- doc2 = WgDraftFactory(name='draft-ietf-mars-test', group__acronym='mars', authors=[author2], ad=ad)
- doc3 = WgRfcFactory.create(name='draft-ietf-mars-finished', group__acronym='mars', authors=[author3], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=a_month_ago)
- DocEventFactory.create(doc=doc3, type='published_rfc', time=a_month_ago)
- doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10, tzinfo=ZoneInfo(settings.TIME_ZONE)))
- DocEventFactory.create(doc=doc4, type='published_rfc', time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO))
- doc5 = IndividualDraftFactory(authors=[author6])
-
- args = [ ]
- kwargs = { }
- out = io.StringIO()
- call_command("generate_draft_aliases", *args, **kwargs, stdout=out, stderr=out)
- self.assertFalse(out.getvalue())
-
- with open(settings.DRAFT_ALIASES_PATH) as afile:
- acontent = afile.read()
- self.assertTrue(all([x in acontent for x in [
- 'xfilter-' + doc1.name,
- 'xfilter-' + doc1.name + '.ad',
- 'xfilter-' + doc1.name + '.authors',
- 'xfilter-' + doc1.name + '.shepherd',
- 'xfilter-' + doc1.name + '.all',
- 'xfilter-' + doc2.name,
- 'xfilter-' + doc2.name + '.ad',
- 'xfilter-' + doc2.name + '.authors',
- 'xfilter-' + doc2.name + '.chairs',
- 'xfilter-' + doc2.name + '.all',
- 'xfilter-' + doc3.name,
- 'xfilter-' + doc3.name + '.ad',
- 'xfilter-' + doc3.name + '.authors',
- 'xfilter-' + doc3.name + '.chairs',
- 'xfilter-' + doc5.name,
- 'xfilter-' + doc5.name + '.authors',
- 'xfilter-' + doc5.name + '.all',
- ]]))
- self.assertFalse(all([x in acontent for x in [
- 'xfilter-' + doc1.name + '.chairs',
- 'xfilter-' + doc2.name + '.shepherd',
- 'xfilter-' + doc3.name + '.shepherd',
- 'xfilter-' + doc4.name,
- 'xfilter-' + doc5.name + '.shepherd',
- 'xfilter-' + doc5.name + '.ad',
- ]]))
-
- with open(settings.DRAFT_VIRTUAL_PATH) as vfile:
- vcontent = vfile.read()
- self.assertTrue(all([x in vcontent for x in [
- ad.email_address(),
- shepherd.email_address(),
- marschairman.email_address(),
- author1.email_address(),
- author2.email_address(),
- author3.email_address(),
- author6.email_address(),
- ]]))
- self.assertFalse(all([x in vcontent for x in [
- author4.email_address(),
- author5.email_address(),
- ]]))
- self.assertTrue(all([x in vcontent for x in [
- 'xfilter-' + doc1.name,
- 'xfilter-' + doc1.name + '.ad',
- 'xfilter-' + doc1.name + '.authors',
- 'xfilter-' + doc1.name + '.shepherd',
- 'xfilter-' + doc1.name + '.all',
- 'xfilter-' + doc2.name,
- 'xfilter-' + doc2.name + '.ad',
- 'xfilter-' + doc2.name + '.authors',
- 'xfilter-' + doc2.name + '.chairs',
- 'xfilter-' + doc2.name + '.all',
- 'xfilter-' + doc3.name,
- 'xfilter-' + doc3.name + '.ad',
- 'xfilter-' + doc3.name + '.authors',
- 'xfilter-' + doc3.name + '.chairs',
- 'xfilter-' + doc5.name,
- 'xfilter-' + doc5.name + '.authors',
- 'xfilter-' + doc5.name + '.all',
- ]]))
- self.assertFalse(all([x in vcontent for x in [
- 'xfilter-' + doc1.name + '.chairs',
- 'xfilter-' + doc2.name + '.shepherd',
- 'xfilter-' + doc3.name + '.shepherd',
- 'xfilter-' + doc4.name,
- 'xfilter-' + doc5.name + '.shepherd',
- 'xfilter-' + doc5.name + '.ad',
- ]]))
+ def setUp(self):
+ super().setUp()
+ self.doc_aliases_file = NamedTemporaryFile(delete=False, mode="w+")
+ self.doc_aliases_file.close()
+ self.doc_virtual_file = NamedTemporaryFile(delete=False, mode="w+")
+ self.doc_virtual_file.close()
+ self.saved_draft_aliases_path = settings.DRAFT_ALIASES_PATH
+ self.saved_draft_virtual_path = settings.DRAFT_VIRTUAL_PATH
+ settings.DRAFT_ALIASES_PATH = self.doc_aliases_file.name
+ settings.DRAFT_VIRTUAL_PATH = self.doc_virtual_file.name
+
+ def tearDown(self):
+ settings.DRAFT_ALIASES_PATH = self.saved_draft_aliases_path
+ settings.DRAFT_VIRTUAL_PATH = self.saved_draft_virtual_path
+ os.unlink(self.doc_aliases_file.name)
+ os.unlink(self.doc_virtual_file.name)
+ super().tearDown()
+
+ def testManagementCommand(self):
+ a_month_ago = (timezone.now() - datetime.timedelta(30)).astimezone(RPC_TZINFO)
+ a_month_ago = a_month_ago.replace(hour=0, minute=0, second=0, microsecond=0)
+ ad = RoleFactory(
+ name_id="ad", group__type_id="area", group__state_id="active"
+ ).person
+ shepherd = PersonFactory()
+ author1 = PersonFactory()
+ author2 = PersonFactory()
+ author3 = PersonFactory()
+ author4 = PersonFactory()
+ author5 = PersonFactory()
+ author6 = PersonFactory()
+ mars = GroupFactory(type_id="wg", acronym="mars")
+ marschairman = PersonFactory(user__username="marschairman")
+ mars.role_set.create(
+ name_id="chair", person=marschairman, email=marschairman.email()
+ )
+ doc1 = IndividualDraftFactory(
+ authors=[author1], shepherd=shepherd.email(), ad=ad
+ )
+ doc2 = WgDraftFactory(
+ name="draft-ietf-mars-test", group__acronym="mars", authors=[author2], ad=ad
+ )
+ doc3 = WgDraftFactory.create(
+ name="draft-ietf-mars-finished",
+ group__acronym="mars",
+ authors=[author3],
+ ad=ad,
+ std_level_id="ps",
+ states=[("draft", "rfc"), ("draft-iesg", "pub")],
+ time=a_month_ago,
+ )
+ rfc3 = WgRfcFactory()
+ DocEventFactory.create(doc=rfc3, type="published_rfc", time=a_month_ago)
+ doc3.relateddocument_set.create(
+ relationship_id="became_rfc", target=rfc3
+ )
+ doc4 = WgDraftFactory.create(
+ authors=[author4, author5],
+ ad=ad,
+ std_level_id="ps",
+ states=[("draft", "rfc"), ("draft-iesg", "pub")],
+ time=datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE)),
+ )
+ rfc4 = WgRfcFactory()
+ DocEventFactory.create(
+ doc=rfc4,
+ type="published_rfc",
+ time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO),
+ )
+ doc4.relateddocument_set.create(
+ relationship_id="became_rfc", target=rfc4
+ )
+ doc5 = IndividualDraftFactory(authors=[author6])
+
+ args = []
+ kwargs = {}
+ out = io.StringIO()
+ call_command("generate_draft_aliases", *args, **kwargs, stdout=out, stderr=out)
+ self.assertFalse(out.getvalue())
+
+ with open(settings.DRAFT_ALIASES_PATH) as afile:
+ acontent = afile.read()
+ for x in [
+ "xfilter-" + doc1.name,
+ "xfilter-" + doc1.name + ".ad",
+ "xfilter-" + doc1.name + ".authors",
+ "xfilter-" + doc1.name + ".shepherd",
+ "xfilter-" + doc1.name + ".all",
+ "xfilter-" + doc2.name,
+ "xfilter-" + doc2.name + ".ad",
+ "xfilter-" + doc2.name + ".authors",
+ "xfilter-" + doc2.name + ".chairs",
+ "xfilter-" + doc2.name + ".all",
+ "xfilter-" + doc3.name,
+ "xfilter-" + doc3.name + ".ad",
+ "xfilter-" + doc3.name + ".authors",
+ "xfilter-" + doc3.name + ".chairs",
+ "xfilter-" + doc5.name,
+ "xfilter-" + doc5.name + ".authors",
+ "xfilter-" + doc5.name + ".all",
+ ]:
+ self.assertIn(x, acontent)
+
+ for x in [
+ "xfilter-" + doc1.name + ".chairs",
+ "xfilter-" + doc2.name + ".shepherd",
+ "xfilter-" + doc3.name + ".shepherd",
+ "xfilter-" + doc4.name,
+ "xfilter-" + doc5.name + ".shepherd",
+ "xfilter-" + doc5.name + ".ad",
+ ]:
+ self.assertNotIn(x, acontent)
+
+ with open(settings.DRAFT_VIRTUAL_PATH) as vfile:
+ vcontent = vfile.read()
+ for x in [
+ ad.email_address(),
+ shepherd.email_address(),
+ marschairman.email_address(),
+ author1.email_address(),
+ author2.email_address(),
+ author3.email_address(),
+ author6.email_address(),
+ ]:
+ self.assertIn(x, vcontent)
+
+ for x in [
+ author4.email_address(),
+ author5.email_address(),
+ ]:
+ self.assertNotIn(x, vcontent)
+
+ for x in [
+ "xfilter-" + doc1.name,
+ "xfilter-" + doc1.name + ".ad",
+ "xfilter-" + doc1.name + ".authors",
+ "xfilter-" + doc1.name + ".shepherd",
+ "xfilter-" + doc1.name + ".all",
+ "xfilter-" + doc2.name,
+ "xfilter-" + doc2.name + ".ad",
+ "xfilter-" + doc2.name + ".authors",
+ "xfilter-" + doc2.name + ".chairs",
+ "xfilter-" + doc2.name + ".all",
+ "xfilter-" + doc3.name,
+ "xfilter-" + doc3.name + ".ad",
+ "xfilter-" + doc3.name + ".authors",
+ "xfilter-" + doc3.name + ".chairs",
+ "xfilter-" + doc5.name,
+ "xfilter-" + doc5.name + ".authors",
+ "xfilter-" + doc5.name + ".all",
+ ]:
+ self.assertIn(x, vcontent)
+
+ for x in [
+ "xfilter-" + doc1.name + ".chairs",
+ "xfilter-" + doc2.name + ".shepherd",
+ "xfilter-" + doc3.name + ".shepherd",
+ "xfilter-" + doc4.name,
+ "xfilter-" + doc5.name + ".shepherd",
+ "xfilter-" + doc5.name + ".ad",
+ ]:
+ self.assertNotIn(x, vcontent)
class EmailAliasesTests(TestCase):
@@ -2710,10 +2710,10 @@ class Idnits2SupportTests(TestCase):
settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['DERIVED_DIR']
def test_obsoleted(self):
- rfc = WgRfcFactory(alias2__name='rfc1001')
- WgRfcFactory(alias2__name='rfc1003',relations=[('obs',rfc)])
- rfc = WgRfcFactory(alias2__name='rfc1005')
- WgRfcFactory(alias2__name='rfc1007',relations=[('obs',rfc)])
+ rfc = WgRfcFactory(rfc_number=1001)
+ WgRfcFactory(rfc_number=1003,relations=[('obs',rfc)])
+ rfc = WgRfcFactory(rfc_number=1005)
+ WgRfcFactory(rfc_number=1007,relations=[('obs',rfc)])
url = urlreverse('ietf.doc.views_doc.idnits2_rfcs_obsoleted')
r = self.client.get(url)
@@ -2738,20 +2738,22 @@ def test_rfc_status(self):
def test_idnits2_state(self):
rfc = WgRfcFactory()
- url = urlreverse('ietf.doc.views_doc.idnits2_state', kwargs=dict(name=rfc.canonical_name()))
+ draft = WgDraftFactory()
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
+ url = urlreverse('ietf.doc.views_doc.idnits2_state', kwargs=dict(name=rfc.name))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r,'rfcnum')
draft = WgDraftFactory()
- url = urlreverse('ietf.doc.views_doc.idnits2_state', kwargs=dict(name=draft.canonical_name()))
+ url = urlreverse('ietf.doc.views_doc.idnits2_state', kwargs=dict(name=draft.name))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertNotContains(r,'rfcnum')
self.assertContains(r,'Unknown')
draft = WgDraftFactory(intended_std_level_id='ps')
- url = urlreverse('ietf.doc.views_doc.idnits2_state', kwargs=dict(name=draft.canonical_name()))
+ url = urlreverse('ietf.doc.views_doc.idnits2_state', kwargs=dict(name=draft.name))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r,'Proposed')
@@ -2796,16 +2798,12 @@ def test_raw_id(self):
self.should_succeed(dict(name=draft.name, rev='00',ext='txt'))
self.should_404(dict(name=draft.name, rev='00',ext='html'))
- def test_raw_id_rfc(self):
- rfc = WgRfcFactory()
- dir = settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR
- (Path(dir) / f'{rfc.name}-{rfc.rev}.txt').touch()
- self.should_succeed(dict(name=rfc.name))
- self.should_404(dict(name=rfc.canonical_name()))
+ # test_raw_id_rfc intentionally removed
+ # an rfc is no longer a pseudo-version of a draft.
def test_non_draft(self):
- charter = CharterFactory()
- self.should_404(dict(name=charter.name))
+ for doc in [CharterFactory(), WgRfcFactory()]:
+ self.should_404(dict(name=doc.name))
class PdfizedTests(TestCase):
@@ -2824,24 +2822,27 @@ def should_404(self, argdict):
r = self.client.get(url)
self.assertEqual(r.status_code, 404)
+ # This takes a _long_ time (32s on a 2022 m1 macbook pro) - is it worth what it covers?
def test_pdfized(self):
- rfc = WgRfcFactory(create_revisions=range(0,2))
+ rfc = WgRfcFactory()
+ draft = WgDraftFactory(create_revisions=range(0,2))
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
dir = settings.RFC_PATH
- with (Path(dir) / f'{rfc.canonical_name()}.txt').open('w') as f:
+ with (Path(dir) / f'{rfc.name}.txt').open('w') as f:
f.write('text content')
dir = settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR
for r in range(0,2):
- with (Path(dir) / f'{rfc.name}-{r:02d}.txt').open('w') as f:
+ with (Path(dir) / f'{draft.name}-{r:02d}.txt').open('w') as f:
f.write('text content')
- self.should_succeed(dict(name=rfc.canonical_name()))
self.should_succeed(dict(name=rfc.name))
+ self.should_succeed(dict(name=draft.name))
for r in range(0,2):
- self.should_succeed(dict(name=rfc.name,rev=f'{r:02d}'))
+ self.should_succeed(dict(name=draft.name,rev=f'{r:02d}'))
for ext in ('pdf','txt','html','anythingatall'):
- self.should_succeed(dict(name=rfc.name,rev=f'{r:02d}',ext=ext))
- self.should_404(dict(name=rfc.name,rev='02'))
+ self.should_succeed(dict(name=draft.name,rev=f'{r:02d}',ext=ext))
+ self.should_404(dict(name=draft.name,rev='02'))
class NotifyValidationTests(TestCase):
def test_notify_validation(self):
@@ -2924,3 +2925,30 @@ def test_gets_request_conflict_review_action_button(self):
r = self.client.get(url)
self.assertContains(r, target_string)
+class DocInfoMethodsTests(TestCase):
+
+ def test_became_rfc(self):
+ draft = WgDraftFactory()
+ rfc = WgRfcFactory()
+ draft.relateddocument_set.create(relationship_id="became_rfc",target=rfc)
+ self.assertEqual(draft.became_rfc(), rfc)
+ self.assertEqual(rfc.came_from_draft(), draft)
+
+ charter = CharterFactory()
+ self.assertIsNone(charter.became_rfc())
+ self.assertIsNone(charter.came_from_draft())
+
+ def test_revisions(self):
+ draft = WgDraftFactory(rev="09",create_revisions=range(0,10))
+ self.assertEqual(draft.revisions_by_dochistory(),[f"{i:02d}" for i in range(0,10)])
+ self.assertEqual(draft.revisions_by_newrevisionevent(),[f"{i:02d}" for i in range(0,10)])
+ rfc = WgRfcFactory()
+ self.assertEqual(rfc.revisions_by_newrevisionevent(),[])
+ self.assertEqual(rfc.revisions_by_dochistory(),[])
+
+ draft.history_set.filter(rev__lt="08").delete()
+ draft.docevent_set.filter(newrevisiondocevent__rev="05").delete()
+ self.assertEqual(draft.revisions_by_dochistory(),[f"{i:02d}" for i in range(8,10)])
+ self.assertEqual(draft.revisions_by_newrevisionevent(),[f"{i:02d}" for i in [*range(0,5), *range(6,10)]])
+
+
diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py
index 8a4717c748..9c9287dab2 100644
--- a/ietf/doc/tests_ballot.py
+++ b/ietf/doc/tests_ballot.py
@@ -803,8 +803,8 @@ def test_ballot_downref_approve(self):
desc='Last call announcement was changed',
text='this is simple last call text.' )
rfc = IndividualRfcFactory.create(
+ name = "rfc6666",
stream_id='ise',
- other_aliases=['rfc6666',],
states=[('draft','rfc'),('draft-iesg','pub')],
std_level_id='inf', )
@@ -821,7 +821,7 @@ def test_ballot_downref_approve(self):
self.assertContains(r, "No downward references for")
# Add a downref, the page should ask if it should be added to the registry
- rel = draft.relateddocument_set.create(target=rfc.docalias.get(name='rfc6666'),relationship_id='refnorm')
+ rel = draft.relateddocument_set.create(target=rfc, relationship_id='refnorm')
d = [rdoc for rdoc in draft.relateddocument_set.all() if rel.is_approved_downref()]
original_len = len(d)
r = self.client.get(url)
@@ -1121,13 +1121,13 @@ def test_regenerate_last_call(self):
self.assertFalse("contains these normative down" in lc_text)
rfc = IndividualRfcFactory.create(
+ rfc_number=6666,
stream_id='ise',
- other_aliases=['rfc6666',],
states=[('draft','rfc'),('draft-iesg','pub')],
std_level_id='inf',
)
- draft.relateddocument_set.create(target=rfc.docalias.get(name='rfc6666'),relationship_id='refnorm')
+ draft.relateddocument_set.create(target=rfc,relationship_id='refnorm')
r = self.client.post(url, dict(regenerate_last_call_text="1"))
self.assertEqual(r.status_code, 200)
@@ -1137,7 +1137,7 @@ def test_regenerate_last_call(self):
self.assertTrue("rfc6666" in lc_text)
self.assertTrue("Independent Submission" in lc_text)
- draft.relateddocument_set.create(target=rfc.docalias.get(name='rfc6666'), relationship_id='downref-approval')
+ draft.relateddocument_set.create(target=rfc, relationship_id='downref-approval')
r = self.client.post(url, dict(regenerate_last_call_text="1"))
self.assertEqual(r.status_code, 200)
diff --git a/ietf/doc/tests_bofreq.py b/ietf/doc/tests_bofreq.py
index 2fdc8c282a..319ab778d6 100644
--- a/ietf/doc/tests_bofreq.py
+++ b/ietf/doc/tests_bofreq.py
@@ -18,7 +18,7 @@
from ietf.group.factories import RoleFactory
from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory
-from ietf.doc.models import State, Document, DocAlias, NewRevisionDocEvent
+from ietf.doc.models import State, Document, NewRevisionDocEvent
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
from ietf.ietfauth.utils import has_role
from ietf.person.factories import PersonFactory
@@ -32,7 +32,7 @@ class BofreqTests(TestCase):
settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['BOFREQ_PATH']
def write_bofreq_file(self, bofreq):
- fname = Path(settings.BOFREQ_PATH) / ("%s-%s.md" % (bofreq.canonical_name(), bofreq.rev))
+ fname = Path(settings.BOFREQ_PATH) / ("%s-%s.md" % (bofreq.name, bofreq.rev))
with fname.open("w") as f:
f.write(f"""# This is a test bofreq.
Version: {bofreq.rev}
@@ -366,7 +366,6 @@ def test_start_new_bofreq(self):
name = f"bofreq-{xslugify(nobody.last_name())[:64]}-{postdict['title']}".replace(' ','-')
bofreq = Document.objects.filter(name=name,type_id='bofreq').first()
self.assertIsNotNone(bofreq)
- self.assertIsNotNone(DocAlias.objects.filter(name=name).first())
self.assertEqual(bofreq.title, postdict['title'])
self.assertEqual(bofreq.rev, '00')
self.assertEqual(bofreq.get_state_slug(), 'proposed')
diff --git a/ietf/doc/tests_charter.py b/ietf/doc/tests_charter.py
index 0350fc0221..1bd6c1701d 100644
--- a/ietf/doc/tests_charter.py
+++ b/ietf/doc/tests_charter.py
@@ -88,10 +88,7 @@ class EditCharterTests(TestCase):
settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['CHARTER_PATH']
def write_charter_file(self, charter):
- with (Path(settings.CHARTER_PATH) /
- ("%s-%s.txt" % (charter.canonical_name(), charter.rev))
- ).open("w") as f:
- f.write("This is a charter.")
+ (Path(settings.CHARTER_PATH) / f"{charter.name}-{charter.rev}.txt").write_text("This is a charter.")
def test_startstop_process(self):
CharterFactory(group__acronym='mars')
@@ -509,8 +506,13 @@ def test_submit_charter(self):
self.assertEqual(charter.rev, next_revision(prev_rev))
self.assertTrue("new_revision" in charter.latest_event().type)
- with (Path(settings.CHARTER_PATH) / (charter.canonical_name() + "-" + charter.rev + ".txt")).open(encoding='utf-8') as f:
- self.assertEqual(f.read(), "Windows line\nMac line\nUnix line\n" + utf_8_snippet.decode('utf-8'))
+ file_contents = (
+ Path(settings.CHARTER_PATH) / (charter.name + "-" + charter.rev + ".txt")
+ ).read_text("utf-8")
+ self.assertEqual(
+ file_contents,
+ "Windows line\nMac line\nUnix line\n" + utf_8_snippet.decode("utf-8"),
+ )
def test_submit_initial_charter(self):
group = GroupFactory(type_id='wg',acronym='mars',list_email='mars-wg@ietf.org')
@@ -538,6 +540,24 @@ def test_submit_initial_charter(self):
group = Group.objects.get(pk=group.pk)
self.assertEqual(group.charter, charter)
+ def test_submit_charter_with_invalid_name(self):
+ self.client.login(username="secretary", password="secretary+password")
+ ietf_group = GroupFactory(type_id="wg")
+ for bad_name in ("charter-irtf-{}", "charter-randomjunk-{}", "charter-ietf-thisisnotagroup"):
+ url = urlreverse("ietf.doc.views_charter.submit", kwargs={"name": bad_name.format(ietf_group.acronym)})
+ r = self.client.get(url)
+ self.assertEqual(r.status_code, 404, f"GET of charter named {bad_name} should 404")
+ r = self.client.post(url, {})
+ self.assertEqual(r.status_code, 404, f"POST of charter named {bad_name} should 404")
+
+ irtf_group = GroupFactory(type_id="rg")
+ for bad_name in ("charter-ietf-{}", "charter-whatisthis-{}", "charter-irtf-thisisnotagroup"):
+ url = urlreverse("ietf.doc.views_charter.submit", kwargs={"name": bad_name.format(irtf_group.acronym)})
+ r = self.client.get(url)
+ self.assertEqual(r.status_code, 404, f"GET of charter named {bad_name} should 404")
+ r = self.client.post(url, {})
+ self.assertEqual(r.status_code, 404, f"POST of charter named {bad_name} should 404")
+
def test_edit_review_announcement_text(self):
area = GroupFactory(type_id='area')
RoleFactory(name_id='ad',group=area,person=Person.objects.get(user__username='ad'))
diff --git a/ietf/doc/tests_conflict_review.py b/ietf/doc/tests_conflict_review.py
index 1b4b8eefa0..485f5655eb 100644
--- a/ietf/doc/tests_conflict_review.py
+++ b/ietf/doc/tests_conflict_review.py
@@ -70,12 +70,12 @@ def test_start_review_as_secretary(self):
self.assertEqual(review_doc.ad.name,'Areað Irector')
self.assertEqual(review_doc.notify,'ipu@ietf.org')
doc = Document.objects.get(name='draft-imaginary-independent-submission')
- self.assertTrue(doc in [x.target.document for x in review_doc.relateddocument_set.filter(relationship__slug='conflrev')])
+ self.assertTrue(doc in [x.target for x in review_doc.relateddocument_set.filter(relationship__slug='conflrev')])
self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review requested"))
self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review initiated"))
self.assertTrue('Conflict Review requested' in outbox[-1]['Subject'])
-
+
# verify you can't start a review when a review is already in progress
r = self.client.post(url,dict(ad="Areað Irector",create_in_state="Needs Shepherd",notify='ipu@ietf.org'))
self.assertEqual(r.status_code, 404)
@@ -119,7 +119,7 @@ def test_start_review_as_stream_owner(self):
self.assertEqual(review_doc.ad.name,'Ietf Chair')
self.assertEqual(review_doc.notify,'ipu@ietf.org')
doc = Document.objects.get(name='draft-imaginary-independent-submission')
- self.assertTrue(doc in [x.target.document for x in review_doc.relateddocument_set.filter(relationship__slug='conflrev')])
+ self.assertTrue(doc in [x.target for x in review_doc.relateddocument_set.filter(relationship__slug='conflrev')])
self.assertEqual(len(outbox), messages_before + 2)
@@ -403,7 +403,7 @@ def test_initial_submission(self):
# Right now, nothing to test - we let people put whatever the web browser will let them put into that textbox
# sane post using textbox
- path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev))
+ path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (doc.name, doc.rev))
self.assertEqual(doc.rev,'00')
self.assertFalse(os.path.exists(path))
r = self.client.post(url,dict(content="Some initial review text\n",submit_response="1"))
@@ -423,7 +423,7 @@ def test_subsequent_submission(self):
# A little additional setup
# doc.rev is u'00' per the test setup - double-checking that here - if it fails, the breakage is in setUp
self.assertEqual(doc.rev,'00')
- path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev))
+ path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (doc.name, doc.rev))
with io.open(path,'w') as f:
f.write('This is the old proposal.')
f.close()
@@ -450,7 +450,7 @@ def test_subsequent_submission(self):
self.assertEqual(r.status_code, 302)
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
self.assertEqual(doc.rev,'01')
- path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev))
+ path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (doc.name, doc.rev))
with io.open(path) as f:
self.assertEqual(f.read(),"This is a new proposal.")
f.close()
diff --git a/ietf/doc/tests_downref.py b/ietf/doc/tests_downref.py
index 258494e364..0222ad7942 100644
--- a/ietf/doc/tests_downref.py
+++ b/ietf/doc/tests_downref.py
@@ -19,12 +19,9 @@ def setUp(self):
super().setUp()
PersonFactory(name='Plain Man',user__username='plain')
self.draft = WgDraftFactory(name='draft-ietf-mars-test')
- self.draftalias = self.draft.docalias.get(name='draft-ietf-mars-test')
self.doc = WgDraftFactory(name='draft-ietf-mars-approved-document',states=[('draft-iesg','rfcqueue')])
- self.docalias = self.doc.docalias.get(name='draft-ietf-mars-approved-document')
- self.rfc = WgRfcFactory(alias2__name='rfc9998')
- self.rfcalias = self.rfc.docalias.get(name='rfc9998')
- RelatedDocument.objects.create(source=self.doc, target=self.rfcalias, relationship_id='downref-approval')
+ self.rfc = WgRfcFactory(rfc_number=9998)
+ RelatedDocument.objects.create(source=self.doc, target=self.rfc, relationship_id='downref-approval')
def test_downref_registry(self):
url = urlreverse('ietf.doc.views_downref.downref_registry')
@@ -64,44 +61,44 @@ def test_downref_registry_add(self):
self.assertContains(r, 'Save downref')
# error - already in the downref registry
- r = self.client.post(url, dict(rfc=self.rfcalias.pk, drafts=(self.docalias.pk, )))
+ r = self.client.post(url, dict(rfc=self.rfc.pk, drafts=(self.doc.pk, )))
self.assertContains(r, 'Downref is already in the registry')
# error - source is not in an approved state
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
- r = self.client.post(url, dict(rfc=self.rfcalias.pk, drafts=(self.draftalias.pk, )))
+ r = self.client.post(url, dict(rfc=self.rfc.pk, drafts=(self.draft.pk, )))
self.assertContains(r, 'Draft is not yet approved')
# error - the target is not a normative reference of the source
self.draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="pub"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
- r = self.client.post(url, dict(rfc=self.rfcalias.pk, drafts=(self.draftalias.pk, )))
+ r = self.client.post(url, dict(rfc=self.rfc.pk, drafts=(self.draft.pk, )))
self.assertContains(r, 'There does not seem to be a normative reference to RFC')
self.assertContains(r, 'Save downref anyway')
# normal - approve the document so the downref is now okay
- RelatedDocument.objects.create(source=self.draft, target=self.rfcalias, relationship_id='refnorm')
+ RelatedDocument.objects.create(source=self.draft, target=self.rfc, relationship_id='refnorm')
draft_de_count_before = self.draft.docevent_set.count()
rfc_de_count_before = self.rfc.docevent_set.count()
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
- r = self.client.post(url, dict(rfc=self.rfcalias.pk, drafts=(self.draftalias.pk, )))
+ r = self.client.post(url, dict(rfc=self.rfc.pk, drafts=(self.draft.pk, )))
self.assertEqual(r.status_code, 302)
newurl = urlreverse('ietf.doc.views_downref.downref_registry')
r = self.client.get(newurl)
self.assertContains(r, '(document|docalias))/(?P Add {{ref.target.document.canonical_name}}
- ({{ref.target.document.std_level}} - {{ref.target.document.stream.desc}})
+ Add {{ref.target.name}}
+ ({{ref.target.std_level}} - {{ref.target.stream.desc}})
to downref registry.
".join(names)
))
+
+ # gather RFCs
+ rfcs = Document.objects.filter(type_id="rfc").order_by('-rfc_number')
+ names = [
+ f'{rfc.name.upper()}'
+ for rfc in rfcs
+ ]
+
+ state = State.objects.get(type_id="rfc", slug="published")
+
+ categories.append((state,
+ "RFCs",
+ len(names),
+ "
".join(names)
+ ))
+
+ # Return to the previous section ordering
+ categories = categories[0:1]+categories[5:]+categories[1:5]
+
return render(request, 'doc/index_all_drafts.html', { "categories": categories })
def index_active_drafts(request):
@@ -748,27 +805,42 @@ def index_active_drafts(request):
slowcache.set(cache_key, groups, 15*60)
return render(request, "doc/index_active_drafts.html", { 'groups': groups })
-def ajax_select2_search_docs(request, model_name, doc_type):
- if model_name == "docalias":
- model = DocAlias
- else:
- model = Document
+def ajax_select2_search_docs(request, model_name, doc_type): # TODO - remove model_name argument...
+ """Get results for a select2 search field
+
+ doc_type can be "draft", "rfc", or "all", to search for only docs of type "draft", only docs of
+ type "rfc", or docs of type "draft" or "rfc" or any of the subseries ("bcp", "std", ...).
+
+ If a need arises for searching _only_ for draft or rfc, without including the subseries, then an
+ additional option or options will be needed.
+ """
+ model = Document # Earlier versions allowed searching over DocAlias which no longer exists
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
if not q:
objs = model.objects.none()
else:
- qs = model.objects.all()
-
- if model == Document:
- qs = qs.filter(type=doc_type)
- elif model == DocAlias:
- qs = qs.filter(docs__type=doc_type)
-
+ if doc_type == "draft":
+ types = ["draft"]
+ elif doc_type == "rfc":
+ types = ["rfc"]
+ elif doc_type == "all":
+ types = ("draft", "rfc", "bcp", "fyi", "std")
+ else:
+ return HttpResponseBadRequest("Invalid document type")
+ qs = model.objects.filter(type__in=[t.strip() for t in types])
for t in q:
qs = qs.filter(name__icontains=t)
objs = qs.distinct().order_by("name")[:20]
return HttpResponse(select2_id_doc_name_json(model, objs), content_type='application/json')
+
+def index_subseries(request, type_id):
+ docs = sorted(Document.objects.filter(type_id=type_id),key=lambda o: int(o.name[3:]))
+ if len(docs)>0:
+ type = docs[0].type
+ else:
+ type = DocTypeName.objects.get(slug=type_id)
+ return render(request, "doc/index_subseries.html", {"type": type, "docs": docs})
diff --git a/ietf/doc/views_statement.py b/ietf/doc/views_statement.py
index 7f10af3562..04adb5d1db 100644
--- a/ietf/doc/views_statement.py
+++ b/ietf/doc/views_statement.py
@@ -13,7 +13,7 @@
from ietf.utils import markdown
from django.utils.html import escape
-from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, State
+from ietf.doc.models import Document, DocEvent, NewRevisionDocEvent, State
from ietf.group.models import Group
from ietf.ietfauth.utils import role_required
from ietf.utils.text import xslugify
@@ -242,8 +242,6 @@ def new_statement(request):
time=statement.time,
)
statement.save_with_history([e1, e2])
- alias = DocAlias.objects.create(name=name)
- alias.docs.set([statement])
markdown_content = ""
if statement_submission == "upload":
if not writing_pdf:
diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py
index 6db4338f42..9034971ebf 100644
--- a/ietf/doc/views_status_change.py
+++ b/ietf/doc/views_status_change.py
@@ -21,7 +21,7 @@
import debug # pyflakes:ignore
from ietf.doc.mails import email_ad_approved_status_change
-from ietf.doc.models import ( Document, DocAlias, State, DocEvent, BallotDocEvent,
+from ietf.doc.models import ( Document, State, DocEvent, BallotDocEvent,
BallotPositionDocEvent, NewRevisionDocEvent, WriteupDocEvent, STATUSCHANGE_RELATIONS )
from ietf.doc.forms import AdForm
from ietf.doc.lastcall import request_last_call
@@ -104,8 +104,8 @@ def change_state(request, name, option=None):
relationship__slug__in=STATUSCHANGE_RELATIONS
)
related_doc_info = [
- dict(title=rel_doc.target.document.title,
- canonical_name=rel_doc.target.document.canonical_name(),
+ dict(title=rel_doc.target.title,
+ name=rel_doc.target.name,
newstatus=newstatus(rel_doc))
for rel_doc in related_docs
]
@@ -154,7 +154,7 @@ def clean_txt(self):
return get_cleaned_text_file_content(self.cleaned_data["txt"])
def save(self, doc):
- filename = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev))
+ filename = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.name, doc.rev))
with io.open(filename, 'w', encoding='utf-8') as destination:
if self.cleaned_data['txt']:
destination.write(self.cleaned_data['txt'])
@@ -168,7 +168,7 @@ def submit(request, name):
login = request.user.person
- path = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev))
+ path = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.name, doc.rev))
not_uploaded_yet = doc.rev == "00" and not os.path.exists(path)
if not_uploaded_yet:
@@ -185,7 +185,7 @@ def submit(request, name):
events = []
e = NewRevisionDocEvent(doc=doc, by=login, type="new_revision")
- e.desc = "New version available: %s-%s.txt" % (doc.canonical_name(), doc.rev)
+ e.desc = "New version available: %s-%s.txt" % (doc.name, doc.rev)
e.rev = doc.rev
e.save()
events.append(e)
@@ -217,7 +217,7 @@ def submit(request, name):
dict(),
)
else:
- filename = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev))
+ filename = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.name, doc.rev))
try:
with io.open(filename, 'r') as f:
init["content"] = f.read()
@@ -259,7 +259,7 @@ def edit_title(request, name):
init = { "title" : status_change.title }
form = ChangeTitleForm(initial=init)
- titletext = '%s-%s.txt' % (status_change.canonical_name(),status_change.rev)
+ titletext = '%s-%s.txt' % (status_change.name,status_change.rev)
return render(request, 'doc/change_title.html',
{'form': form,
'doc': status_change,
@@ -290,7 +290,7 @@ def edit_ad(request, name):
init = { "ad" : status_change.ad_id }
form = AdForm(initial=init)
- titletext = '%s-%s.txt' % (status_change.canonical_name(),status_change.rev)
+ titletext = '%s-%s.txt' % (status_change.name,status_change.rev)
return render(request, 'doc/change_ad.html',
{'form': form,
'doc': status_change,
@@ -315,7 +315,7 @@ def default_approval_text(status_change,relateddoc):
current_text = status_change.text_or_error() # pyflakes:ignore
- if relateddoc.target.document.std_level_id in ('std','ps','ds','bcp',):
+ if relateddoc.target.std_level_id in ('std','ps','ds','bcp',):
action = "Protocol Action"
else:
action = "Document Action"
@@ -326,7 +326,7 @@ def default_approval_text(status_change,relateddoc):
dict(status_change=status_change,
status_change_url = settings.IDTRACKER_BASE_URL+status_change.get_absolute_url(),
relateddoc= relateddoc,
- relateddoc_url = settings.IDTRACKER_BASE_URL+relateddoc.target.document.get_absolute_url(),
+ relateddoc_url = settings.IDTRACKER_BASE_URL+relateddoc.target.get_absolute_url(),
approved_text = current_text,
action=action,
newstatus=newstatus(relateddoc),
@@ -394,7 +394,7 @@ def approve(request, name):
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):
# Add a document event to each target
- c = DocEvent(type="added_comment", doc=rel.target.document, rev=rel.target.document.rev, by=login)
+ c = DocEvent(type="added_comment", doc=rel.target, rev=rel.target.rev, by=login)
c.desc = "New status of %s approved by the IESG\n%s%s" % (newstatus(rel), settings.IDTRACKER_BASE_URL,reverse('ietf.doc.views_doc.document_main', kwargs={'name': status_change.name}))
c.save()
@@ -405,7 +405,7 @@ def approve(request, name):
init = []
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):
init.append({"announcement_text" : escape(default_approval_text(status_change,rel)),
- "label": "Announcement text for %s to %s"%(rel.target.document.canonical_name(),newstatus(rel)),
+ "label": "Announcement text for %s to %s"%(rel.target.name,newstatus(rel)),
})
formset = AnnouncementFormSet(initial=init)
for form in formset.forms:
@@ -445,7 +445,7 @@ def clean_helper(form, formtype):
if not re.match(r'(?i)rfc\d{1,4}',key):
errors.append(key+" is not a valid RFC - please use the form RFCn\n")
- elif not DocAlias.objects.filter(name=key):
+ elif not Document.objects.filter(name=key):
errors.append(key+" does not exist\n")
if new_relations[key] not in STATUSCHANGE_RELATIONS:
@@ -543,7 +543,7 @@ def start_rfc_status_change(request, name=None):
if name:
if not re.match("(?i)rfc[0-9]{1,4}",name):
raise Http404
- seed_rfc = get_object_or_404(Document, type="draft", docalias__name=name)
+ seed_rfc = get_object_or_404(Document, type="rfc", name=name)
login = request.user.person
@@ -566,14 +566,11 @@ def start_rfc_status_change(request, name=None):
group=iesg_group,
)
status_change.set_state(form.cleaned_data['create_in_state'])
-
- DocAlias.objects.create( name= 'status-change-'+form.cleaned_data['document_name']).docs.add(status_change)
for key in form.cleaned_data['relations']:
- status_change.relateddocument_set.create(target=DocAlias.objects.get(name=key),
+ status_change.relateddocument_set.create(target=Document.objects.get(name=key),
relationship_id=form.cleaned_data['relations'][key])
-
tc_date = form.cleaned_data['telechat_date']
if tc_date:
update_telechat(request, status_change, login, tc_date)
@@ -583,9 +580,9 @@ def start_rfc_status_change(request, name=None):
init = {}
if name:
init['title'] = "%s to CHANGETHIS" % seed_rfc.title
- init['document_name'] = "%s-to-CHANGETHIS" % seed_rfc.canonical_name()
+ init['document_name'] = "%s-to-CHANGETHIS" % seed_rfc.name
relations={}
- relations[seed_rfc.canonical_name()]=None
+ relations[seed_rfc.name]=None
init['relations'] = relations
form = StartStatusChangeForm(initial=init)
@@ -611,11 +608,11 @@ def edit_relations(request, name):
old_relations={}
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):
- old_relations[rel.target.document.canonical_name()]=rel.relationship.slug
+ old_relations[rel.target.name]=rel.relationship.slug
new_relations=form.cleaned_data['relations']
status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS).delete()
for key in new_relations:
- status_change.relateddocument_set.create(target=DocAlias.objects.get(name=key),
+ status_change.relateddocument_set.create(target=Document.objects.get(name=key),
relationship_id=new_relations[key])
c = DocEvent(type="added_comment", doc=status_change, rev=status_change.rev, by=login)
c.desc = "Affected RFC list changed.\nOLD:"
@@ -632,7 +629,7 @@ def edit_relations(request, name):
else:
relations={}
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):
- relations[rel.target.document.canonical_name()]=rel.relationship.slug
+ relations[rel.target.name]=rel.relationship.slug
init = { "relations":relations,
}
form = EditStatusChangeForm(initial=init)
@@ -659,8 +656,8 @@ def generate_last_call_text(request, doc):
settings=settings,
requester=requester,
expiration_date=expiration_date.strftime("%Y-%m-%d"),
- changes=['%s from %s to %s\n (%s)'%(rel.target.name.upper(),rel.target.document.std_level.name,newstatus(rel),rel.target.document.title) for rel in doc.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS)],
- urls=[rel.target.document.get_absolute_url() for rel in doc.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS)],
+ changes=['%s from %s to %s\n (%s)'%(rel.target.name.upper(),rel.target.std_level.name,newstatus(rel),rel.target.title) for rel in doc.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS)],
+ urls=[rel.target.get_absolute_url() for rel in doc.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS)],
cc=cc
)
)
diff --git a/ietf/group/milestones.py b/ietf/group/milestones.py
index 64ebb389e2..039fdb44ce 100644
--- a/ietf/group/milestones.py
+++ b/ietf/group/milestones.py
@@ -369,7 +369,7 @@ def save_milestone_form(f):
email_milestones_changed(request, group, changes, states)
if milestone_set == "charter":
- return redirect('ietf.doc.views_doc.document_main', name=group.charter.canonical_name())
+ return redirect('ietf.doc.views_doc.document_main', name=group.charter.name)
else:
return HttpResponseRedirect(group.about_url())
else:
diff --git a/ietf/group/tests.py b/ietf/group/tests.py
index 233cde55e6..b11ed8e5fb 100644
--- a/ietf/group/tests.py
+++ b/ietf/group/tests.py
@@ -69,7 +69,7 @@ def setUp(self):
a = WgDraftFactory()
b = WgDraftFactory()
RelatedDocument.objects.create(
- source=a, target=b.docalias.first(), relationship_id="refnorm"
+ source=a, target=b, relationship_id="refnorm"
)
def test_group_stats(self):
@@ -95,7 +95,7 @@ def setUp(self):
a = WgDraftFactory()
b = WgDraftFactory()
RelatedDocument.objects.create(
- source=a, target=b.docalias.first(), relationship_id="refnorm"
+ source=a, target=b, relationship_id="refnorm"
)
def test_group_document_dependencies(self):
diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
index 39a1e69e26..4c353f1108 100644
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -27,7 +27,7 @@
from ietf.community.models import CommunityList
from ietf.community.utils import reset_name_contains_index_for_rule
from ietf.doc.factories import WgDraftFactory, IndividualDraftFactory, CharterFactory, BallotDocEventFactory
-from ietf.doc.models import Document, DocAlias, DocEvent, State
+from ietf.doc.models import Document, DocEvent, State
from ietf.doc.utils_charter import charter_name_for_group
from ietf.group.admin import GroupForm as AdminGroupForm
from ietf.group.factories import (GroupFactory, RoleFactory, GroupEventFactory,
@@ -117,8 +117,9 @@ def test_wg_summaries(self):
chair = Email.objects.filter(role__group=group, role__name="chair")[0]
- with (Path(settings.CHARTER_PATH) / ("%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev))).open("w") as f:
- f.write("This is a charter.")
+ (
+ Path(settings.CHARTER_PATH) / f"{group.charter.name}-{group.charter.rev}.txt"
+ ).write_text("This is a charter.")
url = urlreverse('ietf.group.views.wg_summary_area', kwargs=dict(group_type="wg"))
r = self.client.get(url)
@@ -264,8 +265,9 @@ def test_group_charter(self):
group = CharterFactory().group
draft = WgDraftFactory(group=group)
- with (Path(settings.CHARTER_PATH) / ("%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev))).open("w") as f:
- f.write("This is a charter.")
+ (
+ Path(settings.CHARTER_PATH) / f"{group.charter.name}-{group.charter.rev}.txt"
+ ).write_text("This is a charter.")
milestone = GroupMilestone.objects.create(
group=group,
@@ -385,7 +387,6 @@ def test_materials(self):
type_id="slides",
)
doc.set_state(State.objects.get(type="slides", slug="active"))
- DocAlias.objects.create(name=doc.name).docs.add(doc)
for url in group_urlreverse_list(group, 'ietf.group.views.materials'):
r = self.client.get(url)
@@ -668,8 +669,9 @@ def test_edit_info(self):
self.assertTrue(len(q('form .is-invalid')) > 0)
# edit info
- with (Path(settings.CHARTER_PATH) / ("%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev))).open("w") as f:
- f.write("This is a charter.")
+ (
+ Path(settings.CHARTER_PATH) / f"{group.charter.name}-{group.charter.rev}.txt"
+ ).write_text("This is a charter.")
area = group.parent
ad = Person.objects.get(name="Areað Irector")
state = GroupStateName.objects.get(slug="bof")
@@ -711,7 +713,9 @@ def test_edit_info(self):
self.assertEqual(group.list_archive, "archive.mars")
self.assertEqual(group.description, '')
- self.assertTrue((Path(settings.CHARTER_PATH) / ("%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev))).exists())
+ self.assertTrue(
+ (Path(settings.CHARTER_PATH) / f"{group.charter.name}-{group.charter.rev}.txt").exists()
+ )
self.assertEqual(len(outbox), 2)
self.assertTrue('Personnel change' in outbox[0]['Subject'])
for prefix in ['ad1','ad2','aread','marschairman','marsdelegate']:
diff --git a/ietf/group/utils.py b/ietf/group/utils.py
index f7e4b2f175..92b9ac1bd6 100644
--- a/ietf/group/utils.py
+++ b/ietf/group/utils.py
@@ -2,8 +2,7 @@
# -*- coding: utf-8 -*-
-import io
-import os
+from pathlib import Path
from django.db.models import Q
from django.shortcuts import get_object_or_404
@@ -55,15 +54,14 @@ def get_charter_text(group):
if (h.rev > c.rev and not (c_appr and not h_appr)) or (h_appr and not c_appr):
c = h
- filename = os.path.join(c.get_file_path(), "%s-%s.txt" % (c.canonical_name(), c.rev))
+ filename = Path(c.get_file_path()) / f"{c.name}-{c.rev}.txt"
try:
- with io.open(filename, 'rb') as f:
- text = f.read()
- try:
- text = text.decode('utf8')
- except UnicodeDecodeError:
- text = text.decode('latin1')
- return text
+ text = filename.read_bytes()
+ try:
+ text = text.decode('utf8')
+ except UnicodeDecodeError:
+ text = text.decode('latin1')
+ return text
except IOError:
return 'Error Loading Group Charter'
@@ -191,7 +189,7 @@ def setup_default_community_list_for_group(group):
community_list=clist,
rule_type="group_rfc",
group=group,
- state=State.objects.get(slug="rfc", type="draft"),
+ state=State.objects.get(slug="published", type="rfc"),
)
SearchRule.objects.create(
community_list=clist,
diff --git a/ietf/group/views.py b/ietf/group/views.py
index 129247455d..f2abe73caa 100644
--- a/ietf/group/views.py
+++ b/ietf/group/views.py
@@ -61,7 +61,7 @@
from ietf.community.models import CommunityList, EmailSubscription
from ietf.community.utils import docs_tracked_by_community_list
-from ietf.doc.models import DocTagName, State, DocAlias, RelatedDocument, Document, DocEvent
+from ietf.doc.models import DocTagName, State, RelatedDocument, Document, DocEvent
from ietf.doc.templatetags.ietf_filters import clean_whitespace
from ietf.doc.utils import get_chartering_type, get_tags_for_stream_id
from ietf.doc.utils_charter import charter_name_for_group, replace_charter_of_replaced_group
@@ -186,17 +186,12 @@ def get_roles(slug, default):
group.secretaries = get_roles("secr", [])
def fill_in_wg_drafts(group):
- aliases = DocAlias.objects.filter(docs__type="draft", docs__group=group).prefetch_related('docs').order_by("name")
- group.drafts = []
- group.rfcs = []
- for a in aliases:
- if a.name.startswith("draft"):
- group.drafts.append(a)
- else:
- group.rfcs.append(a)
- a.remote_field = RelatedDocument.objects.filter(source=a.document,relationship_id__in=['obs','updates']).distinct()
- a.invrel = RelatedDocument.objects.filter(target=a,relationship_id__in=['obs','updates']).distinct()
-
+ group.drafts = Document.objects.filter(type_id="draft", group=group).order_by("name")
+ group.rfcs = Document.objects.filter(type_id="rfc", group=group).order_by("rfc_number")
+ for rfc in group.rfcs:
+ # TODO: remote_field?
+ rfc.remote_field = RelatedDocument.objects.filter(source=rfc,relationship_id__in=['obs','updates']).distinct()
+ rfc.invrel = RelatedDocument.objects.filter(target=rfc,relationship_id__in=['obs','updates']).distinct()
def check_group_email_aliases():
pattern = re.compile(r'expand-(.*?)(-\w+)@.*? +(.*)$')
@@ -475,8 +470,8 @@ def prepare_group_documents(request, group, clist):
# non-WG drafts and call for WG adoption are considered related
if (d.group != group
or (d.stream_id and d.get_state_slug("draft-stream-%s" % d.stream_id) in ("c-adopt", "wg-cand"))):
- if d.get_state_slug() != "expired":
- d.search_heading = "Related Internet-Draft"
+ if (d.type_id == "draft" and d.get_state_slug() not in ["expired","rfc"]) or d.type_id == "rfc":
+ d.search_heading = "Related Internet-Drafts and RFCs"
docs_related.append(d)
else:
if not (d.get_state_slug('draft-iesg') == "dead" or (d.stream_id and d.get_state_slug("draft-stream-%s" % d.stream_id) == "dead")):
@@ -535,9 +530,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.type_id == "rfc":
+ name = str(d.rfc_number)
else:
name = "%s-%s" % (d.name, d.rev)
@@ -747,7 +741,7 @@ def dependencies(request, acronym, group_type=None):
relationship__slug__startswith="ref",
)
- both_rfcs = Q(source__states__slug="rfc", target__docs__states__slug="rfc")
+ both_rfcs = Q(source__type_id="rfc", target__type_id="rfc")
inactive = Q(source__states__slug__in=["expired", "repl"])
attractor = Q(target__name__in=["rfc5000", "rfc5741"])
removed = Q(source__states__slug__in=["auth-rm", "ietf-rm"])
@@ -761,23 +755,23 @@ def dependencies(request, acronym, group_type=None):
links = set()
for x in relations:
- target_state = x.target.document.get_state_slug("draft")
+ target_state = x.target.get_state_slug("draft")
if target_state != "rfc" or x.is_downref():
links.add(x)
replacements = RelatedDocument.objects.filter(
relationship__slug="replaces",
- target__docs__in=[x.target.document for x in links],
+ target__in=[x.target for x in links],
)
for x in replacements:
links.add(x)
- nodes = set([x.source for x in links]).union([x.target.document for x in links])
+ nodes = set([x.source for x in links]).union([x.target for x in links])
graph = {
"nodes": [
{
- "id": x.canonical_name(),
+ "id": x.name,
"rfc": x.get_state("draft").slug == "rfc",
"post-wg": not x.get_state("draft-iesg").slug
in ["idexists", "watching", "dead"],
@@ -795,8 +789,8 @@ def dependencies(request, acronym, group_type=None):
],
"links": [
{
- "source": x.source.canonical_name(),
- "target": x.target.document.canonical_name(),
+ "source": x.source.name,
+ "target": x.target.name,
"rel": "downref" if x.is_downref() else x.relationship.slug,
}
for x in links
@@ -1283,7 +1277,10 @@ def stream_documents(request, acronym):
editable = has_role(request.user, "Secretariat") or group.has_role(request.user, "chair")
stream = StreamName.objects.get(slug=acronym)
- qs = Document.objects.filter(states__type="draft", states__slug__in=["active", "rfc"], stream=acronym)
+ qs = Document.objects.filter(stream=acronym).filter(
+ Q(type_id="draft", states__type="draft", states__slug="active")
+ | Q(type_id="rfc")
+ )
docs, meta = prepare_document_table(request, qs, max_results=1000)
return render(request, 'group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta, 'editable':editable } )
diff --git a/ietf/idindex/index.py b/ietf/idindex/index.py
index cda8310b45..11c4521485 100644
--- a/ietf/idindex/index.py
+++ b/ietf/idindex/index.py
@@ -14,7 +14,7 @@
import debug # pyflakes:ignore
-from ietf.doc.models import Document, DocEvent, DocumentAuthor, RelatedDocument, DocAlias, State
+from ietf.doc.models import Document, DocEvent, DocumentAuthor, RelatedDocument, State
from ietf.doc.models import LastCallDocEvent, NewRevisionDocEvent
from ietf.doc.models import IESG_SUBSTATE_TAGS
from ietf.doc.templatetags.ietf_filters import clean_whitespace
@@ -31,15 +31,18 @@ def formatted_rev_date(name):
t = revision_time.get(name)
return t.strftime("%Y-%m-%d") if t else ""
- rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc",
- docs__states=State.objects.get(type="draft", slug="rfc")).values_list("docs__name", "name"))
+ rfcs = dict()
+ for rfc in Document.objects.filter(type_id="rfc"):
+ draft = rfc.came_from_draft()
+ if draft is not None:
+ rfcs[draft.name] = rfc.name
- replacements = dict(RelatedDocument.objects.filter(target__docs__states=State.objects.get(type="draft", slug="repl"),
+ replacements = dict(RelatedDocument.objects.filter(target__states=State.objects.get(type="draft", slug="repl"),
relationship="replaces").values_list("target__name", "source__name"))
# we need a distinct to prevent the queries below from multiplying the result
- all_ids = Document.objects.filter(type="draft").order_by('name').exclude(name__startswith="rfc").distinct()
+ all_ids = Document.objects.filter(type="draft").order_by('name').distinct()
res = ["\nInternet-Drafts Status Summary\n"]
@@ -77,9 +80,9 @@ def add_line(f1, f2, f3, f4):
last_field = ""
if s.slug == "rfc":
- a = rfc_aliases.get(name)
- if a:
- last_field = a[3:]
+ rfc = rfcs.get(name)
+ if rfc:
+ last_field = rfc[3:] # Rework this to take advantage of having the number at hand already.
elif s.slug == "repl":
state += " replaced by " + replacements.get(name, "0")
@@ -108,14 +111,17 @@ def file_types_for_drafts():
def all_id2_txt():
# this returns a lot of data so try to be efficient
- drafts = Document.objects.filter(type="draft").exclude(name__startswith="rfc").order_by('name')
+ drafts = Document.objects.filter(type="draft").order_by('name')
drafts = drafts.select_related('group', 'group__parent', 'ad', 'intended_std_level', 'shepherd', )
drafts = drafts.prefetch_related("states")
- rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc",
- docs__states=State.objects.get(type="draft", slug="rfc")).values_list("docs__name", "name"))
+ rfcs = dict()
+ for rfc in Document.objects.filter(type_id="rfc"):
+ draft = rfc.came_from_draft()
+ if draft is not None:
+ rfcs[draft.name] = rfc.name
- replacements = dict(RelatedDocument.objects.filter(target__docs__states=State.objects.get(type="draft", slug="repl"),
+ replacements = dict(RelatedDocument.objects.filter(target__states=State.objects.get(type="draft", slug="repl"),
relationship="replaces").values_list("target__name", "source__name"))
revision_time = dict(DocEvent.objects.filter(type="new_revision", doc__name__startswith="draft-").order_by('time').values_list("doc__name", "time"))
@@ -164,9 +170,9 @@ def all_id2_txt():
# 4
rfc_number = ""
if state == "rfc":
- a = rfc_aliases.get(d.name)
- if a:
- rfc_number = a[3:]
+ rfc = rfcs.get(d.name)
+ if rfc:
+ rfc_number = rfc[3:]
fields.append(rfc_number)
# 5
repl = ""
diff --git a/ietf/idindex/tests.py b/ietf/idindex/tests.py
index f207fa5621..c558783789 100644
--- a/ietf/idindex/tests.py
+++ b/ietf/idindex/tests.py
@@ -11,8 +11,8 @@
import debug # pyflakes:ignore
-from ietf.doc.factories import WgDraftFactory
-from ietf.doc.models import Document, DocAlias, RelatedDocument, State, LastCallDocEvent, NewRevisionDocEvent
+from ietf.doc.factories import WgDraftFactory, RfcFactory
+from ietf.doc.models import Document, RelatedDocument, State, LastCallDocEvent, NewRevisionDocEvent
from ietf.group.factories import GroupFactory
from ietf.name.models import DocRelationshipName
from ietf.idindex.index import all_id_txt, all_id2_txt, id_index_txt
@@ -41,7 +41,8 @@ def test_all_id_txt(self):
# published
draft.set_state(State.objects.get(type="draft", slug="rfc"))
- DocAlias.objects.create(name="rfc1234").docs.add(draft)
+ rfc = RfcFactory(rfc_number=1234)
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
txt = all_id_txt()
self.assertTrue(draft.name + "-" + draft.rev in txt)
@@ -52,8 +53,13 @@ def test_all_id_txt(self):
RelatedDocument.objects.create(
relationship=DocRelationshipName.objects.get(slug="replaces"),
- source=Document.objects.create(type_id="draft", rev="00", name="draft-test-replacement"),
- target=draft.docalias.get(name__startswith="draft"))
+ source=Document.objects.create(
+ type_id="draft",
+ rev="00",
+ name="draft-test-replacement"
+ ),
+ target=draft
+ )
txt = all_id_txt()
self.assertTrue(draft.name + "-" + draft.rev in txt)
@@ -103,7 +109,8 @@ def get_fields(content):
# test RFC
draft.set_state(State.objects.get(type="draft", slug="rfc"))
- DocAlias.objects.create(name="rfc1234").docs.add(draft)
+ rfc = RfcFactory(rfc_number=1234)
+ draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
t = get_fields(all_id2_txt())
self.assertEqual(t[4], "1234")
@@ -111,8 +118,12 @@ def get_fields(content):
draft.set_state(State.objects.get(type="draft", slug="repl"))
RelatedDocument.objects.create(
relationship=DocRelationshipName.objects.get(slug="replaces"),
- source=Document.objects.create(type_id="draft", rev="00", name="draft-test-replacement"),
- target=draft.docalias.get(name__startswith="draft"))
+ source=Document.objects.create(
+ type_id="draft",
+ rev="00",
+ name="draft-test-replacement"
+ ),
+ target=draft)
t = get_fields(all_id2_txt())
self.assertEqual(t[5], "draft-test-replacement")
diff --git a/ietf/iesg/agenda.py b/ietf/iesg/agenda.py
index 0abc5e6cda..587713089f 100644
--- a/ietf/iesg/agenda.py
+++ b/ietf/iesg/agenda.py
@@ -66,7 +66,7 @@ def get_doc_section(doc):
elif doc.type_id == 'statchg':
protocol_action = False
for relation in doc.relateddocument_set.filter(relationship__slug__in=('tops','tois','tohist','toinf','tobcp','toexp')):
- if relation.relationship_id in ('tops','tois') or relation.target.document.std_level_id in ('std','ds','ps'):
+ if relation.relationship_id in ('tops','tois') or relation.target.std_level_id in ('std','ds','ps'):
protocol_action = True
if protocol_action:
s = "2.3"
@@ -186,7 +186,7 @@ def fill_in_agenda_docs(date, sections, docs=None):
doc.review_assignments = review_assignments_for_docs.get(doc.name, [])
elif doc.type_id == "conflrev":
- doc.conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
+ doc.conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target
elif doc.type_id == "charter":
pass
@@ -219,4 +219,4 @@ def agenda_data(date=None):
fill_in_agenda_docs(date, sections)
fill_in_agenda_management_issues(date, sections)
- return { 'date': date.isoformat(), 'sections': sections }
\ No newline at end of file
+ return { 'date': date.isoformat(), 'sections': sections }
diff --git a/ietf/iesg/tests.py b/ietf/iesg/tests.py
index ceda918bb2..7211a6bc06 100644
--- a/ietf/iesg/tests.py
+++ b/ietf/iesg/tests.py
@@ -17,7 +17,7 @@
import debug # pyflakes:ignore
from ietf.doc.models import DocEvent, BallotPositionDocEvent, TelechatDocEvent
-from ietf.doc.models import Document, DocAlias, State, RelatedDocument
+from ietf.doc.models import Document, State, RelatedDocument
from ietf.doc.factories import WgDraftFactory, IndividualDraftFactory, ConflictReviewFactory, BaseDocumentFactory, CharterFactory, WgRfcFactory, IndividualRfcFactory
from ietf.doc.utils import create_ballot_if_not_open
from ietf.group.factories import RoleFactory, GroupFactory, DatedGroupMilestoneFactory, DatelessGroupMilestoneFactory
@@ -150,8 +150,8 @@ def setUp(self):
super().setUp()
mars = GroupFactory(acronym='mars',parent=Group.objects.get(acronym='farfut'))
wgdraft = WgDraftFactory(name='draft-ietf-mars-test', group=mars, intended_std_level_id='ps')
- rfc = IndividualRfcFactory.create(stream_id='irtf', other_aliases=['rfc6666',], states=[('draft','rfc'),('draft-iesg','pub')], std_level_id='inf', )
- wgdraft.relateddocument_set.create(target=rfc.docalias.get(name='rfc6666'), relationship_id='refnorm')
+ rfc = IndividualRfcFactory.create(stream_id='irtf', rfc_number=6666, std_level_id='inf', )
+ wgdraft.relateddocument_set.create(target=rfc, relationship_id='refnorm')
ise_draft = IndividualDraftFactory(name='draft-imaginary-independent-submission')
ise_draft.stream = StreamName.objects.get(slug="ise")
ise_draft.save_with_history([DocEvent(doc=ise_draft, rev=ise_draft.rev, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
@@ -281,7 +281,7 @@ def test_fill_in_agenda_docs(self):
relation = RelatedDocument.objects.create(
source=statchg,
- target=DocAlias.objects.filter(name__startswith='rfc', docs__std_level="ps")[0],
+ target=Document.objects.filter(type_id="rfc", std_level="ps").first(),
relationship_id="tohist")
statchg.group = Group.objects.get(acronym="mars")
@@ -299,7 +299,7 @@ def test_fill_in_agenda_docs(self):
self.assertTrue(statchg in agenda_data(date_str)["sections"]["2.3.3"]["docs"])
# 3.3 document status changes
- relation.target = DocAlias.objects.filter(name__startswith='rfc', docs__std_level="inf")[0]
+ relation.target = Document.objects.filter(type_id="rfc", std_level="inf").first()
relation.save()
statchg.group = Group.objects.get(acronym="mars")
diff --git a/ietf/iesg/utils.py b/ietf/iesg/utils.py
index 4ddc9cb404..3f4883798f 100644
--- a/ietf/iesg/utils.py
+++ b/ietf/iesg/utils.py
@@ -32,10 +32,10 @@ def telechat_page_count(date=None, docs=None):
pages_for_action += d.pages or 0
elif d.type_id == 'statchg':
for rel in d.related_that_doc(STATUSCHANGE_RELATIONS):
- pages_for_action += rel.document.pages or 0
+ pages_for_action += rel.pages or 0
elif d.type_id == 'conflrev':
for rel in d.related_that_doc('conflrev'):
- pages_for_action += rel.document.pages or 0
+ pages_for_action += rel.pages or 0
else:
pass
@@ -43,10 +43,10 @@ def telechat_page_count(date=None, docs=None):
for d in for_approval-set(drafts):
if d.type_id == 'statchg':
for rel in d.related_that_doc(STATUSCHANGE_RELATIONS):
- related_pages += rel.document.pages or 0
+ related_pages += rel.pages or 0
elif d.type_id == 'conflrev':
for rel in d.related_that_doc('conflrev'):
- related_pages += rel.document.pages or 0
+ related_pages += rel.pages or 0
else:
# There's really nothing to rely on to give a reading load estimate for charters
pass
diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py
index 0830db2619..a219a6b5de 100644
--- a/ietf/iesg/views.py
+++ b/ietf/iesg/views.py
@@ -122,7 +122,7 @@ def agenda_json(request, date=None):
for doc in docs:
wginfo = {
- 'docname': doc.canonical_name(),
+ 'docname': doc.name,
'rev': doc.rev,
'wgname': doc.group.name,
'acronym': doc.group.acronym,
@@ -137,7 +137,7 @@ def agenda_json(request, date=None):
for doc in docs:
docinfo = {
- 'docname':doc.canonical_name(),
+ 'docname':doc.name,
'title':doc.title,
'ad':doc.ad.name if doc.ad else None,
}
@@ -149,8 +149,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.type_id == "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"):
@@ -170,8 +170,8 @@ def agenda_json(request, date=None):
elif doc.type_id == 'conflrev':
docinfo['rev'] = doc.rev
- td = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
- docinfo['target-docname'] = td.canonical_name()
+ td = doc.relateddocument_set.get(relationship__slug='conflrev').target
+ docinfo['target-docname'] = td.name
docinfo['target-title'] = td.title
docinfo['target-rev'] = td.rev
docinfo['intended-std-level'] = str(td.intended_std_level)
diff --git a/ietf/ipr/admin.py b/ietf/ipr/admin.py
index a0185f58c6..afc1952d72 100644
--- a/ietf/ipr/admin.py
+++ b/ietf/ipr/admin.py
@@ -94,7 +94,7 @@ class IprDocRelAdmin(admin.ModelAdmin):
class RelatedIprAdmin(admin.ModelAdmin):
list_display = ['source', 'target', 'relationship', ]
- search_fields = ['source__name', 'target__name', 'target__docs__name', ]
+ search_fields = ['source__name', 'target__name', ]
raw_id_fields = ['source', 'target', ]
admin.site.register(RelatedIpr, RelatedIprAdmin)
diff --git a/ietf/ipr/factories.py b/ietf/ipr/factories.py
index ca48b32d02..8a8a740158 100644
--- a/ietf/ipr/factories.py
+++ b/ietf/ipr/factories.py
@@ -42,7 +42,7 @@ def docs(self, create, extracted, **kwargs):
return
if extracted:
for doc in extracted:
- IprDocRel.objects.create(disclosure=self,document=doc.docalias.first())
+ IprDocRel.objects.create(disclosure=self,document=doc)
@factory.post_generation
def updates(self, create, extracted, **kwargs):
diff --git a/ietf/ipr/forms.py b/ietf/ipr/forms.py
index fe4a70f8c4..8ea179789b 100644
--- a/ietf/ipr/forms.py
+++ b/ietf/ipr/forms.py
@@ -14,7 +14,7 @@
import debug # pyflakes:ignore
from ietf.group.models import Group
-from ietf.doc.fields import SearchableDocAliasField
+from ietf.doc.fields import SearchableDocumentField
from ietf.ipr.mail import utc_from_string
from ietf.ipr.fields import SearchableIprDisclosuresField
from ietf.ipr.models import (IprDocRel, IprDisclosureBase, HolderIprDisclosure,
@@ -95,7 +95,7 @@ def clean(self):
return self.cleaned_data
class DraftForm(forms.ModelForm):
- document = SearchableDocAliasField(label="I-D name/RFC number", required=True, doc_type="draft")
+ document = SearchableDocumentField(label="I-D name/RFC number", required=True, doc_type="all")
class Meta:
model = IprDocRel
diff --git a/ietf/ipr/migrations/0002_iprdocrel_no_aliases.py b/ietf/ipr/migrations/0002_iprdocrel_no_aliases.py
new file mode 100644
index 0000000000..bcfc73a320
--- /dev/null
+++ b/ietf/ipr/migrations/0002_iprdocrel_no_aliases.py
@@ -0,0 +1,104 @@
+# Generated by Django 4.2.2 on 2023-06-16 13:40
+
+from django.db import migrations
+import django.db.models.deletion
+from django.db.models import F, Subquery, OuterRef, ManyToManyField, CharField
+import ietf.utils.models
+
+def forward(apps, schema_editor):
+ IprDocRel = apps.get_model("ipr", "IprDocRel")
+ DocAlias = apps.get_model("doc", "DocAlias")
+ document_subquery = Subquery(
+ DocAlias.objects.filter(
+ pk=OuterRef("deprecated_document")
+ ).values("docs")[:1]
+ )
+ name_subquery = Subquery(
+ DocAlias.objects.filter(
+ pk=OuterRef("deprecated_document")
+ ).values("name")[:1]
+ )
+ IprDocRel.objects.annotate(
+ firstdoc=document_subquery,
+ aliasname=name_subquery,
+ ).update(
+ document=F("firstdoc"),
+ originaldocumentaliasname=F("aliasname"),
+ )
+ # This might not be right - we may need here (and in the relateddocument migrations) to pay attention to
+ # whether the name being pointed to is and rfc name or a draft name and point to the right object instead...
+
+def reverse(apps, schema_editor):
+ pass
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("ipr", "0001_initial"),
+ ("doc", "0016_relate_hist_no_aliases")
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='iprdocrel',
+ name='document',
+ field=ietf.utils.models.ForeignKey(
+ db_index=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.docalias',
+ ),
+ ),
+ migrations.RenameField(
+ model_name="iprdocrel",
+ old_name="document",
+ new_name="deprecated_document"
+ ),
+ migrations.AlterField(
+ model_name='iprdocrel',
+ name='deprecated_document',
+ field=ietf.utils.models.ForeignKey(
+ db_index=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.docalias',
+ ),
+ ),
+ migrations.AddField(
+ model_name="iprdocrel",
+ name="document",
+ field=ietf.utils.models.ForeignKey(
+ default=1, # A lie, but a convenient one - no iprdocrel objects point here.
+ on_delete=django.db.models.deletion.CASCADE,
+ to="doc.document",
+ db_index=False,
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="iprdocrel",
+ name="originaldocumentaliasname",
+ field=CharField(max_length=255,null=True,blank=True),
+ preserve_default=True,
+ ),
+ migrations.RunPython(forward, reverse),
+ migrations.AlterField(
+ model_name="iprdocrel",
+ name="document",
+ field=ietf.utils.models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="doc.document",
+ db_index=True,
+ ),
+ ),
+ migrations.AlterField(
+ model_name='iprdisclosurebase',
+ name='docs',
+ field=ManyToManyField(through='ipr.IprDocRel', to='doc.Document'),
+ ),
+ migrations.RemoveField(
+ model_name="iprdocrel",
+ name="deprecated_document",
+ field=ietf.utils.models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='doc.DocAlias',
+ ),
+ ),
+ ]
diff --git a/ietf/ipr/migrations/0003_alter_iprdisclosurebase_docs.py b/ietf/ipr/migrations/0003_alter_iprdisclosurebase_docs.py
new file mode 100644
index 0000000000..23b349f567
--- /dev/null
+++ b/ietf/ipr/migrations/0003_alter_iprdisclosurebase_docs.py
@@ -0,0 +1,18 @@
+# Copyright The IETF Trust 2023, All Rights Reserved
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("doc", "0017_delete_docalias"),
+ ("ipr", "0002_iprdocrel_no_aliases"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="iprdisclosurebase",
+ name="docs",
+ field=models.ManyToManyField(through="ipr.IprDocRel", to="doc.document"),
+ ),
+ ]
diff --git a/ietf/ipr/models.py b/ietf/ipr/models.py
index b3add079d0..a3c917747e 100644
--- a/ietf/ipr/models.py
+++ b/ietf/ipr/models.py
@@ -7,7 +7,7 @@
from django.urls import reverse
from django.utils import timezone
-from ietf.doc.models import DocAlias, DocEvent
+from ietf.doc.models import Document, DocEvent
from ietf.name.models import DocRelationshipName,IprDisclosureStateName,IprLicenseTypeName,IprEventTypeName
from ietf.person.models import Person
from ietf.message.models import Message
@@ -16,7 +16,7 @@
class IprDisclosureBase(models.Model):
by = ForeignKey(Person) # who was logged in, or System if nobody was logged in
compliant = models.BooleanField("Complies to RFC3979", default=True)
- docs = models.ManyToManyField(DocAlias, through='IprDocRel')
+ docs = models.ManyToManyField(Document, through='IprDocRel')
holder_legal_name = models.CharField(max_length=255)
notes = models.TextField("Additional notes", blank=True)
other_designations = models.CharField("Designations for other contributions", blank=True, max_length=255)
@@ -160,9 +160,10 @@ class GenericIprDisclosure(IprDisclosureBase):
class IprDocRel(models.Model):
disclosure = ForeignKey(IprDisclosureBase)
- document = ForeignKey(DocAlias)
+ document = ForeignKey(Document)
sections = models.TextField(blank=True)
revisions = models.CharField(max_length=16,blank=True) # allows strings like 01-07
+ originaldocumentaliasname = models.CharField(max_length=255, null=True, blank=True)
def doc_type(self):
name = self.document.name
@@ -175,7 +176,7 @@ def doc_type(self):
def formatted_name(self):
name = self.document.name
- if name.startswith("rfc"):
+ if len(name) >= 3 and name[:3] in ("rfc", "bcp", "fyi", "std"):
return name.upper()
#elif self.revisions:
# return "%s-%s" % (name, self.revisions)
@@ -234,10 +235,7 @@ def create_doc_events(self):
'removed_objfalse': 'removed_objfalse_related_ipr',
}
if self.type_id in event_type_map:
- related_docs = set() # related docs, no duplicates
- for alias in self.disclosure.docs.all():
- related_docs.update(alias.docs.all())
- for doc in related_docs:
+ for doc in self.disclosure.docs.distinct():
DocEvent.objects.create(
type=event_type_map[self.type_id],
time=self.time,
diff --git a/ietf/ipr/resources.py b/ietf/ipr/resources.py
index 665b0ab02f..0d8421cdec 100644
--- a/ietf/ipr/resources.py
+++ b/ietf/ipr/resources.py
@@ -16,11 +16,11 @@
from ietf.person.resources import PersonResource
from ietf.name.resources import IprDisclosureStateNameResource
-from ietf.doc.resources import DocAliasResource
+from ietf.doc.resources import DocumentResource
class IprDisclosureBaseResource(ModelResource):
by = ToOneField(PersonResource, 'by')
state = ToOneField(IprDisclosureStateNameResource, 'state')
- docs = ToManyField(DocAliasResource, 'docs', null=True)
+ docs = ToManyField(DocumentResource, 'docs', null=True)
rel = ToManyField('ietf.ipr.resources.IprDisclosureBaseResource', 'rel', null=True)
class Meta:
queryset = IprDisclosureBase.objects.all()
@@ -45,10 +45,9 @@ class Meta:
}
api.ipr.register(IprDisclosureBaseResource())
-from ietf.doc.resources import DocAliasResource
class IprDocRelResource(ModelResource):
disclosure = ToOneField(IprDisclosureBaseResource, 'disclosure')
- document = ToOneField(DocAliasResource, 'document')
+ document = ToOneField(DocumentResource, 'document')
class Meta:
cache = SimpleCache()
queryset = IprDocRel.objects.all()
@@ -66,13 +65,12 @@ class Meta:
from ietf.person.resources import PersonResource
from ietf.name.resources import IprDisclosureStateNameResource, IprLicenseTypeNameResource
-from ietf.doc.resources import DocAliasResource
class HolderIprDisclosureResource(ModelResource):
by = ToOneField(PersonResource, 'by')
state = ToOneField(IprDisclosureStateNameResource, 'state')
iprdisclosurebase_ptr = ToOneField(IprDisclosureBaseResource, 'iprdisclosurebase_ptr')
licensing = ToOneField(IprLicenseTypeNameResource, 'licensing')
- docs = ToManyField(DocAliasResource, 'docs', null=True)
+ docs = ToManyField(DocumentResource, 'docs', null=True)
rel = ToManyField(IprDisclosureBaseResource, 'rel', null=True)
class Meta:
cache = SimpleCache()
@@ -111,12 +109,11 @@ class Meta:
from ietf.person.resources import PersonResource
from ietf.name.resources import IprDisclosureStateNameResource
-from ietf.doc.resources import DocAliasResource
class ThirdPartyIprDisclosureResource(ModelResource):
by = ToOneField(PersonResource, 'by')
state = ToOneField(IprDisclosureStateNameResource, 'state')
iprdisclosurebase_ptr = ToOneField(IprDisclosureBaseResource, 'iprdisclosurebase_ptr')
- docs = ToManyField(DocAliasResource, 'docs', null=True)
+ docs = ToManyField(DocumentResource, 'docs', null=True)
rel = ToManyField(IprDisclosureBaseResource, 'rel', null=True)
class Meta:
cache = SimpleCache()
@@ -168,12 +165,11 @@ class Meta:
from ietf.person.resources import PersonResource
from ietf.name.resources import IprDisclosureStateNameResource
-from ietf.doc.resources import DocAliasResource
class NonDocSpecificIprDisclosureResource(ModelResource):
by = ToOneField(PersonResource, 'by')
state = ToOneField(IprDisclosureStateNameResource, 'state')
iprdisclosurebase_ptr = ToOneField(IprDisclosureBaseResource, 'iprdisclosurebase_ptr')
- docs = ToManyField(DocAliasResource, 'docs', null=True)
+ docs = ToManyField(DocumentResource, 'docs', null=True)
rel = ToManyField(IprDisclosureBaseResource, 'rel', null=True)
class Meta:
cache = SimpleCache()
@@ -207,12 +203,11 @@ class Meta:
from ietf.person.resources import PersonResource
from ietf.name.resources import IprDisclosureStateNameResource
-from ietf.doc.resources import DocAliasResource
class GenericIprDisclosureResource(ModelResource):
by = ToOneField(PersonResource, 'by')
state = ToOneField(IprDisclosureStateNameResource, 'state')
iprdisclosurebase_ptr = ToOneField(IprDisclosureBaseResource, 'iprdisclosurebase_ptr')
- docs = ToManyField(DocAliasResource, 'docs', null=True)
+ docs = ToManyField(DocumentResource, 'docs', null=True)
rel = ToManyField(IprDisclosureBaseResource, 'rel', null=True)
class Meta:
cache = SimpleCache()
diff --git a/ietf/ipr/templatetags/ipr_filters.py b/ietf/ipr/templatetags/ipr_filters.py
index 0130387500..8b3b420c41 100644
--- a/ietf/ipr/templatetags/ipr_filters.py
+++ b/ietf/ipr/templatetags/ipr_filters.py
@@ -32,7 +32,7 @@ def to_class_name(value):
return value.__class__.__name__
def draft_rev_at_time(iprdocrel):
- draft = iprdocrel.document.document
+ draft = iprdocrel.document
event = iprdocrel.disclosure.get_latest_event_posted()
if event is None:
return ("","The Internet-Draft's revision at the time this disclosure was posted could not be determined.")
@@ -47,7 +47,7 @@ def draft_rev_at_time(iprdocrel):
@register.filter
def no_revisions_message(iprdocrel):
- draft = iprdocrel.document.document
+ draft = iprdocrel.document
if draft.type_id != "draft" or iprdocrel.revisions.strip() != "":
return ""
rev_at_time, exception = draft_rev_at_time(iprdocrel)
diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py
index 66337bff20..73b5d0dc5a 100644
--- a/ietf/ipr/tests.py
+++ b/ietf/ipr/tests.py
@@ -15,11 +15,11 @@
import debug # pyflakes:ignore
-from ietf.doc.models import DocAlias
from ietf.doc.factories import (
DocumentFactory,
WgDraftFactory,
WgRfcFactory,
+ RfcFactory,
NewRevisionDocEventFactory
)
from ietf.group.factories import RoleFactory
@@ -180,7 +180,8 @@ def test_search(self):
self.assertContains(r, draft.name)
self.assertNotContains(r, ipr.title)
- DocAlias.objects.create(name="rfc321").docs.add(draft)
+ rfc = RfcFactory(rfc_number=321)
+ draft.relateddocument_set.create(relationship_id="became_rfc",target=rfc)
# find RFC
r = self.client.get(url + "?submit=rfc&rfc=321")
@@ -285,7 +286,7 @@ def test_new_specific(self):
"""Add a new specific disclosure. Note: submitter does not need to be logged in.
"""
draft = WgDraftFactory()
- WgRfcFactory()
+ rfc = WgRfcFactory()
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
# successful post
@@ -299,9 +300,9 @@ def test_new_specific(self):
"ietfer_contact_info": "555-555-0101",
"iprdocrel_set-TOTAL_FORMS": 2,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.pk,
"iprdocrel_set-0-revisions": '00',
- "iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
+ "iprdocrel_set-1-document": rfc.pk,
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transferring bits",
@@ -341,7 +342,7 @@ def test_new_specific(self):
def test_new_specific_no_revision(self):
draft = WgDraftFactory()
- WgRfcFactory()
+ rfc = WgRfcFactory()
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "specific" })
# successful post
@@ -355,8 +356,8 @@ def test_new_specific_no_revision(self):
"ietfer_contact_info": "555-555-0101",
"iprdocrel_set-TOTAL_FORMS": 2,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": draft.docalias.first().pk,
- "iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
+ "iprdocrel_set-0-document": draft.pk,
+ "iprdocrel_set-1-document": rfc.pk,
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transferring bits",
@@ -375,7 +376,7 @@ def test_new_thirdparty(self):
"""Add a new third-party disclosure. Note: submitter does not need to be logged in.
"""
draft = WgDraftFactory()
- WgRfcFactory()
+ rfc = WgRfcFactory()
url = urlreverse("ietf.ipr.views.new", kwargs={ "type": "third-party" })
# successful post
@@ -387,9 +388,9 @@ def test_new_thirdparty(self):
"ietfer_contact_info": "555-555-0101",
"iprdocrel_set-TOTAL_FORMS": 2,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.pk,
"iprdocrel_set-0-revisions": '00',
- "iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
+ "iprdocrel_set-1-document": rfc.pk,
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transferring bits",
@@ -434,7 +435,7 @@ def test_edit(self):
"holder_legal_name": "Test Legal",
"ietfer_contact_info": "555-555-0101",
"ietfer_name": "Test Participant",
- "iprdocrel_set-0-document": draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-INITIAL_FORMS": 0,
"iprdocrel_set-TOTAL_FORMS": 1,
@@ -462,7 +463,7 @@ def test_edit(self):
def test_update(self):
draft = WgDraftFactory()
- WgRfcFactory()
+ rfc = WgRfcFactory()
original_ipr = HolderIprDisclosureFactory(docs=[draft,])
# get
@@ -483,9 +484,9 @@ def test_update(self):
"ietfer_contact_info": "555-555-0101",
"iprdocrel_set-TOTAL_FORMS": 2,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.pk,
"iprdocrel_set-0-revisions": '00',
- "iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
+ "iprdocrel_set-1-document": rfc.pk,
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transferring bits",
@@ -520,7 +521,7 @@ def test_update_bad_post(self):
"holder_contact_email": "test@holder.com",
"iprdocrel_set-TOTAL_FORMS": 1,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.pk,
"iprdocrel_set-0-revisions": '00',
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
@@ -786,7 +787,7 @@ def test_edit_using_factory(self):
'iprdocrel_set-INITIAL_FORMS' : 0,
'iprdocrel_set-0-id': '',
"iprdocrel_set-0-document": disclosure.docs.first().pk,
- "iprdocrel_set-0-revisions": disclosure.docs.first().document.rev,
+ "iprdocrel_set-0-revisions": disclosure.docs.first().rev,
'holder_legal_name': disclosure.holder_legal_name,
'patent_number': patent_dict['Number'],
'patent_title': patent_dict['Title'],
@@ -848,7 +849,7 @@ def test_no_revisions_message(self):
NewRevisionDocEventFactory(doc=draft, rev=f"{rev:02d}", time=now-datetime.timedelta(days=30*(2-rev)))
# Disclosure has non-empty revisions field on its related draft
- iprdocrel = IprDocRelFactory(document=draft.docalias.first())
+ iprdocrel = IprDocRelFactory(document=draft)
IprEventFactory(type_id="posted",time=now,disclosure=iprdocrel.disclosure)
self.assertEqual(
no_revisions_message(iprdocrel),
@@ -856,7 +857,7 @@ def test_no_revisions_message(self):
)
# Disclosure has more than one revision, none called out, disclosure after submissions
- iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
+ iprdocrel = IprDocRelFactory(document=draft, revisions="")
IprEventFactory(type_id="posted",time=now,disclosure=iprdocrel.disclosure)
self.assertEqual(
no_revisions_message(iprdocrel),
@@ -864,7 +865,7 @@ def test_no_revisions_message(self):
)
# Disclosure has more than one revision, none called out, disclosure after 01
- iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
+ iprdocrel = IprDocRelFactory(document=draft, revisions="")
e = IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
e.time = now-datetime.timedelta(days=15)
e.save()
@@ -874,7 +875,7 @@ def test_no_revisions_message(self):
)
# Disclosure has more than one revision, none called out, disclosure was before the 00
- iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
+ iprdocrel = IprDocRelFactory(document=draft, revisions="")
e = IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
e.time = now-datetime.timedelta(days=180)
e.save()
@@ -886,7 +887,7 @@ def test_no_revisions_message(self):
# disclosed draft has no NewRevisionDocEvents
draft = WgDraftFactory(rev="20")
draft.docevent_set.all().delete()
- iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
+ iprdocrel = IprDocRelFactory(document=draft, revisions="")
IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
self.assertEqual(
no_revisions_message(iprdocrel),
@@ -895,7 +896,7 @@ def test_no_revisions_message(self):
# disclosed draft has only one revision
draft = WgDraftFactory(rev="00")
- iprdocrel = IprDocRelFactory(document=draft.docalias.first(), revisions="")
+ iprdocrel = IprDocRelFactory(document=draft, revisions="")
IprEventFactory(type_id="posted",disclosure=iprdocrel.disclosure)
self.assertEqual(
no_revisions_message(iprdocrel),
diff --git a/ietf/ipr/utils.py b/ietf/ipr/utils.py
index f288803de2..c4f17c4822 100644
--- a/ietf/ipr/utils.py
+++ b/ietf/ipr/utils.py
@@ -32,33 +32,31 @@ def get_ipr_summary(disclosure):
return summary if len(summary) <= 128 else summary[:125]+'...'
-def iprs_from_docs(aliases,**kwargs):
- """Returns a list of IPRs related to doc aliases"""
+def iprs_from_docs(docs,**kwargs):
+ """Returns a list of IPRs related to docs"""
iprdocrels = []
- for alias in aliases:
- for document in alias.docs.all():
- if document.ipr(**kwargs):
- iprdocrels += document.ipr(**kwargs)
+ for document in docs:
+ if document.ipr(**kwargs):
+ iprdocrels += document.ipr(**kwargs)
return list(set([i.disclosure for i in iprdocrels]))
-def related_docs(alias, relationship=('replaces', 'obs')):
+def related_docs(doc, relationship=('replaces', 'obs'), reverse_relationship=("became_rfc",)):
"""Returns list of related documents"""
- results = []
- for doc in alias.docs.all():
- results += list(doc.docalias.all())
-
- rels = []
- for doc in alias.docs.all():
- rels += list(doc.all_relations_that_doc(relationship))
+ results = [doc]
+
+ rels = doc.all_relations_that_doc(relationship)
for rel in rels:
- rel_aliases = list(rel.target.document.docalias.all())
-
- for x in rel_aliases:
- x.related = rel
- x.relation = rel.relationship.revname
- results += rel_aliases
+ rel.target.related = rel
+ rel.target.relation = rel.relationship.revname
+ results += [x.target for x in rels]
+
+ rev_rels = doc.all_relations_that(reverse_relationship)
+ for rel in rev_rels:
+ rel.source.related = rel
+ rel.source.relation = rel.relationship.name
+ results += [x.source for x in rev_rels]
return list(set(results))
@@ -67,17 +65,16 @@ def generate_draft_recursive_txt():
docipr = {}
for o in IprDocRel.objects.filter(disclosure__state='posted').select_related('document'):
- alias = o.document
- name = alias.name
- for document in alias.docs.all():
- related = set(document.docalias.all()) | set(document.all_related_that_doc(('obs', 'replaces')))
- for alias in related:
- name = alias.name
- if name.startswith("rfc"):
- name = name.upper()
- if not name in docipr:
- docipr[name] = []
- docipr[name].append(o.disclosure_id)
+ doc = o.document
+ name = doc.name
+ related_set = set(doc) | set(doc.all_related_that_doc(('obs', 'replaces')))
+ for related in related_set:
+ name = related.name
+ if name.startswith("rfc"):
+ name = name.upper()
+ if not name in docipr:
+ docipr[name] = []
+ docipr[name].append(o.disclosure_id)
lines = [ "# Machine-readable list of IPR disclosures by Internet-Draft name" ]
for name, iprs in docipr.items():
diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py
index e2ddb3bcc3..a061232b8f 100644
--- a/ietf/ipr/views.py
+++ b/ietf/ipr/views.py
@@ -18,7 +18,7 @@
import debug # pyflakes:ignore
-from ietf.doc.models import DocAlias
+from ietf.doc.models import Document
from ietf.group.models import Role, Group
from ietf.ietfauth.utils import role_required, has_role
from ietf.ipr.mail import (message_from_message, get_reply_to, get_update_submitter_emails)
@@ -38,7 +38,7 @@
from ietf.message.utils import infer_message
from ietf.name.models import IprLicenseTypeName
from ietf.person.models import Person
-from ietf.secr.utils.document import get_rfc_num, is_draft
+from ietf.utils import log
from ietf.utils.draft_search import normalize_draftname
from ietf.utils.mail import send_mail, send_mail_message
from ietf.utils.response import permission_denied
@@ -69,12 +69,15 @@ def get_document_emails(ipr):
has been posted"""
messages = []
for rel in ipr.iprdocrel_set.all():
- doc = rel.document.document
+ doc = rel.document
- if is_draft(doc):
+ if doc.type_id=="draft":
doc_info = 'Internet-Draft entitled "{}" ({})'.format(doc.title,doc.name)
+ elif doc.type_id=="rfc":
+ doc_info = 'RFC entitled "{}" (RFC{})'.format(doc.title, doc.rfc_number)
else:
- doc_info = 'RFC entitled "{}" (RFC{})'.format(doc.title,get_rfc_num(doc))
+ log.unreachable("2023-08-15")
+ return ""
addrs = gather_address_lists('ipr_posted_on_doc',doc=doc).as_strings(compact=False)
@@ -674,17 +677,18 @@ def search(request):
doc = q
if docid:
- start = DocAlias.objects.filter(name__iexact=docid)
- elif search_type == "draft":
- q = normalize_draftname(q)
- start = DocAlias.objects.filter(name__icontains=q, name__startswith="draft")
- else: # search_type == "rfc"
- start = DocAlias.objects.filter(name="rfc%s" % q.lstrip("0"))
-
+ start = Document.objects.filter(name__iexact=docid)
+ else:
+ if search_type == "draft":
+ q = normalize_draftname(q)
+ start = Document.objects.filter(name__icontains=q, name__startswith="draft")
+ elif search_type == "rfc":
+ start = Document.objects.filter(name="rfc%s" % q.lstrip("0"))
+
# one match
if len(start) == 1:
first = start[0]
- doc = first.document
+ doc = first
docs = related_docs(first)
iprs = iprs_from_docs(docs,states=states)
template = "ipr/search_doc_result.html"
@@ -716,27 +720,27 @@ def search(request):
# Search by wg acronym
# Document list with IPRs
elif search_type == "group":
- docs = list(DocAlias.objects.filter(docs__group=q))
+ docs = list(Document.objects.filter(group=q))
related = []
for doc in docs:
doc.product_of_this_wg = True
related += related_docs(doc)
iprs = iprs_from_docs(list(set(docs+related)),states=states)
- docs = [ doc for doc in docs if doc.document.ipr() ]
- docs = sorted(docs, key=lambda x: max([ipr.disclosure.time for ipr in x.document.ipr()]), reverse=True)
+ docs = [ doc for doc in docs if doc.ipr() ]
+ docs = sorted(docs, key=lambda x: max([ipr.disclosure.time for ipr in x.ipr()]), reverse=True)
template = "ipr/search_wg_result.html"
q = Group.objects.get(id=q).acronym # make acronym for use in template
# Search by rfc and id title
# Document list with IPRs
elif search_type == "doctitle":
- docs = list(DocAlias.objects.filter(docs__title__icontains=q))
+ docs = list(Document.objects.filter(title__icontains=q))
related = []
for doc in docs:
related += related_docs(doc)
iprs = iprs_from_docs(list(set(docs+related)),states=states)
- docs = [ doc for doc in docs if doc.document.ipr() ]
- docs = sorted(docs, key=lambda x: max([ipr.disclosure.time for ipr in x.document.ipr()]), reverse=True)
+ docs = [ doc for doc in docs if doc.ipr() ]
+ docs = sorted(docs, key=lambda x: max([ipr.disclosure.time for ipr in x.ipr()]), reverse=True)
template = "ipr/search_doctitle_result.html"
# Search by title of IPR disclosure
diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py
index 605c19902b..0a6974e5bb 100644
--- a/ietf/liaisons/forms.py
+++ b/ietf/liaisons/forms.py
@@ -31,7 +31,7 @@
from ietf.group.models import Group
from ietf.person.models import Email
from ietf.person.fields import SearchableEmailField
-from ietf.doc.models import Document, DocAlias
+from ietf.doc.models import Document
from ietf.utils.fields import DatepickerDateField
from ietf.utils.timezone import date_today, datetime_from_date, DEADLINE_TZINFO
from functools import reduce
@@ -375,8 +375,6 @@ def save_attachments(self):
uploaded_filename = name + extension,
)
)
- if created:
- DocAlias.objects.create(name=attach.name).docs.add(attach)
LiaisonStatementAttachment.objects.create(statement=self.instance,document=attach)
attach_file = io.open(os.path.join(settings.LIAISON_ATTACH_PATH, attach.name + extension), 'wb')
attach_file.write(attached_file.read())
diff --git a/ietf/mailtrigger/migrations/0005_rfc_recipients.py b/ietf/mailtrigger/migrations/0005_rfc_recipients.py
new file mode 100644
index 0000000000..dee49d9133
--- /dev/null
+++ b/ietf/mailtrigger/migrations/0005_rfc_recipients.py
@@ -0,0 +1,25 @@
+# Copyright The IETF Trust 2023, All Rights Reserved
+
+from django.db import migrations
+
+
+def forward(apps, schema_editor):
+ Recipient = apps.get_model("mailtrigger", "Recipient")
+ Recipient.objects.filter(slug="doc_authors").update(
+ template='{% if doc.type_id == "draft" or doc.type_id == "rfc" %}<{{doc.name}}@ietf.org>{% endif %}'
+ )
+
+
+def reverse(apps, schema_editor):
+ Recipient = apps.get_model("mailtrigger", "Recipient")
+ Recipient.objects.filter(slug="doc_authors").update(
+ template='{% if doc.type_id == "draft" %}<{{doc.name}}@ietf.org>{% endif %}'
+ )
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("mailtrigger", "0004_slides_approved"),
+ ]
+
+ operations = [migrations.RunPython(forward, reverse)]
diff --git a/ietf/mailtrigger/models.py b/ietf/mailtrigger/models.py
index 171dbd85ed..1cf84033b3 100644
--- a/ietf/mailtrigger/models.py
+++ b/ietf/mailtrigger/models.py
@@ -96,35 +96,35 @@ def gather_doc_affecteddoc_authors(self, **kwargs):
addrs = []
if 'doc' in kwargs:
for reldoc in kwargs['doc'].related_that_doc(('conflrev','tohist','tois','tops')):
- addrs.extend(Recipient.objects.get(slug='doc_authors').gather(**{'doc':reldoc.document}))
+ addrs.extend(Recipient.objects.get(slug='doc_authors').gather(**{'doc':reldoc}))
return addrs
def gather_doc_affecteddoc_group_chairs(self, **kwargs):
addrs = []
if 'doc' in kwargs:
for reldoc in kwargs['doc'].related_that_doc(('conflrev','tohist','tois','tops')):
- addrs.extend(Recipient.objects.get(slug='doc_group_chairs').gather(**{'doc':reldoc.document}))
+ addrs.extend(Recipient.objects.get(slug='doc_group_chairs').gather(**{'doc':reldoc}))
return addrs
def gather_doc_affecteddoc_notify(self, **kwargs):
addrs = []
if 'doc' in kwargs:
for reldoc in kwargs['doc'].related_that_doc(('conflrev','tohist','tois','tops')):
- addrs.extend(Recipient.objects.get(slug='doc_notify').gather(**{'doc':reldoc.document}))
+ addrs.extend(Recipient.objects.get(slug='doc_notify').gather(**{'doc':reldoc}))
return addrs
def gather_conflict_review_stream_manager(self, **kwargs):
addrs = []
if 'doc' in kwargs:
for reldoc in kwargs['doc'].related_that_doc(('conflrev',)):
- addrs.extend(Recipient.objects.get(slug='doc_stream_manager').gather(**{'doc':reldoc.document}))
+ addrs.extend(Recipient.objects.get(slug='doc_stream_manager').gather(**{'doc':reldoc}))
return addrs
def gather_conflict_review_steering_group(self,**kwargs):
addrs = []
if 'doc' in kwargs:
for reldoc in kwargs['doc'].related_that_doc(('conflrev',)):
- if reldoc.document.stream_id=='irtf':
+ if reldoc.stream_id=='irtf':
addrs.append('"Internet Research Steering Group" Ballot Writeup
{% if downrefs %}
Downward References
{% for ref in downrefs %}
-
- {% if not ref.target.document.std_level %}
+ {% if not ref.target.std_level %}
+++ Warning: The standards level has not been set yet!!!
{% endif %}
- {% if not ref.target.document.stream %}
+ {% if not ref.target.stream %}
+++ Warning: document stream has not been set yet!!!
{% endif %}
{% endfor %}
The ballot for - {{ doc }} + {{ doc }} was just approved.
{% if not downrefs_to_rfc %}No downward references for - {{ doc }} + {{ doc }}
Back + href="{% url "ietf.doc.views_doc.document_main" name=doc.name %}">Back {% else %}Add downward references to RFCs to the DOWNREF registry, if they were identified in the IETF Last Call and approved by the Sponsoring Area Director. @@ -41,7 +41,7 @@
Ballot for - {{ doc }} + {{ doc }} has been sent out.
{% if doc.telechat_date %} @@ -24,5 +24,5 @@
Clear the ballot for
- {{ doc }}?
+ {{ doc }}?
This will clear all ballot positions and discuss entries.
Defer the ballot for
- {{ doc }}?
+ {{ doc }}?
The ballot will then be put on the IESG agenda of {{ telechat_date }}.