Merge branch 'feat/rfc' into main-merge

# Conflicts:
#	ietf/doc/factories.py
#	ietf/doc/tests_status_change.py
#	ietf/name/fixtures/names.json
#	ietf/templates/doc/document_info.html
This commit is contained in:
Jennifer Richards 2023-08-03 18:34:06 -03:00
commit 48d4072eeb
No known key found for this signature in database
GPG key ID: 9B2BF5C5ADDA6A6E
95 changed files with 2469 additions and 1301 deletions

View file

@ -4,6 +4,7 @@ on:
pull_request:
branches:
- 'main'
- 'feat/rfc'
paths:
- 'client/**'
- 'ietf/**'

View file

@ -24,7 +24,7 @@ import debug # pyflakes:ignore
import ietf
from ietf.doc.utils import get_unicode_document_content
from ietf.doc.models import RelatedDocument, State
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, WgRfcFactory
from ietf.group.factories import RoleFactory
from ietf.meeting.factories import MeetingFactory, SessionFactory
from ietf.meeting.models import Session
@ -943,7 +943,7 @@ class RfcdiffSupportTests(TestCase):
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')
@ -973,19 +973,19 @@ class RfcdiffSupportTests(TestCase):
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(),
),
@ -1025,11 +1025,11 @@ class RfcdiffSupportTests(TestCase):
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()))
@ -1037,11 +1037,11 @@ class RfcdiffSupportTests(TestCase):
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(

View file

@ -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,39 @@ 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.type_id == "rfc":
draft_alias = next(iter(document.related_that('became_rfc')), None)
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_alias:
draft = draft_alias.document
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 +378,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()')

View file

@ -84,7 +84,7 @@ for changes, doc, rfc_published in ietf.sync.rfceditor.update_docs_from_rfc_inde
new_rfcs.append(doc)
for c in changes:
log("RFC%s, %s: %s" % (doc.rfcnum, doc.name, c))
log("RFC%s, %s: %s" % (doc.rfc_number, doc.name, c))
sys.exit(0)

View file

@ -30,6 +30,8 @@ class SearchRuleForm(forms.ModelForm):
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 @@ class SearchRuleForm(forms.ModelForm):
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 @@ class SearchRuleForm(forms.ModelForm):
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 @@ class SearchRuleForm(forms.ModelForm):
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"]

View file

@ -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", "0010_move_rfc_docaliases"),
]
operations = [migrations.RunPython(forward, reverse)]

View file

@ -151,7 +151,7 @@ class CommunityListTests(WebTest):
"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 @@ class CommunityListTests(WebTest):
self.assertEqual(len(outbox), mailbox_before + 1)
self.assertTrue(draft.name in outbox[-1]["Subject"])

View file

@ -71,70 +71,103 @@ 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):
rules = SearchRule.objects.none()
if doc.type_id not in ["draft", "rfc"]:
return rules # none
states = list(doc.states.values_list("pk", flat=True))
rules = SearchRule.objects.none()
# 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="ad",
rule_type="author_rfc",
person__in=list(Person.objects.filter(documentauthor__document=doc)),
)
else:
rules |= SearchRule.objects.filter(
rule_type="author",
state__in=states,
person=doc.ad_id,
person__in=list(Person.objects.filter(documentauthor__document=doc)),
)
if doc.shepherd_id:
rules |= SearchRule.objects.filter(
rule_type="shepherd",
state__in=states,
person__email=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,
)
rules |= SearchRule.objects.filter(
rule_type="name_contains",
state__in=states,
name_contains_index=doc, # search our materialized index to avoid full scan
)
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="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(alias.docs.first().pk for alias 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))

View file

@ -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:

View file

@ -43,6 +43,7 @@ class DocActionHolderInline(admin.TabularInline):
class RelatedDocumentInline(admin.TabularInline):
model = RelatedDocument
fk_name= 'source'
def this(self, instance):
return instance.source.canonical_name()
readonly_fields = ['this', ]
@ -125,7 +126,7 @@ admin.site.register(DocReminder, DocReminderAdmin)
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)

View file

@ -82,13 +82,7 @@ class BaseDocumentFactory(factory.django.DjangoModelFactory):
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
@ -118,6 +112,29 @@ class DocumentFactory(BaseDocumentFactory):
group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none')
class RfcFactory(BaseDocumentFactory):
type_id = "rfc"
rfc_number = factory.Sequence(lambda n: n + 1000)
name = factory.LazyAttribute(lambda o: f"rfc{o.rfc_number:d}")
expires = None
@factory.post_generation
def states(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
else:
obj.set_state(State.objects.get(type_id='rfc',slug='published'))
@factory.post_generation
def reset_canonical_name(obj, create, extracted, **kwargs):
if hasattr(obj, '_canonical_name'):
del obj._canonical_name
return None
class IndividualDraftFactory(BaseDocumentFactory):
type_id = 'draft'
@ -136,28 +153,11 @@ class IndividualDraftFactory(BaseDocumentFactory):
obj.set_state(State.objects.get(type_id='draft',slug='active'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
class IndividualRfcFactory(IndividualDraftFactory):
class IndividualRfcFactory(RfcFactory):
group = factory.SubFactory('ietf.group.factories.GroupFactory',acronym='none')
alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000)))
@factory.post_generation
def states(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
else:
obj.set_state(State.objects.get(type_id='draft',slug='rfc'))
@factory.post_generation
def reset_canonical_name(obj, create, extracted, **kwargs):
if hasattr(obj, '_canonical_name'):
del obj._canonical_name
return None
class WgDraftFactory(BaseDocumentFactory):
type_id = 'draft'
group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='wg')
stream_id = 'ietf'
@ -176,30 +176,12 @@ class WgDraftFactory(BaseDocumentFactory):
obj.set_state(State.objects.get(type_id='draft-stream-ietf',slug='wg-doc'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
class WgRfcFactory(WgDraftFactory):
alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000)))
class WgRfcFactory(RfcFactory):
group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='wg')
stream_id = 'ietf'
std_level_id = 'ps'
@factory.post_generation
def states(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if not obj.get_state('draft-iesg'):
obj.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
else:
obj.set_state(State.objects.get(type_id='draft',slug='rfc'))
obj.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
@factory.post_generation
def reset_canonical_name(obj, create, extracted, **kwargs):
if hasattr(obj, '_canonical_name'):
del obj._canonical_name
return None
class RgDraftFactory(BaseDocumentFactory):
@ -222,34 +204,11 @@ class RgDraftFactory(BaseDocumentFactory):
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
class RgRfcFactory(RgDraftFactory):
alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000)))
class RgRfcFactory(RfcFactory):
group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='rg')
stream_id = 'irtf'
std_level_id = 'inf'
@factory.post_generation
def states(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if not obj.get_state('draft-stream-irtf'):
obj.set_state(State.objects.get(type_id='draft-stream-irtf', slug='pub'))
if not obj.get_state('draft-iesg'):
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
else:
obj.set_state(State.objects.get(type_id='draft',slug='rfc'))
obj.set_state(State.objects.get(type_id='draft-stream-irtf', slug='pub'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
@factory.post_generation
def reset_canonical_name(obj, create, extracted, **kwargs):
if hasattr(obj, '_canonical_name'):
del obj._canonical_name
return None
class CharterFactory(BaseDocumentFactory):
@ -278,7 +237,7 @@ class StatusChangeFactory(BaseDocumentFactory):
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):
@ -305,9 +264,9 @@ class ConflictReviewFactory(BaseDocumentFactory):
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
@ -550,33 +509,8 @@ class EditorialDraftFactory(BaseDocumentFactory):
obj.set_state(State.objects.get(type_id='draft-stream-editorial',slug='active'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
class EditorialRfcFactory(RgDraftFactory):
alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000)))
std_level_id = 'inf'
@factory.post_generation
def states(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if not obj.get_state('draft-stream-editorial'):
obj.set_state(State.objects.get(type_id='draft-stream-editorial', slug='pub'))
if not obj.get_state('draft-iesg'):
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
else:
obj.set_state(State.objects.get(type_id='draft',slug='rfc'))
obj.set_state(State.objects.get(type_id='draft-stream-editorial', slug='pub'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
@factory.post_generation
def reset_canonical_name(obj, create, extracted, **kwargs):
if hasattr(obj, '_canonical_name'):
del obj._canonical_name
return None
class EditorialRfcFactory(RgRfcFactory):
pass
class StatementFactory(BaseDocumentFactory):
type_id = "statement"

View file

@ -224,8 +224,8 @@ class RfcFeed(Feed):
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(
{
@ -234,7 +234,7 @@ class RfcFeed(Feed):
"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",

View file

@ -8,7 +8,7 @@ from django import forms
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,11 @@ 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(
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 +148,7 @@ class AddDownrefForm(forms.Form):
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 +158,10 @@ class AddDownrefForm(forms.Form):
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 +173,23 @@ class AddDownrefForm(forms.Form):
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)

View file

@ -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,

View file

@ -24,6 +24,7 @@ from ietf.doc.models import Document
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 @@ class Command(BaseCommand):
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 = next(iter(draft.related_that_doc("became_rfc")), None)
log.assertion("rfc is not None")
if rfc.latest_event(type='published_rfc').time < show_since:
continue
alias = draft.name

View file

@ -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", "0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more"),
]
operations = [
migrations.RunPython(forward),
]

View file

@ -0,0 +1,22 @@
# Generated by Django 4.2.2 on 2023-06-14 22:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("doc", "0005_add_rfc_states"),
]
operations = [
migrations.AddField(
model_name="dochistory",
name="rfc_number",
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name="document",
name="rfc_number",
field=models.PositiveIntegerField(blank=True, null=True),
),
]

View file

@ -0,0 +1,79 @@
# Generated by Django 4.2.2 on 2023-06-15 15:27
from django.db import migrations
def forward(apps, schema_editor):
Document = apps.get_model("doc", "Document")
DocAlias = apps.get_model("doc", "DocAlias")
DocumentAuthor = apps.get_model("doc", "DocumentAuthor")
State = apps.get_model("doc", "State")
draft_rfc_state = State.objects.get(type_id="draft", slug="rfc")
rfc_published_state = State.objects.get(type_id="rfc", slug="published")
DocTypeName = apps.get_model("name", "DocTypeName")
rfc_doctype = DocTypeName(slug="rfc")
# Find draft Documents in the "rfc" state
found_by_state = Document.objects.filter(states=draft_rfc_state).distinct()
# Find Documents with an "rfc..." alias and confirm they're the same set
rfc_docaliases = DocAlias.objects.filter(name__startswith="rfc")
found_by_name = Document.objects.filter(docalias__in=rfc_docaliases).distinct()
assert set(found_by_name) == set(found_by_state), "mismatch between rfcs identified by state and docalias"
# As of 2023-06-15, there is one Document with two rfc aliases: rfc6312 and rfc6342 are the same Document. This
# was due to a publication error. 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 = rfc_doctype
rfc.rfc_number = int(draft.name[3:])
rfc.save()
rfc.states.set([rfc_published_state])
else:
rfc = Document.objects.create(
type=rfc_doctype,
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", "0006_dochistory_rfc_number_document_rfc_number"),
("name", "0004_rfc_doctype_names"),
]
operations = [
migrations.RunPython(forward),
]

View file

@ -0,0 +1,98 @@
# 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!
"sync_from_rfc_editor",
"rfc_editor_received_announcement", # problematic for new RFCs until RPC tools enhancements come in?
]
)
| Q(
type="changed_state",
desc__startswith="RFC Editor state",
)
| Q(
type="changed_state",
desc__startswith="IANA Action state",
)
)
# 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", "0007_create_rfc_documents"),
]
operations = [
migrations.RunPython(forward),
]

View file

@ -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__slug="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", "0008_move_rfc_docevents"),
]
operations = [migrations.RunPython(forward)]

View file

@ -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", "0009_rfc_relateddocuments"),
]
operations = [
migrations.RunPython(forward),
]

View file

@ -0,0 +1,77 @@
# 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
import ietf.utils.models
def forward(apps, schema_editor):
RelatedDocument = apps.get_model("doc", "RelatedDocument")
DocAlias = apps.get_model("doc", "DocAlias")
subquery = Subquery(DocAlias.objects.filter(pk=OuterRef("deprecated_target")).values("docs")[:1])
RelatedDocument.objects.annotate(firstdoc=subquery).update(target=F("firstdoc"))
def reverse(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
("doc", "0010_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.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',
),
),
]

View file

@ -0,0 +1,80 @@
# 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
import ietf.utils.models
def forward(apps, schema_editor):
RelatedDocHistory = apps.get_model("doc", "RelatedDocHistory")
DocAlias = apps.get_model("doc", "DocAlias")
subquery = Subquery(DocAlias.objects.filter(pk=OuterRef("deprecated_target")).values("docs")[:1])
RelatedDocHistory.objects.annotate(firstdoc=subquery).update(target=F("firstdoc"))
def reverse(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
("doc", "0011_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.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',
),
),
]

View file

@ -124,6 +124,7 @@ class DocumentInfo(models.Model):
uploaded_filename = models.TextField(blank=True)
note = models.TextField(blank=True)
internal_comments = models.TextField(blank=True)
rfc_number = models.PositiveIntegerField(blank=True, null=True) # only valid for type="rfc"
def file_extension(self):
if not hasattr(self, '_cached_extension'):
@ -136,18 +137,17 @@ class DocumentInfo(models.Model):
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,14 +172,13 @@ class DocumentInfo(models.Model):
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.canonical_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}'
@ -244,7 +243,7 @@ class DocumentInfo(models.Model):
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 +333,9 @@ class DocumentInfo(models.Model):
if not state:
return "Unknown state"
if self.type_id == 'draft':
if self.type_id == "rfc":
return f"RFC {self.rfc_number} ({self.std_level})"
elif self.type_id == 'draft':
iesg_state = self.get_state("draft-iesg")
iesg_state_summary = None
if iesg_state:
@ -345,7 +346,12 @@ class DocumentInfo(models.Model):
iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate)
if state.slug == "rfc":
return "RFC %s (%s)" % (self.rfc_number(), self.std_level)
rfcs = self.related_that_doc("became_rfc") # should be only one
if len(rfcs) > 0:
rfc = rfcs[0].document
return f"Became RFC {rfc.rfc_number} ({rfc.std_level})"
else:
return "Became RFC"
elif state.slug == "repl":
rs = self.related_that("replaces")
if rs:
@ -375,27 +381,6 @@ class DocumentInfo(models.Model):
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 +453,9 @@ class DocumentInfo(models.Model):
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,8 +489,7 @@ class DocumentInfo(models.Model):
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):
@ -656,10 +640,20 @@ class DocumentInfo(models.Model):
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"
)
class Meta:
abstract = True
@ -668,7 +662,7 @@ STATUSCHANGE_RELATIONS = ('tops','tois','tohist','toinf','tobcp','toexp')
class RelatedDocument(models.Model):
source = ForeignKey('Document')
target = ForeignKey('DocAlias')
target = ForeignKey('Document', related_name='targets_related')
relationship = ForeignKey(DocRelationshipName)
def action(self):
return self.relationship.name
@ -691,16 +685,16 @@ class RelatedDocument(models.Model):
if source_lvl not in ['bcp','ps','ds','std']:
return None
if self.target.document.get_state().slug == 'rfc':
if not self.target.document.std_level:
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.slug
else:
if not self.target.document.intended_std_level:
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.slug
rank = { 'ps':1, 'ds':2, 'std':3, 'bcp':3 }
@ -714,8 +708,8 @@ class RelatedDocument(models.Model):
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
@ -856,12 +850,6 @@ class Document(DocumentInfo):
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
@ -973,7 +961,15 @@ class Document(DocumentInfo):
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=('posted','removed')).values_list('disclosure', flat=True).distinct()
iprs = (
IprDocRel.objects.filter(
document__in=list(self.docalias.all())
+ [x.docalias.first() for x in self.all_related_that_doc(("obs", "replaces"))] # this really is docalias until IprDocRel changes
)
.filter(disclosure__state__in=("posted", "removed"))
.values_list("disclosure", flat=True)
.distinct()
)
return iprs
def future_presentations(self):
@ -1010,7 +1006,7 @@ class Document(DocumentInfo):
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.
@ -1112,7 +1108,7 @@ 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)
def __str__(self):
return u"%s %s %s" % (self.source.doc.name, self.relationship.name.lower(), self.target.name)

View file

@ -288,8 +288,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.canonical_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 +556,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:

View file

@ -630,23 +630,22 @@ Man Expires September 22, 2015 [Page 3]
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
@ -659,16 +658,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertNotContains(r, "Deimos street")
self.assertContains(r, replaced.canonical_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)
@ -677,15 +666,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertNotContains(r, "Deimos street")
self.assertContains(r, replaced.canonical_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)
@ -694,15 +674,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertContains(r, "Deimos street")
self.assertContains(r, replaced.canonical_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)
@ -711,15 +682,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertContains(r, "Deimos street")
self.assertContains(r, replaced.canonical_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)))
@ -729,15 +691,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertContains(r, "Deimos street")
self.assertContains(r, replaced.canonical_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)))
@ -747,15 +700,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertNotContains(r, "Deimos street")
self.assertContains(r, replaced.canonical_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)))
@ -766,15 +710,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertNotContains(r, "Deimos street")
self.assertContains(r, replaced.canonical_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)
@ -803,12 +738,12 @@ Man Expires September 22, 2015 [Page 3]
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.canonical_name())))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(q('title').text(), f'RFC {rfc.rfc_number()} - {rfc.title}')
self.assertEqual(q('title').text(), f'RFC {rfc.rfc_number} - {rfc.title}')
# synonyms for the rfc should be redirected to its canonical view
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.rfc_number())))
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.rfc_number)))
self.assertRedirects(r, urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.canonical_name())))
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=f'RFC {rfc.rfc_number()}')))
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=f'RFC {rfc.rfc_number}')))
self.assertRedirects(r, urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=rfc.canonical_name())))
# expired draft
@ -830,7 +765,7 @@ Man Expires September 22, 2015 [Page 3]
shepherd_id=draft.shepherd_id, ad_id=draft.ad_id, expires=draft.expires,
notify=draft.notify, note=draft.note)
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)))
@ -842,26 +777,29 @@ Man Expires September 22, 2015 [Page 3]
# 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)"))])
rfc_alias = DocAlias.objects.create(name="rfc123456")
rfc_alias.docs.add(draft)
bcp_alias = DocAlias.objects.create(name="bcp123456")
bcp_alias.docs.add(draft)
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
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.title)
@ -902,7 +840,7 @@ Man Expires September 22, 2015 [Page 3]
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',
@ -910,7 +848,7 @@ Man Expires September 22, 2015 [Page 3]
)
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(
@ -1501,11 +1439,11 @@ Man Expires September 22, 2015 [Page 3]
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)
@ -1516,11 +1454,11 @@ Man Expires September 22, 2015 [Page 3]
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)
@ -1621,8 +1559,8 @@ class DocTestCase(TestCase):
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')
@ -1786,8 +1724,8 @@ class DocTestCase(TestCase):
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()
@ -1800,12 +1738,12 @@ class DocTestCase(TestCase):
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')
@ -1868,15 +1806,14 @@ class DocTestCase(TestCase):
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)
@ -1926,7 +1863,7 @@ class DocTestCase(TestCase):
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)
@ -1989,7 +1926,7 @@ class DocTestCase(TestCase):
std_level_id = 'ps',
time = datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE)),
)
num = rfc.rfc_number()
num = rfc.rfc_number
DocEventFactory.create(
doc=rfc,
type='published_rfc',
@ -2000,7 +1937,7 @@ class DocTestCase(TestCase):
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(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')
@ -2020,7 +1957,7 @@ class DocTestCase(TestCase):
std_level_id = 'inf',
time = datetime.datetime(1990, 4, 1, tzinfo=ZoneInfo(settings.TIME_ZONE)),
)
num = april1.rfc_number()
num = april1.rfc_number
DocEventFactory.create(
doc=april1,
type='published_rfc',
@ -2032,7 +1969,7 @@ class DocTestCase(TestCase):
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(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')
@ -2129,7 +2066,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)
@ -2141,124 +2078,168 @@ class ReferencesTest(TestCase):
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 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 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])
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())
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_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)
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',
]]))
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):
@ -2692,10 +2673,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)
@ -2720,6 +2701,8 @@ class Idnits2SupportTests(TestCase):
def test_idnits2_state(self):
rfc = WgRfcFactory()
draft = WgDraftFactory()
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
url = urlreverse('ietf.doc.views_doc.idnits2_state', kwargs=dict(name=rfc.canonical_name()))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
@ -2778,16 +2761,12 @@ class RawIdTests(TestCase):
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):
@ -2806,24 +2785,27 @@ class PdfizedTests(TestCase):
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):

View file

@ -803,8 +803,8 @@ class ApproveBallotTests(TestCase):
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 @@ class ApproveBallotTests(TestCase):
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 @@ class RegenerateLastCallTestCase(TestCase):
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 @@ class RegenerateLastCallTestCase(TestCase):
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)

View file

@ -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 @@ class EditCharterTests(TestCase):
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 @@ class EditCharterTests(TestCase):
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'))

View file

@ -70,12 +70,12 @@ class ConflictReviewTests(TestCase):
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 @@ class ConflictReviewTests(TestCase):
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)

View file

@ -22,9 +22,9 @@ class Downref(TestCase):
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.rfc = WgRfcFactory(rfc_number=9998)
self.rfcalias = self.rfc.docalias.get(name='rfc9998')
RelatedDocument.objects.create(source=self.doc, target=self.rfcalias, relationship_id='downref-approval')
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 +64,44 @@ class Downref(TestCase):
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, '<a href="/doc/draft-ietf-mars-test')
self.assertTrue(RelatedDocument.objects.filter(source=self.draft, target=self.rfcalias, relationship_id='downref-approval'))
self.assertTrue(RelatedDocument.objects.filter(source=self.draft, target=self.rfc, relationship_id='downref-approval'))
self.assertEqual(self.draft.docevent_set.count(), draft_de_count_before + 1)
self.assertEqual(self.rfc.docevent_set.count(), rfc_de_count_before + 1)
def test_downref_last_call(self):
draft = WgDraftFactory(name='draft-ietf-mars-ready-for-lc-document',intended_std_level_id='ps',states=[('draft-iesg','iesg-eva')])
WgDraftFactory(name='draft-ietf-mars-another-approved-document',states=[('draft-iesg','rfcqueue')])
rfc9999 = WgRfcFactory(alias2__name='rfc9999', std_level_id=None)
RelatedDocument.objects.create(source=draft, target=rfc9999.docalias.get(name='rfc9999'), relationship_id='refnorm')
rfc9999 = WgRfcFactory(rfc_number=9999, std_level_id=None)
RelatedDocument.objects.create(source=draft, target=rfc9999, relationship_id='refnorm')
url = urlreverse('ietf.doc.views_ballot.lastcalltext', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "secretary", url)
@ -113,7 +113,7 @@ class Downref(TestCase):
self.assertIn('The document contains these normative downward references', text)
# now, the announcement text about the downref to RFC 9999 should be gone
RelatedDocument.objects.create(source=draft, target=rfc9999.docalias.get(name='rfc9999'),relationship_id='downref-approval')
RelatedDocument.objects.create(source=draft, target=rfc9999, relationship_id='downref-approval')
r = self.client.post(url, dict(regenerate_last_call_text="1"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)

View file

@ -2017,10 +2017,10 @@ class ChangeReplacesTests(TestCase):
# Post that says replacea replaces base a
empty_outbox()
RelatedDocument.objects.create(source=self.replacea, target=self.basea.docalias.first(),
RelatedDocument.objects.create(source=self.replacea, target=self.basea,
relationship=DocRelationshipName.objects.get(slug="possibly-replaces"))
self.assertEqual(self.basea.get_state().slug,'active')
r = self.client.post(url, dict(replaces=self.basea.docalias.first().pk))
r = self.client.post(url, dict(replaces=self.basea.pk))
self.assertEqual(r.status_code, 302)
self.assertEqual(RelatedDocument.objects.filter(relationship__slug='replaces',source=self.replacea).count(),1)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
@ -2034,7 +2034,7 @@ class ChangeReplacesTests(TestCase):
# Post that says replaceboth replaces both base a and base b
url = urlreverse('ietf.doc.views_draft.replaces', kwargs=dict(name=self.replaceboth.name))
self.assertEqual(self.baseb.get_state().slug,'expired')
r = self.client.post(url, dict(replaces=[self.basea.docalias.first().pk, self.baseb.docalias.first().pk]))
r = self.client.post(url, dict(replaces=[self.basea.pk, self.baseb.pk]))
self.assertEqual(r.status_code, 302)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
@ -2065,7 +2065,7 @@ class ChangeReplacesTests(TestCase):
def test_review_possibly_replaces(self):
replaced = self.basea.docalias.first()
replaced = self.basea
RelatedDocument.objects.create(source=self.replacea, target=replaced,
relationship=DocRelationshipName.objects.get(slug="possibly-replaces"))
@ -2093,7 +2093,7 @@ class MoreReplacesTests(TestCase):
new_doc = IndividualDraftFactory(stream_id=stream)
url = urlreverse('ietf.doc.views_draft.replaces', kwargs=dict(name=new_doc.name))
r = self.client.post(url, dict(replaces=old_doc.docalias.first().pk))
r = self.client.post(url, dict(replaces=old_doc.pk))
self.assertEqual(r.status_code,302)
old_doc = Document.objects.get(name=old_doc.name)
self.assertEqual(old_doc.get_state_slug('draft'),'repl')

View file

@ -137,10 +137,18 @@ class ReviewTests(TestCase):
url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
login_testing_unauthorized(self, "ad", url)
# get should fail
# get should fail - all non draft types 404
r = self.client.get(url)
self.assertEqual(r.status_code, 404)
# Can only request reviews on active draft documents
doc = WgDraftFactory(states=[("draft","rfc")])
url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
def test_doc_page(self):
doc = WgDraftFactory(group__acronym='mars',rev='01')
@ -153,8 +161,8 @@ class ReviewTests(TestCase):
# check we can fish it out
old_doc = WgDraftFactory(name="draft-foo-mars-test")
older_doc = WgDraftFactory(name="draft-older")
RelatedDocument.objects.create(source=old_doc, target=older_doc.docalias.first(), relationship_id='replaces')
RelatedDocument.objects.create(source=doc, target=old_doc.docalias.first(), relationship_id='replaces')
RelatedDocument.objects.create(source=old_doc, target=older_doc, relationship_id='replaces')
RelatedDocument.objects.create(source=doc, target=old_doc, relationship_id='replaces')
review_req.doc = older_doc
review_req.save()

View file

@ -14,8 +14,9 @@ from textwrap import wrap
from django.conf import settings
from django.urls import reverse as urlreverse
from ietf.doc.factories import DocumentFactory, IndividualRfcFactory, WgRfcFactory, DocEventFactory
from ietf.doc.models import ( Document, DocAlias, State, DocEvent,
from ietf.doc.factories import ( DocumentFactory, IndividualRfcFactory,
WgRfcFactory, DocEventFactory, WgDraftFactory )
from ietf.doc.models import ( Document, State, DocEvent,
BallotPositionDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent )
from ietf.doc.utils import create_ballot_if_not_open
from ietf.doc.views_status_change import default_approval_text
@ -74,7 +75,7 @@ class StatusChangeTests(TestCase):
self.assertEqual(status_change.rev,'00')
self.assertEqual(status_change.ad.name,'Areað Irector')
self.assertEqual(status_change.notify,'ipu@ietf.org')
self.assertTrue(status_change.relateddocument_set.filter(relationship__slug='tois',target__docs__name='draft-ietf-random-thing'))
self.assertTrue(status_change.relateddocument_set.filter(relationship__slug='tois',target__name='rfc9999'))
# Verify that it's possible to start a status change without a responsible ad.
r = self.client.post(url,dict(
@ -169,8 +170,8 @@ class StatusChangeTests(TestCase):
self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('Notification list changed'))
# Some additional setup so there's something to put in a generated notify list
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9998'),relationship_id='tohist')
# Ask the form to regenerate the list
r = self.client.post(url,dict(regenerate_addresses="1"))
@ -273,8 +274,8 @@ class StatusChangeTests(TestCase):
login_testing_unauthorized(self, "ad", url)
# additional setup
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9998'),relationship_id='tohist')
doc.ad = Person.objects.get(name='Ad No2')
doc.save_with_history([DocEvent.objects.create(doc=doc, rev=doc.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
@ -329,8 +330,8 @@ class StatusChangeTests(TestCase):
login_testing_unauthorized(self, "secretary", url)
# Some additional setup
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9998'),relationship_id='tohist')
create_ballot_if_not_open(None, doc, Person.objects.get(user__username="secretary"), "statchg")
doc.set_state(State.objects.get(slug='appr-pend',type='statchg'))
@ -370,10 +371,10 @@ class StatusChangeTests(TestCase):
url = urlreverse('ietf.doc.views_status_change.change_state',kwargs=dict(name=doc.name))
# Add some status change related documents
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9998'),relationship_id='tohist')
# And a non-status change related document
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc14'),relationship_id='updates')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc14'),relationship_id='updates')
login_testing_unauthorized(self, role, url)
empty_outbox()
@ -395,9 +396,9 @@ class StatusChangeTests(TestCase):
self.assertTrue(notification['Subject'].startswith('Approved:'))
notification_text = get_payload_text(notification)
self.assertIn('The AD has approved changing the status', notification_text)
self.assertIn(DocAlias.objects.get(name='rfc9999').document.canonical_name(), notification_text)
self.assertIn(DocAlias.objects.get(name='rfc9998').document.canonical_name(), notification_text)
self.assertNotIn(DocAlias.objects.get(name='rfc14').document.canonical_name(), notification_text)
self.assertIn(Document.objects.get(name='rfc9999').canonical_name(), notification_text)
self.assertIn(Document.objects.get(name='rfc9998').canonical_name(), notification_text)
self.assertNotIn(Document.objects.get(name='rfc14').canonical_name(), notification_text)
self.assertNotIn('No value found for', notification_text) # make sure all interpolation values were set
else:
self.assertEqual(len(outbox), 0)
@ -417,8 +418,8 @@ class StatusChangeTests(TestCase):
login_testing_unauthorized(self, "secretary", url)
# Some additional setup
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=Document.objects.get(name='rfc9998'),relationship_id='tohist')
# get
r = self.client.get(url)
@ -471,9 +472,16 @@ class StatusChangeTests(TestCase):
def setUp(self):
super().setUp()
IndividualRfcFactory(alias2__name='rfc14',name='draft-was-never-issued',std_level_id='unkn')
WgRfcFactory(alias2__name='rfc9999',name='draft-ietf-random-thing',std_level_id='ps')
WgRfcFactory(alias2__name='rfc9998',name='draft-ietf-random-other-thing',std_level_id='inf')
IndividualRfcFactory(rfc_number=14,std_level_id='unkn') # draft was never issued
rfc = WgRfcFactory(rfc_number=9999,std_level_id='ps')
draft = WgDraftFactory(name='draft-ietf-random-thing')
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
rfc = WgRfcFactory(rfc_number=9998,std_level_id='inf')
draft = WgDraftFactory(name='draft-ietf-random-other-thing')
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
DocumentFactory(type_id='statchg',name='status-change-imaginary-mid-review',notify='notify@example.org')
class StatusChangeSubmitTests(TestCase):

View file

@ -11,10 +11,10 @@ from django.utils import timezone
from ietf.group.factories import GroupFactory, RoleFactory
from ietf.name.models import DocTagName
from ietf.person.factories import PersonFactory
from ietf.utils.test_utils import TestCase, name_of_file_containing
from ietf.utils.test_utils import TestCase, name_of_file_containing, reload_db_objects
from ietf.person.models import Person
from ietf.doc.factories import DocumentFactory, WgRfcFactory, WgDraftFactory
from ietf.doc.models import State, DocumentActionHolder, DocumentAuthor, Document
from ietf.doc.models import State, DocumentActionHolder, DocumentAuthor
from ietf.doc.utils import (update_action_holders, add_state_change_event, update_documentauthors,
fuzzy_find_documents, rebuild_reference_relations, build_file_urls)
from ietf.utils.draft import Draft, PlaintextDraft
@ -251,40 +251,42 @@ class MiscTests(TestCase):
self.assertEqual(docauth.country, '')
def do_fuzzy_find_documents_rfc_test(self, name):
rfc = WgRfcFactory(name=name, create_revisions=(0, 1, 2))
rfc = Document.objects.get(pk=rfc.pk) # clear out any cached values
draft = WgDraftFactory(name=name, create_revisions=(0, 1, 2))
rfc = WgRfcFactory()
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
draft, rfc = reload_db_objects(draft, rfc)
# by canonical name
found = fuzzy_find_documents(rfc.canonical_name(), None)
self.assertCountEqual(found.documents, [rfc])
self.assertEqual(found.matched_rev, None)
self.assertEqual(found.matched_name, rfc.canonical_name())
# by draft name, no rev
found = fuzzy_find_documents(rfc.name, None)
self.assertCountEqual(found.documents, [rfc])
self.assertEqual(found.matched_rev, None)
self.assertEqual(found.matched_name, rfc.name)
# by draft name, no rev
found = fuzzy_find_documents(draft.name, None)
self.assertCountEqual(found.documents, [draft])
self.assertEqual(found.matched_rev, None)
self.assertEqual(found.matched_name, draft.name)
# by draft name, latest rev
found = fuzzy_find_documents(rfc.name, '02')
self.assertCountEqual(found.documents, [rfc])
found = fuzzy_find_documents(draft.name, '02')
self.assertCountEqual(found.documents, [draft])
self.assertEqual(found.matched_rev, '02')
self.assertEqual(found.matched_name, rfc.name)
self.assertEqual(found.matched_name, draft.name)
# by draft name, earlier rev
found = fuzzy_find_documents(rfc.name, '01')
self.assertCountEqual(found.documents, [rfc])
found = fuzzy_find_documents(draft.name, '01')
self.assertCountEqual(found.documents, [draft])
self.assertEqual(found.matched_rev, '01')
self.assertEqual(found.matched_name, rfc.name)
self.assertEqual(found.matched_name, draft.name)
# wrong name or revision
found = fuzzy_find_documents(rfc.name + '-incorrect')
found = fuzzy_find_documents(draft.name + '-incorrect')
self.assertCountEqual(found.documents, [], 'Should not find document that does not match')
found = fuzzy_find_documents(rfc.name + '-incorrect', '02')
found = fuzzy_find_documents(draft.name + '-incorrect', '02')
self.assertCountEqual(found.documents, [], 'Still should not find document, even with a version')
found = fuzzy_find_documents(rfc.name, '22')
self.assertCountEqual(found.documents, [rfc],
found = fuzzy_find_documents(draft.name, '22')
self.assertCountEqual(found.documents, [draft],
'Should find document even if rev does not exist')
@ -325,14 +327,14 @@ class RebuildReferenceRelationsTests(TestCase):
super().setUp()
self.doc = WgDraftFactory() # document under test
# Other documents that should be found by rebuild_reference_relations
self.normative, self.informative, self.unknown = WgRfcFactory.create_batch(3)
self.normative, self.informative, self.unknown = WgRfcFactory.create_batch(3) # AMHERE - these need to have rfc names.
for relationship in ['refnorm', 'refinfo', 'refunk', 'refold']:
self.doc.relateddocument_set.create(
target=WgRfcFactory().docalias.first(),
target=WgRfcFactory(),
relationship_id=relationship,
)
self.updated = WgRfcFactory() # related document that should be left alone
self.doc.relateddocument_set.create(target=self.updated.docalias.first(), relationship_id='updates')
self.doc.relateddocument_set.create(target=self.updated, relationship_id='updates')
self.assertCountEqual(self.doc.relateddocument_set.values_list('relationship__slug', flat=True),
['refnorm', 'refinfo', 'refold', 'refunk', 'updates'],
'Test conditions set up incorrectly: wrong prior document relationships')
@ -378,7 +380,7 @@ class RebuildReferenceRelationsTests(TestCase):
self.assertEqual(
result,
{
'warnings': ['There were 1 references with no matching DocAlias'],
'warnings': ['There were 1 references with no matching Document'],
'unfound': ['draft-not-found'],
}
)
@ -389,7 +391,7 @@ class RebuildReferenceRelationsTests(TestCase):
(self.normative.canonical_name(), 'refnorm'),
(self.informative.canonical_name(), 'refinfo'),
(self.unknown.canonical_name(), 'refunk'),
(self.updated.docalias.first().name, 'updates'),
(self.updated.name, 'updates'),
]
)
@ -409,7 +411,7 @@ class RebuildReferenceRelationsTests(TestCase):
self.assertEqual(
result,
{
'warnings': ['There were 1 references with no matching DocAlias'],
'warnings': ['There were 1 references with no matching Document'],
'unfound': ['draft-not-found'],
}
)
@ -420,7 +422,7 @@ class RebuildReferenceRelationsTests(TestCase):
(self.normative.canonical_name(), 'refnorm'),
(self.informative.canonical_name(), 'refinfo'),
(self.unknown.canonical_name(), 'refunk'),
(self.updated.docalias.first().name, 'updates'),
(self.updated.name, 'updates'),
]
)
@ -441,7 +443,7 @@ class RebuildReferenceRelationsTests(TestCase):
self.assertEqual(
result,
{
'warnings': ['There were 1 references with no matching DocAlias'],
'warnings': ['There were 1 references with no matching Document'],
'unfound': ['draft-not-found'],
}
)
@ -452,6 +454,6 @@ class RebuildReferenceRelationsTests(TestCase):
(self.normative.canonical_name(), 'refnorm'),
(self.informative.canonical_name(), 'refinfo'),
(self.unknown.canonical_name(), 'refunk'),
(self.updated.docalias.first().name, 'updates'),
(self.updated.name, 'updates'),
]
)

View file

@ -31,7 +31,7 @@ from ietf.community.models import CommunityList
from ietf.community.utils import docs_tracked_by_community_list
from ietf.doc.models import Document, DocHistory, State, DocumentAuthor, DocHistoryAuthor
from ietf.doc.models import DocAlias, RelatedDocument, RelatedDocHistory, BallotType, DocReminder
from ietf.doc.models import RelatedDocument, RelatedDocHistory, BallotType, DocReminder
from ietf.doc.models import DocEvent, ConsensusDocEvent, BallotDocEvent, IRSGBallotDocEvent, NewRevisionDocEvent, StateDocEvent
from ietf.doc.models import TelechatDocEvent, DocumentActionHolder, EditedAuthorsDocEvent
from ietf.name.models import DocReminderTypeName, DocRelationshipName
@ -218,7 +218,7 @@ def needed_ballot_positions(doc, active_positions):
else:
related_set = RelatedDocHistory.objects.none()
for rel in related_set.filter(relationship__slug__in=['tops', 'tois', 'tohist', 'toinf', 'tobcp', 'toexp']):
if (rel.target.document.std_level_id in ['bcp','ps','ds','std']) or (rel.relationship_id in ['tops','tois','tobcp']):
if (rel.target.std_level_id in ['bcp','ps','ds','std']) or (rel.relationship_id in ['tops','tois','tobcp']):
needed = two_thirds_rule(recused=len(recuse))
break
else:
@ -771,22 +771,22 @@ def rebuild_reference_relations(doc, filenames):
errors = []
unfound = set()
for ( ref, refType ) in refs.items():
refdoc = DocAlias.objects.filter(name=ref)
refdoc = Document.objects.filter(name=ref)
if not refdoc and re.match(r"^draft-.*-\d{2}$", ref):
refdoc = DocAlias.objects.filter(name=ref[:-3])
refdoc = Document.objects.filter(name=ref[:-3])
count = refdoc.count()
# As of Dec 2021, DocAlias has a unique constraint on the name field, so count > 1 should not occur
if count == 0:
unfound.add( "%s" % ref )
continue
elif count > 1:
errors.append("Too many DocAlias objects found for %s"%ref)
errors.append("Too many Document objects found for %s"%ref)
else:
# Don't add references to ourself
if doc != refdoc[0].document:
if doc != refdoc[0]:
RelatedDocument.objects.get_or_create( source=doc, target=refdoc[ 0 ], relationship=DocRelationshipName.objects.get( slug='ref%s' % refType ) )
if unfound:
warnings.append('There were %d references with no matching DocAlias'%len(unfound))
warnings.append('There were %d references with no matching Document'%len(unfound))
ret = {}
if errors:
@ -821,26 +821,26 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, com
for d in old_replaces:
if d not in new_replaces:
other_addrs = gather_address_lists('doc_replacement_changed',doc=d.document)
other_addrs = gather_address_lists('doc_replacement_changed',doc=d)
to.update(other_addrs.to)
cc.update(other_addrs.cc)
RelatedDocument.objects.filter(source=doc, target=d, relationship=relationship).delete()
if not RelatedDocument.objects.filter(target=d, relationship=relationship):
s = 'active' if d.document.expires > timezone.now() else 'expired'
d.document.set_state(State.objects.get(type='draft', slug=s))
s = 'active' if d.expires > timezone.now() else 'expired'
d.set_state(State.objects.get(type='draft', slug=s))
for d in new_replaces:
if d not in old_replaces:
other_addrs = gather_address_lists('doc_replacement_changed',doc=d.document)
other_addrs = gather_address_lists('doc_replacement_changed',doc=d)
to.update(other_addrs.to)
cc.update(other_addrs.cc)
RelatedDocument.objects.create(source=doc, target=d, relationship=relationship)
d.document.set_state(State.objects.get(type='draft', slug='repl'))
d.set_state(State.objects.get(type='draft', slug='repl'))
if d.document.stream_id in ('irtf','ise','iab'):
repl_state = State.objects.get(type_id='draft-stream-%s'%d.document.stream_id, slug='repl')
d.document.set_state(repl_state)
events.append(StateDocEvent.objects.create(doc=d.document, rev=d.document.rev, by=by, type='changed_state', desc="Set stream state to Replaced",state_type=repl_state.type, state=repl_state))
if d.stream_id in ('irtf','ise','iab'):
repl_state = State.objects.get(type_id='draft-stream-%s'%d.stream_id, slug='repl')
d.set_state(repl_state)
events.append(StateDocEvent.objects.create(doc=d, rev=d.rev, by=by, type='changed_state', desc="Set stream state to Replaced",state_type=repl_state.type, state=repl_state))
# make sure there are no lingering suggestions duplicating new replacements
RelatedDocument.objects.filter(source=doc, target__in=new_replaces, relationship="possibly-replaces").delete()
@ -910,7 +910,7 @@ def extract_complete_replaces_ancestor_mapping_for_docs(names):
break
relations = ( RelatedDocument.objects.filter(source__name__in=front, relationship="replaces")
.select_related("target").values_list("source__name", "target__docs__name") )
.select_related("target").values_list("source__name", "target__name") )
if not relations:
break
@ -998,14 +998,11 @@ def get_search_cache_key(params):
kwargs = dict([ (k,v) for (k,v) in list(params.items()) if k in fields ])
key = "doc:document:search:" + hashlib.sha512(json.dumps(kwargs, sort_keys=True).encode('utf-8')).hexdigest()
return key
def build_file_urls(doc: Union[Document, DocHistory]):
if doc.type_id != 'draft':
return [], []
if doc.get_state_slug() == "rfc":
name = doc.canonical_name()
base_path = os.path.join(settings.RFC_PATH, name + ".")
def build_file_urls(doc: Union[Document, DocHistory]):
if doc.type_id == "rfc":
base_path = os.path.join(settings.RFC_PATH, doc.name + ".")
possible_types = settings.RFC_FILE_TYPES
found_types = [t for t in possible_types if os.path.exists(base_path + t)]
@ -1014,17 +1011,17 @@ def build_file_urls(doc: Union[Document, DocHistory]):
file_urls = []
for t in found_types:
label = "plain text" if t == "txt" else t
file_urls.append((label, base + name + "." + t))
file_urls.append((label, base + doc.name + "." + t))
if "pdf" not in found_types and "txt" in found_types:
file_urls.append(("pdf", base + "pdfrfc/" + name + ".txt.pdf"))
file_urls.append(("pdf", base + "pdfrfc/" + doc.name + ".txt.pdf"))
if "txt" in found_types:
file_urls.append(("htmlized", urlreverse('ietf.doc.views_doc.document_html', kwargs=dict(name=name))))
file_urls.append(("htmlized", urlreverse('ietf.doc.views_doc.document_html', kwargs=dict(name=doc.name))))
if doc.tags.filter(slug="verified-errata").exists():
file_urls.append(("with errata", settings.RFC_EDITOR_INLINE_ERRATA_URL.format(rfc_number=doc.rfc_number())))
file_urls.append(("bibtex", urlreverse('ietf.doc.views_doc.document_bibtex',kwargs=dict(name=name))))
elif doc.rev:
file_urls.append(("with errata", settings.RFC_EDITOR_INLINE_ERRATA_URL.format(rfc_number=doc.rfc_number)))
file_urls.append(("bibtex", urlreverse('ietf.doc.views_doc.document_bibtex',kwargs=dict(name=doc.name))))
elif doc.type_id == "draft" and doc.rev != "":
base_path = os.path.join(settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR, doc.name + "-" + doc.rev + ".")
possible_types = settings.IDSUBMIT_FILE_TYPES
found_types = [t for t in possible_types if os.path.exists(base_path + t)]
@ -1040,12 +1037,14 @@ def build_file_urls(doc: Union[Document, DocHistory]):
file_urls.append(("bibtex", urlreverse('ietf.doc.views_doc.document_bibtex',kwargs=dict(name=doc.name,rev=doc.rev))))
file_urls.append(("bibxml", urlreverse('ietf.doc.views_doc.document_bibxml',kwargs=dict(name=doc.name,rev=doc.rev))))
else:
# As of 2022-12-14, there are 1463 Document and 3136 DocHistory records with type='draft' and rev=''.
# All of these are in the rfc state and are covered by the above cases.
log.unreachable('2022-12-14')
if doc.type_id == "draft":
# TODO: look at the state of the database post migration and update this comment, or remove the block
# As of 2022-12-14, there are 1463 Document and 3136 DocHistory records with type='draft' and rev=''.
# All of these are in the rfc state and are covered by the above cases.
log.unreachable('2022-12-14')
file_urls = []
found_types = []
return file_urls, found_types
def augment_docs_and_user_with_user_info(docs, user):
@ -1112,16 +1111,16 @@ def generate_idnits2_rfc_status():
'unkn': 'U',
}
rfcs = Document.objects.filter(type_id='draft',states__slug='rfc',states__type='draft')
rfcs = Document.objects.filter(type_id='rfc')
for rfc in rfcs:
offset = int(rfc.rfcnum)-1
offset = int(rfc.rfc_number)-1
blob[offset] = symbols[rfc.std_level_id]
if rfc.related_that('obs'):
blob[offset] = 'O'
# Workarounds for unusual states in the datatracker
# Document.get(docalias='rfc6312').rfcnum == 6342
# Document.get(docalias='rfc6312').rfc_number == 6342
# 6312 was published with the wrong rfc number in it
# weird workaround in the datatracker - there are two
# DocAliases starting with rfc - the canonical name code
@ -1142,7 +1141,7 @@ def generate_idnits2_rfc_status():
def generate_idnits2_rfcs_obsoleted():
obsdict = defaultdict(list)
for r in RelatedDocument.objects.filter(relationship_id='obs'):
obsdict[int(r.target.document.rfc_number())].append(int(r.source.rfc_number()))
obsdict[int(r.target.rfc_number)].append(int(r.source.rfc_number)) # Aren't these already guaranteed to be ints?
for k in obsdict:
obsdict[k] = sorted(obsdict[k])
return render_to_string('doc/idnits2-rfcs-obsoleted.txt', context={'obsitems':sorted(obsdict.items())})
@ -1171,8 +1170,14 @@ def fuzzy_find_documents(name, rev=None):
if re.match("^[0-9]+$", name):
name = f'rfc{name}'
if name.startswith("rfc"):
sought_type = "rfc"
log.assertion("rev is None")
else:
sought_type = "draft"
# see if we can find a document using this name
docs = Document.objects.filter(docalias__name=name, type_id='draft')
docs = Document.objects.filter(docalias__name=name, type_id=sought_type)
if rev and not docs.exists():
# No document found, see if the name/rev split has been misidentified.
# Handles some special cases, like draft-ietf-tsvwg-ieee-802-11.

View file

@ -3,11 +3,12 @@
import datetime
import io
import os
import re
import shutil
from pathlib import Path
from django.conf import settings
from django.urls import reverse as urlreverse
from django.template.loader import render_to_string
@ -62,10 +63,9 @@ def next_approved_revision(rev):
return "%#02d" % (int(m.group('major')) + 1)
def read_charter_text(doc):
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev))
filename = Path(settings.CHARTER_PATH) / f"{doc.name}-{doc.rev}.txt"
try:
with io.open(filename, 'r') as f:
return f.read()
return filename.read_text()
except IOError:
return "Error: couldn't read charter text"
@ -92,8 +92,8 @@ def change_group_state_after_charter_approval(group, by):
def fix_charter_revision_after_approval(charter, by):
# according to spec, 00-02 becomes 01, so copy file and record new revision
try:
old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.name, charter.rev))
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.name, next_approved_revision(charter.rev)))
shutil.copy(old, new)
except IOError:
log("There was an error copying %s to %s" % (old, new))
@ -101,7 +101,7 @@ def fix_charter_revision_after_approval(charter, by):
events = []
e = NewRevisionDocEvent(doc=charter, by=by, type="new_revision")
e.rev = next_approved_revision(charter.rev)
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), e.rev)
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.name, e.rev)
e.save()
events.append(e)

View file

@ -93,7 +93,7 @@ def fill_in_document_table_attributes(docs, have_telechat_date=False):
# emulate canonical name which is used by a lot of the utils
# d.canonical_name = wrap_value(rfc_aliases[d.pk] if d.pk in rfc_aliases else d.name)
if d.rfc_number() != None and d.latest_event_cache["published_rfc"]:
if d.type_id == "rfc" and d.latest_event_cache["published_rfc"]:
d.latest_revision_date = d.latest_event_cache["published_rfc"].time
elif d.latest_event_cache["new_revision"]:
d.latest_revision_date = d.latest_event_cache["new_revision"].time
@ -164,12 +164,11 @@ def fill_in_document_table_attributes(docs, have_telechat_date=False):
)
)
for rel in xed_by:
d = doc_dict[rel.target.document.id]
s = rel_rfc_aliases[rel.source_id]
d = doc_dict[rel.target.id]
if rel.relationship_id == "obs":
d.obsoleted_by_list.append(s)
d.obsoleted_by_list.append(rel.source)
elif rel.relationship_id == "updates":
d.updated_by_list.append(s)
d.updated_by_list.append(rel.source)
def augment_docs_with_related_docs_info(docs):
"""Augment all documents with related documents information.
@ -217,7 +216,7 @@ def prepare_document_table(request, docs, query=None, max_results=200):
res = []
rfc_num = d.rfc_number()
rfc_num = num(d.rfc_number) if d.rfc_number else None
if d.type_id == "draft":
res.append(num(["Active", "Expired", "Replaced", "Withdrawn", "RFC"].index(d.search_heading.split()[0])))
@ -232,23 +231,23 @@ def prepare_document_table(request, docs, query=None, max_results=200):
elif sort_key == "date":
res.append(str(d.latest_revision_date.astimezone(ZoneInfo(settings.TIME_ZONE))))
elif sort_key == "status":
if rfc_num != None:
res.append(num(rfc_num))
if rfc_num is not None:
res.append(rfc_num)
else:
res.append(num(d.get_state().order) if d.get_state() else None)
elif sort_key == "ipr":
res.append(len(d.ipr()))
elif sort_key == "ad":
if rfc_num != None:
res.append(num(rfc_num))
if rfc_num is not None:
res.append(rfc_num)
elif d.get_state_slug() == "active":
if d.get_state("draft-iesg"):
res.append(d.get_state("draft-iesg").order)
else:
res.append(0)
else:
if rfc_num != None:
res.append(num(rfc_num))
if rfc_num is not None:
res.append(rfc_num)
else:
res.append(d.canonical_name())

View file

@ -953,7 +953,13 @@ def approve_downrefs(request, name):
login = request.user.person
downrefs_to_rfc = [rel for rel in doc.relateddocument_set.all() if rel.is_downref() and not rel.is_approved_downref() and rel.target.document.is_rfc()]
downrefs_to_rfc = [
rel
for rel in doc.relateddocument_set.all()
if rel.is_downref()
and not rel.is_approved_downref()
and rel.target.type_id == "rfc"
]
downrefs_to_rfc_qs = RelatedDocument.objects.filter(pk__in=[r.pk for r in downrefs_to_rfc])
@ -968,12 +974,12 @@ def approve_downrefs(request, name):
c = DocEvent(type="downref_approved", doc=rel.source,
rev=rel.source.rev, by=login)
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
rel.target.document.rfc_number(), rel.source, rel.source.rev)
rel.target.rfc_number, rel.source, rel.source.rev)
c.save()
c = DocEvent(type="downref_approved", doc=rel.target.document,
rev=rel.target.document.rev, by=login)
c = DocEvent(type="downref_approved", doc=rel.target,
rev=rel.target.rev, by=login)
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
rel.target.document.rfc_number(), rel.source, rel.source.rev)
rel.target.rfc_number, rel.source, rel.source.rev)
c.save()
return HttpResponseRedirect(doc.get_absolute_url())

View file

@ -3,11 +3,11 @@
import datetime
import io
import json
import os
import textwrap
from pathlib import Path
from django.http import HttpResponseRedirect, HttpResponseNotFound, Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse as urlreverse
@ -32,7 +32,7 @@ from ietf.doc.utils_charter import ( historic_milestones_for_charter,
generate_ballot_writeup, generate_issue_ballot_mail, next_revision,
derive_new_work_text,
change_group_state_after_charter_approval, fix_charter_revision_after_approval,
split_charter_name)
split_charter_name, charter_name_for_group)
from ietf.doc.mails import email_state_changed, email_charter_internal_review
from ietf.group.mails import email_admin_re_charter
from ietf.group.models import Group, ChangeStateGroupEvent, MilestoneGroupEvent
@ -42,6 +42,7 @@ from ietf.ietfauth.utils import has_role, role_required
from ietf.name.models import GroupStateName
from ietf.person.models import Person
from ietf.utils.history import find_history_active_at
from ietf.utils.log import assertion
from ietf.utils.mail import send_mail_preformatted
from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.utils.response import permission_denied
@ -362,38 +363,41 @@ class UploadForm(forms.Form):
@login_required
def submit(request, name, option=None):
if not name.startswith('charter-'):
raise Http404
# Charters are named "charter-<ietf|irtf>-<group acronym>"
charter = Document.objects.filter(type="charter", name=name).first()
if charter:
group = charter.group
charter_canonical_name = charter.canonical_name()
assertion("charter.name == charter_name_for_group(group)")
charter_rev = charter.rev
else:
top_org, group_acronym = split_charter_name(name)
group = get_object_or_404(Group, acronym=group_acronym)
charter_canonical_name = name
if name != charter_name_for_group(group):
raise Http404 # do not allow creation of misnamed charters
charter_rev = "00-00"
if not can_manage_all_groups_of_type(request.user, group.type_id) or not group.features.has_chartering_process:
if (
not can_manage_all_groups_of_type(request.user, group.type_id)
or not group.features.has_chartering_process
):
permission_denied(request, "You don't have permission to access this view.")
path = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter_canonical_name, charter_rev))
not_uploaded_yet = charter_rev.endswith("-00") and not os.path.exists(path)
charter_filename = Path(settings.CHARTER_PATH) / f"{name}-{charter_rev}.txt"
not_uploaded_yet = charter_rev.endswith("-00") and not charter_filename.exists()
if not_uploaded_yet or not charter:
# this case is special - we recently chartered or rechartered and have no file yet
next_rev = charter_rev
else:
# search history for possible collisions with abandoned efforts
prev_revs = list(charter.history_set.order_by('-time').values_list('rev', flat=True))
prev_revs = list(
charter.history_set.order_by("-time").values_list("rev", flat=True)
)
next_rev = next_revision(charter.rev)
while next_rev in prev_revs:
next_rev = next_revision(next_rev)
if request.method == 'POST':
if request.method == "POST":
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
# Also save group history so we can search for it
@ -408,9 +412,11 @@ def submit(request, name, option=None):
abstract=group.name,
rev=next_rev,
)
DocAlias.objects.create(name=charter.name).docs.add(charter)
DocAlias.objects.create(name=name).docs.add(charter)
charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
charter.set_state(
State.objects.get(used=True, type="charter", slug="notrev")
)
group.charter = charter
group.save()
@ -418,56 +424,74 @@ def submit(request, name, option=None):
charter.rev = next_rev
events = []
e = NewRevisionDocEvent(doc=charter, by=request.user.person, type="new_revision")
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), charter.rev)
e = NewRevisionDocEvent(
doc=charter, by=request.user.person, type="new_revision"
)
e.desc = "New version available: <b>%s-%s.txt</b>" % (
charter.name,
charter.rev,
)
e.rev = charter.rev
e.save()
events.append(e)
# Save file on disk
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter.canonical_name(), charter.rev))
with io.open(filename, 'w', encoding='utf-8') as destination:
if form.cleaned_data['txt']:
destination.write(form.cleaned_data['txt'])
charter_filename = charter_filename.with_name(
f"{name}-{charter.rev}.txt"
) # update rev
with charter_filename.open("w", encoding="utf-8") as destination:
if form.cleaned_data["txt"]:
destination.write(form.cleaned_data["txt"])
else:
destination.write(form.cleaned_data['content'])
destination.write(form.cleaned_data["content"])
if option in ['initcharter','recharter'] and charter.ad == None:
charter.ad = getattr(group.ad_role(),'person',None)
if option in ["initcharter", "recharter"] and charter.ad == None:
charter.ad = getattr(group.ad_role(), "person", None)
charter.save_with_history(events)
if option:
return redirect('ietf.doc.views_charter.change_state', name=charter.name, option=option)
return redirect(
"ietf.doc.views_charter.change_state",
name=charter.name,
option=option,
)
else:
return redirect("ietf.doc.views_doc.document_main", name=charter.name)
else:
init = { "content": "" }
init = {"content": ""}
if not_uploaded_yet and charter:
# use text from last approved revision
last_approved = charter.rev.split("-")[0]
h = charter.history_set.filter(rev=last_approved).order_by("-time", "-id").first()
h = (
charter.history_set.filter(rev=last_approved)
.order_by("-time", "-id")
.first()
)
if h:
charter_canonical_name = h.canonical_name()
charter_rev = h.rev
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter_canonical_name, charter_rev))
assertion("h.name == charter_name_for_group(group)")
charter_filename = charter_filename.with_name(
f"{name}-{h.rev}.txt"
) # update rev
try:
with io.open(filename, 'r') as f:
init["content"] = f.read()
init["content"] = charter_filename.read_text()
except IOError:
pass
form = UploadForm(initial=init)
fill_in_charter_info(group)
return render(request, 'doc/charter/submit.html', {
'form': form,
'next_rev': next_rev,
'group': group,
'name': name,
})
return render(
request,
"doc/charter/submit.html",
{
"form": form,
"next_rev": next_rev,
"group": group,
"name": name,
},
)
class ActionAnnouncementTextForm(forms.Form):
announcement_text = forms.CharField(widget=forms.Textarea, required=True, strip=False)
@ -484,7 +508,7 @@ class ReviewAnnouncementTextForm(forms.Form):
return self.cleaned_data["announcement_text"].replace("\r", "")
@role_required('Area Director','Secretariat')
@role_required("Area Director", "Secretariat")
def review_announcement_text(request, name):
"""Editing of review announcement text"""
charter = get_object_or_404(Document, type="charter", name=name)
@ -493,7 +517,9 @@ def review_announcement_text(request, name):
by = request.user.person
existing = charter.latest_event(WriteupDocEvent, type="changed_review_announcement")
existing_new_work = charter.latest_event(WriteupDocEvent, type="changed_new_work_text")
existing_new_work = charter.latest_event(
WriteupDocEvent, type="changed_new_work_text"
)
if not existing:
(existing, existing_new_work) = default_review_text(group, charter, by)
@ -506,19 +532,23 @@ def review_announcement_text(request, name):
existing_new_work.by = by
existing_new_work.type = "changed_new_work_text"
existing_new_work.desc = "%s review text was changed" % group.type.name
existing_new_work.text = derive_new_work_text(existing.text,group)
existing_new_work.text = derive_new_work_text(existing.text, group)
existing_new_work.time = timezone.now()
form = ReviewAnnouncementTextForm(initial=dict(announcement_text=escape(existing.text),new_work_text=escape(existing_new_work.text)))
form = ReviewAnnouncementTextForm(
initial=dict(
announcement_text=escape(existing.text),
new_work_text=escape(existing_new_work.text),
)
)
if request.method == 'POST':
if request.method == "POST":
form = ReviewAnnouncementTextForm(request.POST)
if "save_text" in request.POST and form.is_valid():
now = timezone.now()
events = []
t = form.cleaned_data['announcement_text']
t = form.cleaned_data["announcement_text"]
if t != existing.text:
e = WriteupDocEvent(doc=charter, rev=charter.rev)
e.by = by
@ -532,11 +562,11 @@ def review_announcement_text(request, name):
existing.save()
events.append(existing)
t = form.cleaned_data['new_work_text']
t = form.cleaned_data["new_work_text"]
if t != existing_new_work.text:
e = WriteupDocEvent(doc=charter, rev=charter.rev)
e.by = by
e.type = "changed_new_work_text"
e.type = "changed_new_work_text"
e.desc = "%s new work message text was changed" % (group.type.name)
e.text = t
e.time = now
@ -549,33 +579,71 @@ def review_announcement_text(request, name):
charter.save_with_history(events)
if request.GET.get("next", "") == "approve":
return redirect('ietf.doc.views_charter.approve', name=charter.canonical_name())
return redirect(
"ietf.doc.views_charter.approve", name=charter.name
)
return redirect('ietf.doc.views_doc.document_writeup', name=charter.canonical_name())
return redirect(
"ietf.doc.views_doc.document_writeup", name=charter.name
)
if "regenerate_text" in request.POST:
(existing, existing_new_work) = default_review_text(group, charter, by)
existing.save()
existing_new_work.save()
form = ReviewAnnouncementTextForm(initial=dict(announcement_text=escape(existing.text),
new_work_text=escape(existing_new_work.text)))
form = ReviewAnnouncementTextForm(
initial=dict(
announcement_text=escape(existing.text),
new_work_text=escape(existing_new_work.text),
)
)
if any(x in request.POST for x in ['send_annc_only','send_nw_only','send_both']) and form.is_valid():
if any(x in request.POST for x in ['send_annc_only','send_both']):
parsed_msg = send_mail_preformatted(request, form.cleaned_data['announcement_text'])
messages.success(request, "The email To: '%s' with Subject: '%s' has been sent." % (parsed_msg["To"],parsed_msg["Subject"],))
if any(x in request.POST for x in ['send_nw_only','send_both']):
parsed_msg = send_mail_preformatted(request, form.cleaned_data['new_work_text'])
messages.success(request, "The email To: '%s' with Subject: '%s' has been sent." % (parsed_msg["To"],parsed_msg["Subject"],))
return redirect('ietf.doc.views_doc.document_writeup', name=charter.name)
if (
any(
x in request.POST
for x in ["send_annc_only", "send_nw_only", "send_both"]
)
and form.is_valid()
):
if any(x in request.POST for x in ["send_annc_only", "send_both"]):
parsed_msg = send_mail_preformatted(
request, form.cleaned_data["announcement_text"]
)
messages.success(
request,
"The email To: '%s' with Subject: '%s' has been sent."
% (
parsed_msg["To"],
parsed_msg["Subject"],
),
)
if any(x in request.POST for x in ["send_nw_only", "send_both"]):
parsed_msg = send_mail_preformatted(
request, form.cleaned_data["new_work_text"]
)
messages.success(
request,
"The email To: '%s' with Subject: '%s' has been sent."
% (
parsed_msg["To"],
parsed_msg["Subject"],
),
)
return redirect("ietf.doc.views_doc.document_writeup", name=charter.name)
return render(request, 'doc/charter/review_announcement_text.html',
dict(charter=charter,
back_url=urlreverse('ietf.doc.views_doc.document_writeup', kwargs=dict(name=charter.name)),
announcement_text_form=form,
))
return render(
request,
"doc/charter/review_announcement_text.html",
dict(
charter=charter,
back_url=urlreverse(
"ietf.doc.views_doc.document_writeup", kwargs=dict(name=charter.name)
),
announcement_text_form=form,
),
)
@role_required('Area Director','Secretariat')
@role_required("Area Director", "Secretariat")
def action_announcement_text(request, name):
"""Editing of action announcement text"""
charter = get_object_or_404(Document, type="charter", name=name)
@ -590,16 +658,18 @@ def action_announcement_text(request, name):
if not existing:
raise Http404
form = ActionAnnouncementTextForm(initial=dict(announcement_text=escape(existing.text)))
form = ActionAnnouncementTextForm(
initial=dict(announcement_text=escape(existing.text))
)
if request.method == 'POST':
if request.method == "POST":
form = ActionAnnouncementTextForm(request.POST)
if "save_text" in request.POST and form.is_valid():
t = form.cleaned_data['announcement_text']
t = form.cleaned_data["announcement_text"]
if t != existing.text:
e = WriteupDocEvent(doc=charter, rev=charter.rev)
e.by = by
e.type = "changed_action_announcement"
e.type = "changed_action_announcement"
e.desc = "%s action text was changed" % group.type.name
e.text = t
e.save()
@ -607,25 +677,46 @@ def action_announcement_text(request, name):
existing.save()
if request.GET.get("next", "") == "approve":
return redirect('ietf.doc.views_charter.approve', name=charter.canonical_name())
return redirect(
"ietf.doc.views_charter.approve", name=charter.name
)
return redirect('ietf.doc.views_doc.document_writeup', name=charter.canonical_name())
return redirect(
"ietf.doc.views_doc.document_writeup", name=charter.name
)
if "regenerate_text" in request.POST:
e = default_action_text(group, charter, by)
e.save()
form = ActionAnnouncementTextForm(initial=dict(announcement_text=escape(e.text)))
form = ActionAnnouncementTextForm(
initial=dict(announcement_text=escape(e.text))
)
if "send_text" in request.POST and form.is_valid():
parsed_msg = send_mail_preformatted(request, form.cleaned_data['announcement_text'])
messages.success(request, "The email To: '%s' with Subject: '%s' has been sent." % (parsed_msg["To"],parsed_msg["Subject"],))
return redirect('ietf.doc.views_doc.document_writeup', name=charter.name)
parsed_msg = send_mail_preformatted(
request, form.cleaned_data["announcement_text"]
)
messages.success(
request,
"The email To: '%s' with Subject: '%s' has been sent."
% (
parsed_msg["To"],
parsed_msg["Subject"],
),
)
return redirect("ietf.doc.views_doc.document_writeup", name=charter.name)
return render(request, 'doc/charter/action_announcement_text.html',
dict(charter=charter,
back_url=urlreverse('ietf.doc.views_doc.document_writeup', kwargs=dict(name=charter.name)),
announcement_text_form=form,
))
return render(
request,
"doc/charter/action_announcement_text.html",
dict(
charter=charter,
back_url=urlreverse(
"ietf.doc.views_doc.document_writeup", kwargs=dict(name=charter.name)
),
announcement_text_form=form,
),
)
class BallotWriteupForm(forms.Form):
ballot_writeup = forms.CharField(widget=forms.Textarea, required=True, strip=False)
@ -806,33 +897,37 @@ def approve(request, name):
dict(charter=charter,
announcement=escape(announcement)))
def charter_with_milestones_txt(request, name, rev):
charter = get_object_or_404(Document, type="charter", docalias__name=name)
revision_event = charter.latest_event(NewRevisionDocEvent, type="new_revision", rev=rev)
def charter_with_milestones_txt(request, name, rev):
charter = get_object_or_404(Document, type="charter", name=name)
revision_event = charter.latest_event(
NewRevisionDocEvent, type="new_revision", rev=rev
)
if not revision_event:
return HttpResponseNotFound("Revision %s not found in database" % rev)
# read charter text
c = find_history_active_at(charter, revision_event.time) or charter
filename = '%s-%s.txt' % (c.canonical_name(), rev)
charter_text = ""
filename = Path(settings.CHARTER_PATH) / f"{c.name}-{rev}.txt"
try:
with io.open(os.path.join(settings.CHARTER_PATH, filename), 'r') as f:
charter_text = force_str(f.read(), errors='ignore')
with filename.open() as f:
charter_text = force_str(f.read(), errors="ignore")
except IOError:
charter_text = "Error reading charter text %s" % filename
charter_text = f"Error reading charter text {filename.name}"
milestones = historic_milestones_for_charter(charter, rev)
# wrap the output nicely
wrapper = textwrap.TextWrapper(initial_indent="", subsequent_indent=" " * 11, width=80, break_long_words=False)
wrapper = textwrap.TextWrapper(
initial_indent="", subsequent_indent=" " * 11, width=80, break_long_words=False
)
for m in milestones:
m.desc_filled = wrapper.fill(m.desc)
return render(request, 'doc/charter/charter_with_milestones.txt',
dict(charter_text=charter_text,
milestones=milestones),
content_type="text/plain; charset=%s"%settings.DEFAULT_CHARSET)
return render(
request,
"doc/charter/charter_with_milestones.txt",
dict(charter_text=charter_text, milestones=milestones),
content_type="text/plain; charset=%s" % settings.DEFAULT_CHARSET,
)

View file

@ -113,7 +113,7 @@ def send_conflict_review_ad_changed_email(request, review, event):
by = request.user.person,
event = event,
review = review,
reviewed_doc = review.relateddocument_set.get(relationship__slug='conflrev').target.document,
reviewed_doc = review.relateddocument_set.get(relationship__slug='conflrev').target,
review_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(),
)
)
@ -128,7 +128,7 @@ def send_conflict_review_started_email(request, review):
cc = addrs.cc,
by = request.user.person,
review = review,
reviewed_doc = review.relateddocument_set.get(relationship__slug='conflrev').target.document,
reviewed_doc = review.relateddocument_set.get(relationship__slug='conflrev').target,
review_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(),
)
)
@ -137,7 +137,7 @@ def send_conflict_review_started_email(request, review):
addrs = gather_address_lists('conflrev_requested_iana',doc=review).as_strings(compact=False)
email_iana(request,
review.relateddocument_set.get(relationship__slug='conflrev').target.document,
review.relateddocument_set.get(relationship__slug='conflrev').target,
addrs.to,
msg,
cc=addrs.cc)
@ -155,7 +155,7 @@ def send_conflict_eval_email(request,review):
send_mail_preformatted(request,msg,override=override)
addrs = gather_address_lists('ballot_issued_iana',doc=review).as_strings()
email_iana(request,
review.relateddocument_set.get(relationship__slug='conflrev').target.document,
review.relateddocument_set.get(relationship__slug='conflrev').target,
addrs.to,
msg,
addrs.cc)
@ -247,7 +247,7 @@ def submit(request, name):
{'form': form,
'next_rev': next_rev,
'review' : review,
'conflictdoc' : review.relateddocument_set.get(relationship__slug='conflrev').target.document,
'conflictdoc' : review.relateddocument_set.get(relationship__slug='conflrev').target,
})
@role_required("Area Director", "Secretariat")
@ -275,7 +275,7 @@ def edit_ad(request, name):
form = AdForm(initial=init)
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target
titletext = 'the conflict review of %s-%s' % (conflictdoc.canonical_name(),conflictdoc.rev)
return render(request, 'doc/change_ad.html',
{'form': form,
@ -287,7 +287,7 @@ def edit_ad(request, name):
def default_approval_text(review):
current_text = review.text_or_error() # pyflakes:ignore
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target
if conflictdoc.stream_id=='ise':
receiver = 'Independent Submissions Editor'
elif conflictdoc.stream_id=='irtf':
@ -365,7 +365,7 @@ def approve_conflict_review(request, name):
return render(request, 'doc/conflict_review/approve.html',
dict(
review = review,
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document,
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target,
form = form,
))
@ -416,7 +416,7 @@ def start_review_sanity_check(request, name):
raise Http404
# sanity check that there's not already a conflict review document for this document
if [ rel.source for alias in doc_to_review.docalias.all() for rel in alias.relateddocument_set.filter(relationship='conflrev') ]:
if [ rel.source for rel in doc_to_review.targets_related.filter(relationship='conflrev') ]:
raise Http404
return doc_to_review
@ -452,7 +452,7 @@ def build_conflict_review_document(login, doc_to_review, ad, notify, create_in_s
DocAlias.objects.create( name=review_name).docs.add( conflict_review )
conflict_review.relateddocument_set.create(target=DocAlias.objects.get(name=doc_to_review.name),relationship_id='conflrev')
conflict_review.relateddocument_set.create(target=doc_to_review, relationship_id='conflrev')
c = DocEvent(type="added_comment", doc=conflict_review, rev=conflict_review.rev, by=login)
c.desc = "IETF conflict review requested"

View file

@ -54,13 +54,13 @@ from django.contrib.staticfiles import finders
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDocEvent, BallotType,
from ietf.doc.models import ( Document, DocHistory, DocEvent, BallotDocEvent, BallotType,
ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent, IanaExpertDocEvent,
IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS, DocumentActionHolder, DocumentAuthor,
RelatedDocument, RelatedDocHistory)
from ietf.doc.utils import (augment_events_with_revision,
can_adopt_draft, can_unadopt_draft, get_chartering_type, get_tags_for_stream_id,
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
needed_ballot_positions, nice_consensus, update_telechat, has_same_ballot,
get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
add_events_message_info, get_unicode_document_content,
augment_docs_and_user_with_user_info, irsg_needed_ballot_positions, add_action_holder_change_event,
@ -180,13 +180,12 @@ def interesting_doc_relations(doc):
else:
raise TypeError("Expected this method to be called with a Document or DocHistory object")
that_relationships = STATUSCHANGE_RELATIONS + ('conflrev', 'replaces', 'possibly_replaces', 'updates', 'obs')
that_relationships = STATUSCHANGE_RELATIONS + ('conflrev', 'replaces', 'possibly_replaces', 'updates', 'obs', 'became_rfc')
that_doc_relationships = ('replaces', 'possibly_replaces', 'updates', 'obs')
that_doc_relationships = ('replaces', 'possibly_replaces', 'updates', 'obs', 'became_rfc')
# TODO: This returns the relationships in database order, which may not be the order we want to display them in.
interesting_relations_that = cls.objects.filter(target__docs=target, relationship__in=that_relationships).select_related('source')
interesting_relations_that_doc = cls.objects.filter(source=doc, relationship__in=that_doc_relationships).prefetch_related('target__docs')
interesting_relations_that = cls.objects.filter(target=target, relationship__in=that_relationships).select_related('source')
interesting_relations_that_doc = cls.objects.filter(source=doc, relationship__in=that_doc_relationships).prefetch_related('target')
return interesting_relations_that, interesting_relations_that_doc
@ -197,11 +196,10 @@ def document_main(request, name, rev=None, document_html=False):
doc = get_object_or_404(Document.objects.select_related(), docalias__name=name)
# take care of possible redirections
aliases = DocAlias.objects.filter(docs=doc).values_list("name", flat=True)
if document_html is False and rev==None and doc.type_id == "draft" and not name.startswith("rfc"):
for a in aliases:
if a.startswith("rfc"):
return redirect("ietf.doc.views_doc.document_main", name=a)
if document_html is False and rev is None:
became_rfc = next(iter(doc.related_that_doc("became_rfc")), None)
if became_rfc:
return redirect("ietf.doc.views_doc.document_main", name=became_rfc.name)
revisions = []
for h in doc.history_set.order_by("time", "id"):
@ -243,7 +241,163 @@ def document_main(request, name, rev=None, document_html=False):
# specific document types
if doc.type_id == "draft":
if doc.type_id == "rfc":
split_content = request.COOKIES.get("full_draft", settings.USER_PREFERENCE_DEFAULTS["full_draft"]) == "off"
if request.GET.get('include_text') == "0":
split_content = True
elif request.GET.get('include_text') == "1":
split_content = False
else:
pass
interesting_relations_that, interesting_relations_that_doc = interesting_doc_relations(doc)
can_edit = has_role(request.user, ("Area Director", "Secretariat"))
can_edit_authors = has_role(request.user, ("Secretariat"))
stream_slugs = StreamName.objects.values_list("slug", flat=True)
# For some reason, AnonymousUser has __iter__, but is not iterable,
# which causes problems in the filter() below. Work around this:
if request.user.is_authenticated:
roles = Role.objects.filter(group__acronym__in=stream_slugs, person__user=request.user)
roles = group_features_role_filter(roles, request.user.person, 'docman_roles')
else:
roles = []
can_change_stream = bool(can_edit or roles)
file_urls, found_types = build_file_urls(doc)
content = doc.text_or_error() # pyflakes:ignore
content = markup_txt.markup(maybe_split(content, split=split_content))
if not found_types:
content = "This RFC is not currently available online."
split_content = False
elif "txt" not in found_types:
content = "This RFC is not available in plain text format."
split_content = False
# mailing list search archive
search_archive = "www.ietf.org/mail-archive/web/"
if doc.stream_id == "ietf" and group.type_id == "wg" and group.list_archive:
search_archive = group.list_archive
search_archive = quote(search_archive, safe="~")
# status changes
status_changes = []
proposed_status_changes = []
for r in interesting_relations_that.filter(relationship__in=STATUSCHANGE_RELATIONS):
state_slug = r.source.get_state_slug()
if state_slug in ('appr-sent', 'appr-pend'):
status_changes.append(r)
elif state_slug in ('needshep','adrev','iesgeval','defer','appr-pr'):
proposed_status_changes.append(r)
else:
pass
presentations = doc.future_presentations()
augment_docs_and_user_with_user_info([doc], request.user)
exp_comment = doc.latest_event(IanaExpertDocEvent,type="comment")
iana_experts_comment = exp_comment and exp_comment.desc
# Do not show the Auth48 URL in the "Additional URLs" section
additional_urls = doc.documenturl_set.exclude(tag_id='auth48')
html = None
js = None
css = None
diff_revisions = None
simple_diff_revisions = None
if document_html:
diff_revisions=get_diff_revisions(request, name, doc if isinstance(doc,Document) else doc.doc)
simple_diff_revisions = [t[1] for t in diff_revisions if t[0] == doc.name]
simple_diff_revisions.reverse()
if rev and rev != doc.rev:
# No DocHistory was found matching rev - snapshot will be false
# and doc will be a Document object, not a DocHistory
snapshot = True
doc = doc.fake_history_obj(rev)
else:
html = doc.html_body()
if request.COOKIES.get("pagedeps") == "inline":
js = Path(finders.find("ietf/js/document_html.js")).read_text()
css = Path(finders.find("ietf/css/document_html_inline.css")).read_text()
if html:
css += Path(finders.find("ietf/css/document_html_txt.css")).read_text()
draft_that_became_rfc = None
became_rfc_alias = next(iter(doc.related_that("became_rfc")), None)
if became_rfc_alias:
draft_that_became_rfc = became_rfc_alias.document
# submission
submission = ""
if group is None:
submission = "unknown"
elif group.type_id == "individ":
submission = "individual"
elif group.type_id == "area" and doc.stream_id == "ietf":
submission = "individual in %s area" % group.acronym
else:
if group.features.acts_like_wg and not group.type_id == "edwg":
submission = "%s %s" % (group.acronym, group.type)
else:
submission = group.acronym
submission = '<a href="%s">%s</a>' % (group.about_url(), submission)
# Should be unreachable?
if (
draft_that_became_rfc
and draft_that_became_rfc.stream_id
and draft_that_became_rfc.get_state_slug(
"draft-stream-%s" % draft_that_became_rfc.stream_id
)
== "c-adopt"
):
submission = "candidate for %s" % submission
# todo replace document_html?
return render(request, "doc/document_rfc.html" if document_html is False else "doc/document_html.html",
dict(doc=doc,
document_html=document_html,
css=css,
js=js,
html=html,
group=group,
top=top,
name=doc.name,
content=content,
split_content=split_content,
revisions=simple_diff_revisions if document_html else revisions,
snapshot=snapshot,
latest_rev=latest_rev,
can_edit=can_edit,
can_edit_authors=can_edit_authors,
can_change_stream=can_change_stream,
rfc_number=doc.rfc_number,
draft_name=draft_that_became_rfc and draft_that_became_rfc.name,
updates=interesting_relations_that_doc.filter(relationship="updates"),
updated_by=interesting_relations_that.filter(relationship="updates"),
obsoletes=interesting_relations_that_doc.filter(relationship="obs"),
obsoleted_by=interesting_relations_that.filter(relationship="obs"),
status_changes=status_changes,
proposed_status_changes=proposed_status_changes,
has_errata=doc.pk and doc.tags.filter(slug="errata"), # doc.pk == None if using a fake_history_obj
file_urls=file_urls,
additional_urls=additional_urls,
rfc_editor_state=doc.get_state("draft-rfceditor"),
iana_review_state=doc.get_state("draft-iana-review"),
iana_action_state=doc.get_state("draft-iana-action"),
iana_experts_state=doc.get_state("draft-iana-experts"),
iana_experts_comment=iana_experts_comment,
search_archive=search_archive,
presentations=presentations,
diff_revisions=diff_revisions,
submission=submission
))
elif doc.type_id == "draft":
split_content = request.COOKIES.get("full_draft", settings.USER_PREFERENCE_DEFAULTS["full_draft"]) == "off"
if request.GET.get('include_text') == "0":
split_content = True
@ -281,43 +435,13 @@ def document_main(request, name, rev=None, document_html=False):
is_author = request.user.is_authenticated and doc.documentauthor_set.filter(person__user=request.user).exists()
can_view_possibly_replaces = can_edit_replaces or is_author
rfc_number = name[3:] if name.startswith("rfc") else None
draft_name = None
for a in aliases:
if a.startswith("draft"):
draft_name = a
rfc_aliases = [prettify_std_name(a) for a in aliases
if a.startswith("fyi") or a.startswith("std") or a.startswith("bcp")]
latest_revision = None
# Workaround to allow displaying last rev of draft that became rfc as a draft
# This should be unwound when RFCs become their own documents.
if snapshot:
doc.name = doc.doc.name
name = doc.doc.name
else:
name = doc.name
file_urls, found_types = build_file_urls(doc)
if not snapshot and doc.get_state_slug() == "rfc":
# content
content = doc.text_or_error() # pyflakes:ignore
content = markup_txt.markup(maybe_split(content, split=split_content))
content = doc.text_or_error() # pyflakes:ignore
content = markup_txt.markup(maybe_split(content, split=split_content))
if not snapshot and doc.get_state_slug() == "rfc":
if not found_types:
content = "This RFC is not currently available online."
split_content = False
elif "txt" not in found_types:
content = "This RFC is not available in plain text format."
split_content = False
else:
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
# ballot
iesg_ballot_summary = None
@ -497,7 +621,7 @@ def document_main(request, name, rev=None, document_html=False):
augment_docs_and_user_with_user_info([doc], request.user)
published = doc.latest_event(type="published_rfc")
published = doc.latest_event(type="published_rfc") # todo rethink this now that published_rfc is on rfc
started_iesg_process = doc.latest_event(type="started_iesg_process")
review_assignments = review_assignments_to_list_for_docs([doc]).get(doc.name, [])
@ -516,12 +640,6 @@ def document_main(request, name, rev=None, document_html=False):
# Do not show the Auth48 URL in the "Additional URLs" section
additional_urls = doc.documenturl_set.exclude(tag_id='auth48')
# Stream description passing test
if doc.stream != None:
stream_desc = doc.stream.desc
else:
stream_desc = "(None)"
html = None
js = None
css = None
@ -552,12 +670,11 @@ def document_main(request, name, rev=None, document_html=False):
html=html,
group=group,
top=top,
name=name,
name=doc.name,
content=content,
split_content=split_content,
revisions=simple_diff_revisions if document_html else revisions,
snapshot=snapshot,
stream_desc=stream_desc,
latest_revision=latest_revision,
latest_rev=latest_rev,
can_edit=can_edit,
@ -575,8 +692,7 @@ def document_main(request, name, rev=None, document_html=False):
can_request_review=can_request_review,
can_submit_unsolicited_review_for_teams=can_submit_unsolicited_review_for_teams,
rfc_number=rfc_number,
draft_name=draft_name,
draft_name=doc.name,
telechat=telechat,
iesg_ballot_summary=iesg_ballot_summary,
submission=submission,
@ -593,7 +709,6 @@ def document_main(request, name, rev=None, document_html=False):
conflict_reviews=conflict_reviews,
status_changes=status_changes,
proposed_status_changes=proposed_status_changes,
rfc_aliases=rfc_aliases,
has_errata=doc.pk and doc.tags.filter(slug="errata"), # doc.pk == None if using a fake_history_obj
published=published,
file_urls=file_urls,
@ -623,7 +738,7 @@ def document_main(request, name, rev=None, document_html=False):
diff_revisions=diff_revisions
))
if doc.type_id == "charter":
elif doc.type_id == "charter":
content = doc.text_or_error() # pyflakes:ignore
content = markup_txt.markup(content)
@ -660,7 +775,7 @@ def document_main(request, name, rev=None, document_html=False):
can_manage=can_manage,
))
if doc.type_id == "bofreq":
elif doc.type_id == "bofreq":
content = markdown.markdown(doc.text_or_error())
editors = bofreq_editors(doc)
responsible = bofreq_responsible(doc)
@ -680,7 +795,7 @@ def document_main(request, name, rev=None, document_html=False):
editor_can_manage=editor_can_manage,
))
if doc.type_id == "conflrev":
elif doc.type_id == "conflrev":
filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev)
pathname = os.path.join(settings.CONFLICT_REVIEW_PATH,filename)
@ -695,7 +810,7 @@ def document_main(request, name, rev=None, document_html=False):
if doc.get_state_slug() in ("iesgeval", ) and doc.active_ballot():
ballot_summary = needed_ballot_positions(doc, list(doc.active_ballot().active_balloter_positions().values()))
conflictdoc = doc.related_that_doc('conflrev')[0].document
conflictdoc = doc.related_that_doc('conflrev')[0]
return render(request, "doc/document_conflict_review.html",
dict(doc=doc,
@ -710,7 +825,7 @@ def document_main(request, name, rev=None, document_html=False):
approved_states=('appr-reqnopub-pend','appr-reqnopub-sent','appr-noprob-pend','appr-noprob-sent'),
))
if doc.type_id == "statchg":
elif doc.type_id == "statchg":
filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev)
pathname = os.path.join(settings.STATUS_CHANGE_PATH,filename)
@ -744,7 +859,7 @@ def document_main(request, name, rev=None, document_html=False):
sorted_relations=sorted_relations,
))
if doc.type_id in ("slides", "agenda", "minutes", "bluesheets", "procmaterials",):
elif doc.type_id in ("slides", "agenda", "minutes", "bluesheets", "procmaterials",):
can_manage_material = can_manage_materials(request.user, doc.group)
presentations = doc.future_presentations()
if doc.uploaded_filename:
@ -800,7 +915,7 @@ def document_main(request, name, rev=None, document_html=False):
))
if doc.type_id == "review":
elif doc.type_id == "review":
basename = "{}.txt".format(doc.name)
pathname = os.path.join(doc.get_file_path(), basename)
content = get_unicode_document_content(basename, pathname)
@ -826,7 +941,7 @@ def document_main(request, name, rev=None, document_html=False):
assignments=assignments,
))
if doc.type_id in ("chatlog", "polls"):
elif doc.type_id in ("chatlog", "polls"):
if isinstance(doc,DocHistory):
session = doc.doc.sessionpresentation_set.last().session
else:
@ -934,7 +1049,7 @@ def document_html(request, name, rev=None):
doc = found.documents.get()
rev = found.matched_rev
if not requested_rev and doc.is_rfc(): # Someone asked for /doc/html/8989
if not requested_rev and doc.type_id == "rfc": # Someone asked for /doc/html/8989
if not name.startswith('rfc'):
return redirect('ietf.doc.views_doc.document_html', name=doc.canonical_name())
@ -944,7 +1059,12 @@ def document_html(request, name, rev=None):
if not os.path.exists(doc.get_file_name()):
raise Http404("File not found: %s" % doc.get_file_name())
return document_main(request, name=doc.name if requested_rev else doc.canonical_name(), rev=doc.rev if requested_rev or not doc.is_rfc() else None, document_html=True)
return document_main(
request,
name=doc.name if requested_rev else doc.canonical_name(),
rev=doc.rev if requested_rev or doc.type_id != "rfc" else None,
document_html=True,
)
def document_pdfized(request, name, rev=None, ext=None):
@ -1044,8 +1164,8 @@ def get_diff_revisions(request, name, doc):
diff_documents = [doc]
diff_documents.extend(
Document.objects.filter(
docalias__relateddocument__source=doc,
docalias__relateddocument__relationship="replaces",
relateddocument__source=doc,
relateddocument__relationship="replaces",
)
)
@ -1100,21 +1220,46 @@ def document_history(request, name):
add_events_message_info(events)
# figure out if the current user can add a comment to the history
if doc.type_id == "draft" and doc.group != None:
can_add_comment = bool(has_role(request.user, ("Area Director", "Secretariat", "IRTF Chair", "IANA", "RFC Editor")) or (
request.user.is_authenticated and
Role.objects.filter(name__in=("chair", "secr"),
group__acronym=doc.group.acronym,
person__user=request.user)))
if doc.type_id in ("draft", "rfc") and doc.group is not None:
can_add_comment = bool(
has_role(
request.user,
("Area Director", "Secretariat", "IRTF Chair", "IANA", "RFC Editor"),
)
or (
request.user.is_authenticated
and Role.objects.filter(
name__in=("chair", "secr"),
group__acronym=doc.group.acronym,
person__user=request.user,
)
)
)
else:
can_add_comment = has_role(request.user, ("Area Director", "Secretariat", "IRTF Chair"))
return render(request, "doc/document_history.html",
dict(doc=doc,
top=top,
diff_revisions=diff_revisions,
events=events,
can_add_comment=can_add_comment,
))
can_add_comment = has_role(
request.user, ("Area Director", "Secretariat", "IRTF Chair")
)
# Get related docs whose history should be linked
if doc.type_id == "draft":
related = doc.related_that_doc("became_rfc")
elif doc.type_id == "rfc":
related = doc.related_that("became_rfc")
else:
related = []
return render(
request,
"doc/document_history.html",
{
"doc": doc,
"top": top,
"diff_revisions": diff_revisions,
"events": events,
"related": related,
"can_add_comment": can_add_comment,
},
)
def document_bibtex(request, name, rev=None):
@ -1133,25 +1278,30 @@ def document_bibtex(request, name, rev=None):
doc = get_object_or_404(Document, docalias__name=name)
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
replaced_by = [d.name for d in doc.related_that("replaces")]
published = doc.latest_event(type="published_rfc") is not None
rfc = latest_revision.doc if latest_revision and latest_revision.doc.get_state_slug() == "rfc" else None
doi = None
draft_became_rfc = None
replaced_by = None
latest_revision = None
if doc.type_id == "draft":
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
replaced_by = [d.name for d in doc.related_that("replaces")]
draft_became_rfc_alias = next(iter(doc.related_that_doc("became_rfc")), None)
if rev != None and rev != doc.rev:
# find the entry in the history
for h in doc.history_set.order_by("-time"):
if rev == h.rev:
doc = h
break
if doc.is_rfc():
if rev != None and rev != doc.rev:
# find the entry in the history
for h in doc.history_set.order_by("-time"):
if rev == h.rev:
doc = h
break
if draft_became_rfc_alias:
draft_became_rfc = draft_became_rfc_alias.document
elif doc.type_id == "rfc":
# This needs to be replaced with a lookup, as the mapping may change
# over time. Probably by updating ietf/sync/rfceditor.py to add the
# as a DocAlias, and use a method on Document to retrieve it.
doi = "10.17487/RFC%04d" % int(doc.rfc_number())
else:
doi = None
doi = f"10.17487/RFC{doc.rfc_number:04d}"
if doc.is_dochistory():
latest_event = doc.latest_event(type='new_revision', rev=rev)
@ -1161,8 +1311,7 @@ def document_bibtex(request, name, rev=None):
return render(request, "doc/document_bibtex.bib",
dict(doc=doc,
replaced_by=replaced_by,
published=published,
rfc=rfc,
published_as=draft_became_rfc,
latest_revision=latest_revision,
doi=doi,
),
@ -1618,7 +1767,7 @@ def telechat_date(request, name):
def doc_titletext(doc):
if doc.type.slug=='conflrev':
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target
return 'the conflict review of %s' % conflictdoc.canonical_name()
return doc.canonical_name()
@ -2025,9 +2174,16 @@ def idnits2_rfc_status(request):
def idnits2_state(request, name, rev=None):
doc = get_object_or_404(Document, docalias__name=name)
if doc.type_id!='draft':
if doc.type_id not in ["draft", "rfc"]:
raise Http404
zero_revision = NewRevisionDocEvent.objects.filter(doc=doc,rev='00').first()
zero_revision = None
if doc.type_id == "rfc":
draft_alias = next(iter(doc.related_that('became_rfc')), None)
if draft_alias:
draft = draft_alias.document
zero_revision = NewRevisionDocEvent.objects.filter(doc=draft,rev='00').first()
else:
zero_revision = NewRevisionDocEvent.objects.filter(doc=doc,rev='00').first()
if zero_revision:
doc.created = zero_revision.time
else:

View file

@ -19,7 +19,7 @@ def downref_registry(request):
downref_doc_pairs = [ ]
downref_relations = RelatedDocument.objects.filter(relationship_id='downref-approval')
for rel in downref_relations:
downref_doc_pairs.append((rel.target.document, rel.source))
downref_doc_pairs.append((rel.target, rel.source))
return render(request, 'doc/downref.html', {
"doc_pairs": downref_doc_pairs,
@ -38,18 +38,18 @@ def downref_registry_add(request):
if form.is_valid():
drafts = form.cleaned_data['drafts']
rfc = form.cleaned_data['rfc']
for da in drafts:
RelatedDocument.objects.create(source=da.document,
for d in drafts:
RelatedDocument.objects.create(source=d,
target=rfc, relationship_id='downref-approval')
c = DocEvent(type="downref_approved", doc=da.document,
rev=da.document.rev, by=login)
c = DocEvent(type="downref_approved", doc=d,
rev=d.rev, by=login)
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
rfc.document.rfc_number(), da.name, da.document.rev)
rfc.rfc_number, d.name, d.rev)
c.save()
c = DocEvent(type="downref_approved", doc=rfc.document,
rev=rfc.document.rev, by=login)
c = DocEvent(type="downref_approved", doc=rfc,
rev=rfc.rev, by=login)
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
rfc.document.rfc_number(), da.name, da.document.rev)
rfc.rfc_number, d.name, d.rev)
c.save()
return HttpResponseRedirect(urlreverse('ietf.doc.views_downref.downref_registry'))

View file

@ -23,7 +23,7 @@ from django.utils import timezone
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State,
from ietf.doc.models import ( Document, RelatedDocument, State,
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, StateDocEvent,
IanaExpertDocEvent, IESG_SUBSTATE_TAGS)
from ietf.doc.mails import ( email_pulled_from_rfc_queue, email_resurrect_requested,
@ -38,7 +38,7 @@ from ietf.doc.utils import ( add_state_change_event, can_adopt_draft, can_unadop
set_replaces_for_document, default_consensus, tags_suffix, can_edit_docextresources,
update_doc_extresources )
from ietf.doc.lastcall import request_last_call
from ietf.doc.fields import SearchableDocAliasesField
from ietf.doc.fields import SearchableDocumentsField
from ietf.doc.forms import ExtResourceForm
from ietf.group.models import Group, Role, GroupFeatures
from ietf.iesg.models import TelechatDate
@ -333,7 +333,7 @@ def change_stream(request, name):
))
class ReplacesForm(forms.Form):
replaces = SearchableDocAliasesField(required=False)
replaces = SearchableDocumentsField(required=False)
comment = forms.CharField(widget=forms.Textarea, required=False, strip=False)
def __init__(self, *args, **kwargs):
@ -343,9 +343,9 @@ class ReplacesForm(forms.Form):
def clean_replaces(self):
for d in self.cleaned_data['replaces']:
if d.document == self.doc:
if d == self.doc:
raise forms.ValidationError("An Internet-Draft can't replace itself")
if d.document.type_id == "draft" and d.document.get_state_slug() == "rfc":
if d.type_id == "draft" and d.get_state_slug() == "rfc":
raise forms.ValidationError("An Internet-Draft can't replace an RFC")
return self.cleaned_data['replaces']
@ -383,7 +383,7 @@ def replaces(request, name):
))
class SuggestedReplacesForm(forms.Form):
replaces = forms.ModelMultipleChoiceField(queryset=DocAlias.objects.all(),
replaces = forms.ModelMultipleChoiceField(queryset=Document.objects.all(),
label="Suggestions", required=False, widget=forms.CheckboxSelectMultiple,
help_text="Select only the documents that are replaced by this document")
comment = forms.CharField(label="Optional comment", widget=forms.Textarea, required=False, strip=False)
@ -673,7 +673,7 @@ def edit_info(request, name):
e.save()
events.append(e)
replaces = Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces")
replaces = Document.objects.filter(targets_related__source=doc, targets_related__relationship="replaces")
if replaces:
# this should perhaps be somewhere else, e.g. the
# place where the replace relationship is established?

View file

@ -117,7 +117,7 @@ class RequestReviewForm(forms.ModelForm):
@login_required
def request_review(request, name):
doc = get_object_or_404(Document, name=name)
doc = get_object_or_404(Document, type_id="draft", name=name)
if not can_request_review_of_doc(request.user, doc):
permission_denied(request, "You do not have permission to perform this action")

View file

@ -409,8 +409,8 @@ def shorten_group_name(name):
def ad_dashboard_sort_key(doc):
if doc.type.slug=='draft' and doc.get_state_slug('draft') == 'rfc':
return "21%04d" % int(doc.rfc_number())
if doc.type.slug=='rfc':
return "21%04d" % int(doc.rfc_number)
if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'appr-sent':
return "22%d" % 0 # TODO - get the date of the transition into this state here
if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'):
@ -805,21 +805,20 @@ def recent_drafts(request, days=7):
})
def index_all_drafts(request):
def index_all_drafts(request): # Should we rename this
# try to be efficient since this view returns a lot of data
categories = []
for s in ("active", "rfc", "expired", "repl", "auth-rm", "ietf-rm"):
# Gather drafts
for s in ("active", "expired", "repl", "auth-rm", "ietf-rm"):
state = State.objects.get(type="draft", slug=s)
if state.slug == "rfc":
heading = "RFCs"
elif state.slug in ("ietf-rm", "auth-rm"):
if state.slug in ("ietf-rm", "auth-rm"):
heading = "Internet-Drafts %s" % state.name
else:
heading = "%s Internet-Drafts" % state.name
draft_names = DocAlias.objects.filter(docs__states=state).values_list("name", "docs__name")
draft_names = DocAlias.objects.filter(docs__type_id="draft", docs__states=state).values_list("name", "docs__name")
names = []
names_to_skip = set()
@ -828,24 +827,52 @@ def index_all_drafts(request):
if name != doc:
if not name.startswith("rfc"):
name, doc = doc, name
names_to_skip.add(doc)
if name.startswith("rfc"):
name = name.upper()
sort_key = '%09d' % (100000000-int(name[3:]))
names_to_skip.add(doc) # this is filtering out subseries docaliases (which we will delete, so TODO clean this out after doing so)
names.append((name, sort_key))
names.sort(key=lambda t: t[1])
names = [f'<a href=\"{urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=n))}\">{n}</a>'
for n, __ in names if n not in names_to_skip]
for n, __ in names if n not in names_to_skip]
categories.append((state,
heading,
len(names),
"<br>".join(names)
))
# gather RFCs
rfc_names = DocAlias.objects.filter(docs__type_id="rfc").values_list("name", "docs__name")
names = []
names_to_skip = set()
for name, doc in rfc_names:
sort_key = name
if name != doc: # There are some std docalias that pointed to rfc names pre-migration.
if not name.startswith("rfc"):
name, doc = doc, name
names_to_skip.add(doc) # this is filtering out those std docaliases (which we will delete, so TODO clean this out after doing so)
name = name.upper()
sort_key = '%09d' % (100000000-int(name[3:]))
names.append((name, sort_key))
names.sort(key=lambda t: t[1])
names = [f'<a href=\"{urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=n))}\">{n}</a>'
for n, __ in names if n not in names_to_skip]
state = State.objects.get(type_id="rfc", slug="published")
categories.append((state,
"RFCs",
len(names),
"<br>".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):

View file

@ -98,8 +98,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,
canonical_name=rel_doc.target.canonical_name(),
newstatus=newstatus(rel_doc))
for rel_doc in related_docs
]
@ -309,7 +309,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"
@ -320,7 +320,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),
@ -388,7 +388,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()
@ -399,7 +399,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.canonical_name(),newstatus(rel)),
})
formset = AnnouncementFormSet(initial=init)
for form in formset.forms:
@ -439,7 +439,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:
@ -564,10 +564,9 @@ def start_rfc_status_change(request, name=None):
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)
@ -605,11 +604,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.canonical_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:"
@ -626,7 +625,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.canonical_name()]=rel.relationship.slug
init = { "relations":relations,
}
form = EditStatusChangeForm(initial=init)
@ -653,8 +652,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
)
)

View file

@ -369,7 +369,7 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
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:

View file

@ -69,7 +69,7 @@ class GroupStatsTests(TestCase):
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 @@ class GroupDocDependencyTests(TestCase):
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):

View file

@ -117,8 +117,9 @@ class GroupPagesTests(TestCase):
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 @@ class GroupPagesTests(TestCase):
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,
@ -668,8 +670,9 @@ class GroupEditTests(TestCase):
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 +714,9 @@ class GroupEditTests(TestCase):
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']:

View file

@ -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'

View file

@ -534,9 +534,8 @@ def group_documents_txt(request, acronym, group_type=None):
rows = []
for d in itertools.chain(docs, docs_related):
rfc_number = d.rfc_number()
if rfc_number != None:
name = rfc_number
if d.type_id == "rfc":
name = str(d.rfc_number)
else:
name = "%s-%s" % (d.name, d.rev)
@ -746,7 +745,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__states__slug="rfc", target__states__slug="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"])
@ -760,19 +759,19 @@ 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": [
{
@ -795,7 +794,7 @@ def dependencies(request, acronym, group_type=None):
"links": [
{
"source": x.source.canonical_name(),
"target": x.target.document.canonical_name(),
"target": x.target.canonical_name(),
"rel": "downref" if x.is_downref() else x.relationship.slug,
}
for x in links

View file

@ -34,7 +34,7 @@ def all_id_txt():
rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc",
docs__states=State.objects.get(type="draft", slug="rfc")).values_list("docs__name", "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"))
@ -115,7 +115,7 @@ def all_id2_txt():
rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc",
docs__states=State.objects.get(type="draft", slug="rfc")).values_list("docs__name", "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"))

View file

@ -52,8 +52,13 @@ class IndexTests(TestCase):
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)
@ -111,8 +116,12 @@ class IndexTests(TestCase):
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")

View file

@ -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 }
return { 'date': date.isoformat(), 'sections': sections }

View file

@ -17,7 +17,7 @@ from django.utils.html import escape
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
@ -107,8 +107,8 @@ class IESGAgendaTests(TestCase):
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")])
@ -238,7 +238,7 @@ class IESGAgendaTests(TestCase):
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")
@ -256,7 +256,7 @@ class IESGAgendaTests(TestCase):
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")

View file

@ -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

View file

@ -151,8 +151,8 @@ def agenda_json(request, date=None):
if doc.type_id == "draft":
docinfo['rev'] = doc.rev
docinfo['intended-std-level'] = str(doc.intended_std_level)
if doc.rfc_number():
docinfo['rfc-number'] = doc.rfc_number()
if doc.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"):
@ -172,7 +172,7 @@ 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
td = doc.relateddocument_set.get(relationship__slug='conflrev').target
docinfo['target-docname'] = td.canonical_name()
docinfo['target-title'] = td.title
docinfo['target-rev'] = td.rev

View file

@ -94,7 +94,7 @@ admin.site.register(IprDocRel, IprDocRelAdmin)
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)

View file

@ -41,24 +41,17 @@ def iprs_from_docs(aliases,**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')):
"""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 = list(doc.document.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]
return list(set(results))

View file

@ -96,35 +96,35 @@ class Recipient(models.Model):
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" <irsg@irtf.org>')
return addrs

View file

@ -2565,6 +2565,19 @@
"model": "doc.state",
"pk": 176
},
{
"fields": {
"desc": "",
"name": "Published",
"next_states": [],
"order": 1,
"slug": "published",
"type": "rfc",
"used": true
},
"model": "doc.state",
"pk": 177
},
{
"fields": {
"label": "State"
@ -2747,6 +2760,13 @@
"model": "doc.statetype",
"pk": "review"
},
{
"fields": {
"label": "State"
},
"model": "doc.statetype",
"pk": "rfc"
},
{
"fields": {
"label": "Shepherd's Writeup State"
@ -9933,6 +9953,17 @@
"model": "name.dbtemplatetypename",
"pk": "rst"
},
{
"fields": {
"desc": "",
"name": "Became RFC",
"order": 0,
"revname": "Became RFC as",
"used": true
},
"model": "name.docrelationshipname",
"pk": "became_rfc"
},
{
"fields": {
"desc": "",
@ -10603,6 +10634,17 @@
"model": "name.doctypename",
"pk": "review"
},
{
"fields": {
"desc": "",
"name": "RFC",
"order": 0,
"prefix": "rfc",
"used": true
},
"model": "name.doctypename",
"pk": "rfc"
},
{
"fields": {
"desc": "",

View file

@ -0,0 +1,30 @@
# Generated by Django 4.2.2 on 2023-06-14 20:39
from django.db import migrations
def forward(apps, schema_editor):
DocTypeName = apps.get_model("name", "DocTypeName")
DocTypeName.objects.get_or_create(
slug="rfc",
name="RFC",
used=True,
prefix="rfc",
)
DocRelationshipName = apps.get_model("name", "DocRelationshipName")
DocRelationshipName.objects.get_or_create(
slug="became_rfc",
name="became RFC",
used=True,
revname="came from draft",
)
class Migration(migrations.Migration):
dependencies = [
("name", "0003_populate_telechatagendasectionname"),
]
operations = [
migrations.RunPython(forward),
]

View file

@ -42,7 +42,7 @@ class DocRelationshipName(NameModel):
class DocTypeName(NameModel):
"""Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email,
Review, Issue, Wiki"""
Review, Issue, Wiki, RFC"""
prefix = models.CharField(max_length=16, default="")
class DocTagName(NameModel):
"""Waiting for Reference, IANA Coordination, Revised ID Needed,

View file

@ -50,6 +50,8 @@ def can_request_review_of_doc(user, doc):
if not user.is_authenticated:
return False
# This is in a strange place as it has nothing to do with the user
# but this utility is used in too many places to move this quickly.
if doc.type_id == 'draft' and doc.get_state_slug() != 'active':
return False

View file

@ -67,10 +67,8 @@ class SecrTelechatTestCase(TestCase):
def test_doc_detail_draft_with_downref(self):
ad = Person.objects.get(user__username="ad")
draft = WgDraftFactory(ad=ad, intended_std_level_id='ps', states=[('draft-iesg','pub-req'),])
rfc = IndividualRfcFactory.create(stream_id='irtf', 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')
rfc = IndividualRfcFactory.create(stream_id='irtf', rfc_number=6666, std_level_id='inf')
draft.relateddocument_set.create(target=rfc, relationship_id='refnorm')
create_ballot_if_not_open(None, draft, ad, 'approve')
d = get_next_telechat_date()
date = d.strftime('%Y-%m-%d')

View file

@ -313,7 +313,7 @@ def doc_detail(request, date, name):
# if this is a conflict review document add referenced document
if doc.type_id == 'conflrev':
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target
else:
conflictdoc = None

View file

@ -85,13 +85,13 @@
{% if downrefs %}
<h2 id="downrefs">Downward References</h2>
{% for ref in downrefs %}
<p>Add {{ref.target.document.canonical_name}}
({{ref.target.document.std_level}} - {{ref.target.document.stream.desc}})
<p>Add {{ref.target.canonical_name}}
({{ref.target.std_level}} - {{ref.target.stream.desc}})
to downref registry.<br>
{% if not ref.target.document.std_level %}
{% if not ref.target.std_level %}
+++ Warning: The standards level has not been set yet!!!<br>
{% endif %}
{% if not ref.target.document.stream %}
{% if not ref.target.stream %}
+++ Warning: document stream has not been set yet!!!<br>
{% endif %}
{% endfor %}</p>

View file

@ -3,11 +3,8 @@
# Python imports
import io
import os
# Django imports
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
# Datatracker imports
@ -15,27 +12,6 @@ from ietf.group.models import Group
from ietf.ietfauth.utils import has_role
def current_nomcom():
qs = Group.objects.filter(acronym__startswith='nomcom',state__slug="active").order_by('-time')
if qs.count():
return qs[0]
else:
return None
def get_charter_text(group):
'''
Takes a group object and returns the text or the group's charter as a string
'''
charter = group.charter
path = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter.canonical_name(), charter.rev))
f = io.open(path,'r')
text = f.read()
f.close()
return text
def get_my_groups(user,conclude=False):
'''
Takes a Django user object (from request)

View file

@ -705,7 +705,7 @@ LIAISON_ATTACH_URL = 'https://www.ietf.org/lib/dt/documents/LIAISON/' # should e
DOC_HREFS = {
"charter": "https://www.ietf.org/charter/{doc.name}-{doc.rev}.txt",
"draft": "https://www.ietf.org/archive/id/{doc.name}-{doc.rev}.txt",
"rfc": "https://www.rfc-editor.org/rfc/rfc{doc.rfcnum}.txt",
"rfc": "https://www.rfc-editor.org/rfc/rfc{doc.rfc_number}.txt",
"slides": "https://www.ietf.org/slides/{doc.name}-{doc.rev}",
"procmaterials": "https://www.ietf.org/procmaterials/{doc.name}-{doc.rev}",
"conflrev": "https://www.ietf.org/cr/{doc.name}-{doc.rev}.txt",

View file

@ -82,7 +82,7 @@ class StatisticsTests(TestCase):
DocAlias.objects.create(name=referencing_draft.name).docs.add(referencing_draft)
RelatedDocument.objects.create(
source=referencing_draft,
target=draft.docalias.first(),
target=draft,
relationship=DocRelationshipName.objects.get(slug="refinfo")
)
NewRevisionDocEvent.objects.create(

View file

@ -563,11 +563,11 @@ def document_stats(request, stats_type=None):
bins = defaultdict(set)
cite_relationships = list(DocRelationshipName.objects.filter(slug__in=['refnorm', 'refinfo', 'refunk', 'refold']))
person_filters &= Q(documentauthor__document__docalias__relateddocument__relationship__in=cite_relationships)
person_filters &= Q(documentauthor__document__relateddocument__relationship__in=cite_relationships)
person_qs = Person.objects.filter(person_filters)
for name, citations in person_qs.values_list("name").annotate(Count("documentauthor__document__docalias__relateddocument")):
for name, citations in person_qs.values_list("name").annotate(Count("documentauthor__document__relateddocument")):
bins[citations or 0].add(name)
total_persons = count_bins(bins)
@ -587,11 +587,11 @@ def document_stats(request, stats_type=None):
bins = defaultdict(set)
cite_relationships = list(DocRelationshipName.objects.filter(slug__in=['refnorm', 'refinfo', 'refunk', 'refold']))
person_filters &= Q(documentauthor__document__docalias__relateddocument__relationship__in=cite_relationships)
person_filters &= Q(documentauthor__document__relateddocument__relationship__in=cite_relationships)
person_qs = Person.objects.filter(person_filters)
values = person_qs.values_list("name", "documentauthor__document").annotate(Count("documentauthor__document__docalias__relateddocument"))
values = person_qs.values_list("name", "documentauthor__document").annotate(Count("documentauthor__document__relateddocument"))
for name, ts in itertools.groupby(values.order_by("name"), key=lambda t: t[0]):
h_index = compute_hirsch_index([citations for _, document, citations in ts])
bins[h_index or 0].add(name)

View file

@ -28,8 +28,7 @@ import debug # pyflakes:ignore
from ietf.doc.models import Document
from ietf.group.models import Group
from ietf.ietfauth.utils import has_role
from ietf.doc.fields import SearchableDocAliasesField
from ietf.doc.models import DocAlias
from ietf.doc.fields import SearchableDocumentsField
from ietf.ipr.mail import utc_from_string
from ietf.meeting.models import Meeting
from ietf.message.models import Message
@ -684,9 +683,9 @@ class SubmissionAutoUploadForm(SubmissionBaseUploadForm):
if self.cleaned_data['replaces']:
names_replaced = [s.strip() for s in self.cleaned_data['replaces'].split(',')]
self.cleaned_data['replaces'] = ','.join(names_replaced)
aliases_replaced = DocAlias.objects.filter(name__in=names_replaced)
if len(names_replaced) != len(aliases_replaced):
known_names = aliases_replaced.values_list('name', flat=True)
documents_replaced = Document.objects.filter(name__in=names_replaced)
if len(names_replaced) != len(documents_replaced):
known_names = documents_replaced.values_list('name', flat=True)
unknown_names = [n for n in names_replaced if n not in known_names]
self.add_error(
'replaces',
@ -694,27 +693,27 @@ class SubmissionAutoUploadForm(SubmissionBaseUploadForm):
'Unknown Internet-Draft name(s): ' + ', '.join(unknown_names)
),
)
for alias in aliases_replaced:
if alias.document.name == self.filename:
for doc in documents_replaced:
if doc.name == self.filename:
self.add_error(
'replaces',
forms.ValidationError("An Internet-Draft cannot replace itself"),
)
elif alias.document.type_id != "draft":
elif doc.type_id != "draft":
self.add_error(
'replaces',
forms.ValidationError("An Internet-Draft can only replace another Internet-Draft"),
)
elif alias.document.get_state_slug() == "rfc":
elif doc.get_state_slug() == "rfc":
self.add_error(
'replaces',
forms.ValidationError("An Internet-Draft cannot replace an RFC"),
forms.ValidationError("An Internet-Draft cannot replace another Internet-Draft that has become an RFC"),
)
elif alias.document.get_state_slug('draft-iesg') in ('approved', 'ann', 'rfcqueue'):
elif doc.get_state_slug('draft-iesg') in ('approved', 'ann', 'rfcqueue'):
self.add_error(
'replaces',
forms.ValidationError(
alias.name + " is approved by the IESG and cannot be replaced"
doc.name + " is approved by the IESG and cannot be replaced"
),
)
return cleaned_data
@ -755,22 +754,20 @@ class SubmitterForm(NameEmailForm):
return line
class ReplacesForm(forms.Form):
replaces = SearchableDocAliasesField(required=False, help_text="Any Internet-Drafts that this document replaces (approval required for replacing an Internet-Draft you are not the author of)")
replaces = SearchableDocumentsField(required=False, help_text="Any Internet-Drafts that this document replaces (approval required for replacing an Internet-Draft you are not the author of)")
def __init__(self, *args, **kwargs):
self.name = kwargs.pop("name")
super(ReplacesForm, self).__init__(*args, **kwargs)
def clean_replaces(self):
for alias in self.cleaned_data['replaces']:
if alias.document.name == self.name:
for doc in self.cleaned_data['replaces']:
if doc.name == self.name:
raise forms.ValidationError("An Internet-Draft cannot replace itself.")
if alias.document.type_id != "draft":
if doc.type_id != "draft":
raise forms.ValidationError("An Internet-Draft can only replace another Internet-Draft")
if alias.document.get_state_slug() == "rfc":
raise forms.ValidationError("An Internet-Draft cannot replace an RFC")
if alias.document.get_state_slug('draft-iesg') in ('approved','ann','rfcqueue'):
raise forms.ValidationError(alias.name+" is approved by the IESG and cannot be replaced")
if doc.get_state_slug('draft-iesg') in ('approved','ann','rfcqueue'):
raise forms.ValidationError(doc.name+" is approved by the IESG and cannot be replaced")
return self.cleaned_data['replaces']
class EditSubmissionForm(forms.ModelForm):

View file

@ -32,7 +32,7 @@ from ietf.submit.utils import (expirable_submissions, expire_submission, find_su
process_and_accept_uploaded_submission, SubmissionError, process_submission_text,
process_submission_xml, process_uploaded_submission,
process_and_validate_submission)
from ietf.doc.factories import (DocumentFactory, WgDraftFactory, IndividualDraftFactory, IndividualRfcFactory,
from ietf.doc.factories import (DocumentFactory, WgDraftFactory, IndividualDraftFactory,
ReviewFactory, WgRfcFactory)
from ietf.doc.models import ( Document, DocAlias, DocEvent, State,
BallotPositionDocEvent, DocumentAuthor, SubmissionDocEvent )
@ -302,7 +302,7 @@ class SubmitTests(BaseSubmitTestCase):
submission = Submission.objects.get(name=name)
self.assertEqual(submission.submitter, email.utils.formataddr((submitter_name, submitter_email)))
self.assertEqual([] if submission.replaces == "" else submission.replaces.split(','),
[ d.name for d in DocAlias.objects.filter(pk__in=replaces) ])
[ d.name for d in Document.objects.filter(pk__in=replaces) ])
self.assertCountEqual(
[str(r) for r in submission.external_resources.all()],
[str(r) for r in extresources] if extresources else [],
@ -369,9 +369,8 @@ class SubmitTests(BaseSubmitTestCase):
# supply submitter info, then draft should be in and ready for approval
mailbox_before = len(outbox)
replaced_alias = draft.docalias.first()
r = self.supply_extra_metadata(name, status_url, author.ascii, author.email().address.lower(),
replaces=[str(replaced_alias.pk), str(sug_replaced_alias.pk)])
replaces=[str(draft.pk), str(sug_replaced_draft.pk)])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
@ -419,7 +418,7 @@ class SubmitTests(BaseSubmitTestCase):
self.assertEqual(authors[0].person, author)
self.assertEqual(set(draft.formal_languages.all()), set(FormalLanguageName.objects.filter(slug="json")))
self.assertEqual(draft.relations_that_doc("replaces").count(), 1)
self.assertTrue(draft.relations_that_doc("replaces").first().target, replaced_alias)
self.assertTrue(draft.relations_that_doc("replaces").first().target, draft)
self.assertEqual(draft.relations_that_doc("possibly-replaces").count(), 1)
self.assertTrue(draft.relations_that_doc("possibly-replaces").first().target, sug_replaced_alias)
self.assertEqual(len(outbox), mailbox_before + 5)
@ -1142,7 +1141,7 @@ class SubmitTests(BaseSubmitTestCase):
self.verify_bibxml_ids_creation(draft)
def test_submit_update_individual(self):
IndividualDraftFactory(name='draft-ietf-random-thing', states=[('draft','rfc')], other_aliases=['rfc9999',], pages=5)
IndividualDraftFactory(name='draft-ietf-random-thing', states=[('draft','active'),('draft-iesg','approved')], pages=5)
ad=Person.objects.get(user__username='ad')
# Group of None here does not reflect real individual submissions
draft = IndividualDraftFactory(group=None, ad = ad, authors=[ad,], notify='aliens@example.mars', pages=5)
@ -1152,23 +1151,14 @@ class SubmitTests(BaseSubmitTestCase):
status_url, author = self.do_submission(name,rev)
mailbox_before = len(outbox)
replaced_alias = draft.docalias.first()
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(replaced_alias.pk)])
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(draft.pk)])
self.assertEqual(r.status_code, 200)
self.assertContains(r, 'cannot replace itself')
self._assert_extresources_in_table(r, [])
self._assert_extresources_form(r, [])
replaced_alias = DocAlias.objects.get(name='draft-ietf-random-thing')
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(replaced_alias.pk)])
self.assertEqual(r.status_code, 200)
self.assertContains(r, 'cannot replace an RFC')
self._assert_extresources_in_table(r, [])
self._assert_extresources_form(r, [])
replaced_alias.document.set_state(State.objects.get(type='draft-iesg',slug='approved'))
replaced_alias.document.set_state(State.objects.get(type='draft',slug='active'))
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(replaced_alias.pk)])
replaced = Document.objects.get(name='draft-ietf-random-thing')
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(replaced.pk)])
self.assertEqual(r.status_code, 200)
self.assertContains(r, 'approved by the IESG and cannot')
self._assert_extresources_in_table(r, [])
@ -1262,7 +1252,7 @@ class SubmitTests(BaseSubmitTestCase):
status_url,
"Submitter Name",
"submitter@example.com",
replaces=[str(replaced_draft.docalias.first().pk)],
replaces=[str(replaced_draft.pk)],
)
submission = Submission.objects.get(name=name, rev=rev)
@ -1412,7 +1402,7 @@ class SubmitTests(BaseSubmitTestCase):
"edit-pages": "123",
"submitter-name": "Some Random Test Person",
"submitter-email": "random@example.com",
"replaces": [str(draft.docalias.first().pk)],
"replaces": [str(draft.pk)],
"edit-note": "no comments",
"authors-0-name": "Person 1",
"authors-0-email": "person1@example.com",
@ -1431,7 +1421,7 @@ class SubmitTests(BaseSubmitTestCase):
self.assertEqual(submission.pages, 123)
self.assertEqual(submission.note, "no comments")
self.assertEqual(submission.submitter, "Some Random Test Person <random@example.com>")
self.assertEqual(submission.replaces, draft.docalias.first().name)
self.assertEqual(submission.replaces, draft.name)
self.assertEqual(submission.state_id, "manual")
authors = submission.authors
@ -3099,13 +3089,15 @@ class SubmissionUploadFormTests(BaseSubmitTestCase):
# can't replace RFC
rfc = WgRfcFactory()
draft = WgDraftFactory(states=[("draft", "rfc")])
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
form = SubmissionAutoUploadForm(
request_factory.get('/some/url'),
data={'user': auth.user.username, 'replaces': rfc.name},
data={'user': auth.user.username, 'replaces': draft.name},
files=files_dict,
)
self.assertFalse(form.is_valid())
self.assertIn('An Internet-Draft cannot replace an RFC', form.errors['replaces'])
self.assertIn('An Internet-Draft cannot replace another Internet-Draft that has become an RFC', form.errors['replaces'])
# can't replace draft approved by iesg
existing_drafts[0].set_state(State.objects.get(type='draft-iesg', slug='approved'))
@ -3697,25 +3689,9 @@ class RefsTests(BaseSubmitTestCase):
class PostSubmissionTests(BaseSubmitTestCase):
@override_settings(RFC_FILE_TYPES=('txt', 'xml'), IDSUBMIT_FILE_TYPES=('pdf', 'md'))
def test_find_submission_filenames_rfc(self):
"""Posting an RFC submission should use RFC_FILE_TYPES"""
rfc = IndividualRfcFactory()
path = Path(self.staging_dir)
for ext in ['txt', 'xml', 'pdf', 'md']:
(path / f'{rfc.name}-{rfc.rev}.{ext}').touch()
files = find_submission_filenames(rfc)
self.assertCountEqual(
files,
{
'txt': f'{path}/{rfc.name}-{rfc.rev}.txt',
'xml': f'{path}/{rfc.name}-{rfc.rev}.xml',
# should NOT find the pdf or md
}
)
@override_settings(RFC_FILE_TYPES=('txt', 'xml'), IDSUBMIT_FILE_TYPES=('pdf', 'md'))
def test_find_submission_filenames_draft(self):
def test_find_submission_filenames(self):
"""Posting an I-D submission should use IDSUBMIT_FILE_TYPES"""
draft = WgDraftFactory()
path = Path(self.staging_dir)

View file

@ -287,7 +287,7 @@ def find_submission_filenames(draft):
"""
path = pathlib.Path(settings.IDSUBMIT_STAGING_PATH)
stem = f'{draft.name}-{draft.rev}'
allowed_types = settings.RFC_FILE_TYPES if draft.get_state_slug() == 'rfc' else settings.IDSUBMIT_FILE_TYPES
allowed_types = settings.IDSUBMIT_FILE_TYPES
candidates = {ext: path / f'{stem}.{ext}' for ext in allowed_types}
return {ext: str(filename) for ext, filename in candidates.items() if filename.exists()}
@ -504,7 +504,7 @@ def update_replaces_from_submission(request, submission, draft):
if request.user.is_authenticated:
is_chair_of = list(Group.objects.filter(role__person__user=request.user, role__name="chair"))
replaces = DocAlias.objects.filter(name__in=submission.replaces.split(",")).prefetch_related("docs", "docs__group")
replaces = Document.objects.filter(name__in=submission.replaces.split(",")).prefetch_related("group")
existing_replaces = list(draft.related_that_doc("replaces"))
existing_suggested = set(draft.related_that_doc("possibly-replaces"))
@ -516,14 +516,12 @@ def update_replaces_from_submission(request, submission, draft):
if r in existing_replaces:
continue
rdoc = r.document
if rdoc == draft:
if r == draft:
continue
if (is_secretariat
or (draft.group in is_chair_of and (rdoc.group.type_id == "individ" or rdoc.group in is_chair_of))
or (submitter_email and rdoc.documentauthor_set.filter(email__address__iexact=submitter_email).exists())):
or (draft.group in is_chair_of and (r.group.type_id == "individ" or r.group in is_chair_of))
or (submitter_email and r.documentauthor_set.filter(email__address__iexact=submitter_email).exists())):
approved.append(r)
else:
if r not in existing_suggested:

View file

@ -488,12 +488,15 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
def parse_relation_list(l):
res = []
for x in l:
if x[:3] in ("NIC", "IEN", "STD", "RTR"):
# try translating this to RFCs that we can handle
# sensibly; otherwise we'll have to ignore them
l = DocAlias.objects.filter(name__startswith="rfc", docs__docalias__name=x.lower())
else:
l = DocAlias.objects.filter(name=x.lower())
# This lookup wasn't finding anything but maybe some STD and we know
# if the STD had more than one RFC the wrong thing happens
#
#if x[:3] in ("NIC", "IEN", "STD", "RTR"):
# # try translating this to RFCs that we can handle
# # sensibly; otherwise we'll have to ignore them
# l = DocAlias.objects.filter(name__startswith="rfc", docs__docalias__name=x.lower())
#else:
l = Document.objects.filter(name=x.lower())
for a in l:
if a not in res:

View file

@ -14,7 +14,7 @@ from django.utils import timezone
import debug # pyflakes:ignore
from ietf.doc.factories import WgDraftFactory
from ietf.doc.factories import WgDraftFactory, RfcFactory
from ietf.doc.models import Document, DocAlias, DocEvent, DeletedEvent, DocTagName, RelatedDocument, State, StateDocEvent
from ietf.doc.utils import add_state_change_event
from ietf.group.factories import GroupFactory
@ -235,9 +235,7 @@ class RFCSyncTests(TestCase):
# too, but for testing purposes ...
doc.action_holders.add(doc.ad) # not normally set, but add to be sure it's cleared
updated_doc = Document.objects.create(name="draft-ietf-something")
DocAlias.objects.create(name=updated_doc.name).docs.add(updated_doc)
DocAlias.objects.create(name="rfc123").docs.add(updated_doc)
RfcFactory(rfc_number=123)
today = date_today()
@ -360,7 +358,7 @@ class RFCSyncTests(TestCase):
self.assertTrue(DocAlias.objects.filter(name="bcp1", docs=doc))
self.assertTrue(DocAlias.objects.filter(name="fyi1", docs=doc))
self.assertTrue(DocAlias.objects.filter(name="std1", docs=doc))
self.assertTrue(RelatedDocument.objects.filter(source=doc, target__name="rfc123", relationship="updates"))
self.assertTrue(RelatedDocument.objects.filter(source=doc, target__name="rfc123", relationship="updates").exists())
self.assertEqual(doc.title, "A Testing RFC")
self.assertEqual(doc.abstract, "This is some interesting text.")
self.assertEqual(doc.get_state_slug(), "rfc")
@ -602,4 +600,4 @@ class RFCEditorUndoTests(TestCase):
e = DeletedEvent.objects.all().order_by("-time", "-id")[0]
e.content_type.model_class().objects.create(**json.loads(e.json))
self.assertTrue(StateDocEvent.objects.filter(desc="First", doc=draft))
self.assertTrue(StateDocEvent.objects.filter(desc="First", doc=draft))

View file

@ -21,7 +21,7 @@
</button>
{% if user|has_role:"Secretariat" %}
<a class="btn btn-primary"
href="{% url 'ietf.doc.views_charter.approve' name=charter.canonical_name %}">
href="{% url 'ietf.doc.views_charter.approve' name=charter.name %}">
Charter approval page
</a>
{% endif %}

View file

@ -2,16 +2,16 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load django_bootstrap5 %}
{% block title %}Approve {{ charter.canonical_name }}{% endblock %}
{% block title %}Approve {{ charter.name }}{% endblock %}
{% block content %}
{% origin %}
<h1>Approve {{ charter.canonical_name }}-{{ charter.rev }}</h1>
<h1>Approve {{ charter.name }}-{{ charter.rev }}</h1>
<form class="mt-3" method="post">
{% csrf_token %}
<pre class="border p-3">{{ announcement }}</pre>
<button type="submit" class="btn btn-primary">Send announcement, close ballot &amp; update revision</button>
<a class="btn btn-warning"
href="{% url "ietf.doc.views_charter.action_announcement_text" name=charter.canonical_name %}?next=approve">
href="{% url "ietf.doc.views_charter.action_announcement_text" name=charter.name %}?next=approve">
Edit/regenerate announcement
</a>
<a class="btn btn-secondary float-end"

View file

@ -2,20 +2,20 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load django_bootstrap5 %}
{% block title %}Change responsible AD for {{ charter.canonical_name }}-{{ charter.rev }}{% endblock %}
{% block title %}Change responsible AD for {{ charter.name }}-{{ charter.rev }}{% endblock %}
{% block content %}
{% origin %}
<h1>
Change responsible AD
<br>
<small class="text-body-secondary">{{ charter.canonical_name }}-{{ charter.rev }}</small>
<small class="text-body-secondary">{{ charter.name }}-{{ charter.rev }}</small>
</h1>
<form class="mt-3" enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-secondary float-end"
href="{% url "ietf.doc.views_doc.document_main" name=charter.canonical_name %}">
href="{% url "ietf.doc.views_doc.document_main" name=charter.name %}">
Back
</a>
</form>

View file

@ -3,7 +3,7 @@
{% load ietf_filters %}
{% load textfilters %}
{% if doc.get_state_slug == "rfc" %}
{% if doc.type_id == "rfc" %}
{% if doc.stream|slugify == "legacy" %}
% Datatracker information for RFCs on the Legacy Stream is unfortunately often
% incorrect. Please correct the bibtex below based on the information in the
@ -16,7 +16,7 @@
publisher = {RFC Editor},
doi = {% templatetag openbrace %}{{ doi }}{% templatetag closebrace %},
url = {% templatetag openbrace %}{{ doc.rfc_number|rfceditor_info_url }}{% templatetag closebrace %},{% else %}
{% if published %}%% You should probably cite rfc{{ latest_revision.doc.rfc_number }} instead of this I-D.{% else %}{% if replaced_by %}%% You should probably cite {{replaced_by|join:" or "}} instead of this I-D.{% else %}
{% if published_as %}%% You should probably cite rfc{{ published_as.rfc_number }} instead of this I-D.{% else %}{% if replaced_by %}%% You should probably cite {{replaced_by|join:" or "}} instead of this I-D.{% else %}
{% if doc.rev != latest_revision.rev %}%% You should probably cite {{latest_revision.doc.name}}-{{latest_revision.rev}} instead of this revision.{%endif%}{% endif %}{% endif %}
@techreport{% templatetag openbrace %}{{doc.name|slice:"6:"}}-{{doc.rev}},
number = {% templatetag openbrace %}{{doc.name}}-{{doc.rev}}{% templatetag closebrace %},
@ -29,7 +29,7 @@
title = {% templatetag openbrace %}{% templatetag openbrace %}{{doc.title|texescape}}{% templatetag closebrace %}{% templatetag closebrace %},
pagetotal = {{ doc.pages }},
year = {{ doc.pub_date.year }},
month = {{ doc.pub_date|date:"b" }},{% if not doc.rfc_number or doc.pub_date.day == 1 and doc.pub_date.month == 4 %}
month = {{ doc.pub_date|date:"b" }},{% if not doc.type_id == "rfc" or doc.pub_date.day == 1 and doc.pub_date.month == 4 %}
day = {{ doc.pub_date.day }},{% endif %}
abstract = {% templatetag openbrace %}{{ doc.abstract|clean_whitespace|texescape }}{% templatetag closebrace %},
{% templatetag closebrace %}

View file

@ -11,7 +11,7 @@
<link rel="alternate"
type="application/atom+xml"
href="/feed/document-changes/{{ doc.name }}/">
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
<link rel="stylesheet" href="{% static 'ietf/css/list.css' %}">
{% endblock %}
{% block content %}
{% origin %}
@ -20,7 +20,17 @@
<h2 class="my-3">Revision differences</h2>
{% include "doc/document_history_form.html" with doc=doc diff_revisions=diff_revisions action=rfcdiff_base_url snapshot=snapshot only %}
{% endif %}
<h2 class="my-3">Document history</h2>
<h2 class="my-3">Document history
{% if related %}
<div class="float-end">
{% for related_docalias in related %}
<a class="btn btn-outline-primary btn-sm"
href="{% url 'ietf.doc.views_doc.document_history' name=related_docalias.name %}">
Related history for {{ related_docalias.name }}
</a>
{% endfor %}
</div>
{% endif %}</h2>
{% if can_add_comment %}
<div class="buttonlist">
<a class="btn btn-primary"
@ -34,7 +44,7 @@
<thead>
<tr>
<th scope="col" data-sort="date">Date</th>
<th scope="col" data-sort="rev">Rev.</th>
{% if doc.type_id != "rfc" %}<th scope="col" data-sort="rev">Rev.</th>{% endif %}
<th scope="col" data-sort="by">By</th>
<th scope="col" data-sort="action">Action</th>
</tr>
@ -45,7 +55,7 @@
<td>
<div title="{{ e.time|date:'Y-m-d H:i:s O' }}">{{ e.time|date:"Y-m-d" }}</div>
</td>
<td class="text-end">{{ e.rev }}</td>
{% if doc.type_id != "rfc" %}<td class="text-end">{{ e.rev }}</td>{% endif %}
<td>{{ e.by|escape }}</td>
<td>{{ e.desc|format_history_text }}</td>
</tr>

View file

@ -11,7 +11,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>
{% if not snapshot and doc.get_state_slug == "rfc" %}
{% if doc.type_id == "rfc" %}
RFC {{ doc.rfc_number }} - {{ doc.title }}
{% else %}
{{ doc.name }}-{{ doc.rev }}

View file

@ -11,15 +11,15 @@
<th scope="row">{% if document_html %}Document type{% else %}Type{% endif %}</th>
<td class="edit"></td>
<td>
{% if doc.get_state_slug == "rfc" and not snapshot %}
{% if doc.type_id == "rfc" %}
<span class="text-success">RFC
{% if not document_html %}
- {{ doc.std_level }}
{% else %}
<span class="badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}">{{ doc.std_level }}</span>
{% endif %}
{% else %}
<span class="badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}">{{ doc.std_level }}</span>
{% endif %}
</span>
{% if published %}
{% if doc.pub_date %}
{% if document_html %}<br>{% else %}({% endif %}{{ doc.pub_date|date:"F Y" }}{% if not document_html %}){% endif %}
{% else %}
<span class="text-body-secondary">(Publication date unknown)</span>
@ -52,7 +52,6 @@
{% if proposed_status_changes %}
<div>Proposed status changed by {{ proposed_status_changes|urlize_related_source_list|join:", " }}</div>
{% endif %}
{% if rfc_aliases %}<div>Also known as {{ rfc_aliases|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if draft_name %}
<div>
Was
@ -132,91 +131,93 @@
</td>
</tr>
{% endif %}
{% if replaces or not document_html and can_edit_stream_info %}
<tr>
<td></td>
<th scope="row">Replaces</th>
<td class="edit">
{% if can_edit_stream_info and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.replaces' name=doc.name %}">Edit</a>
{% endif %}
</td>
<td>
{% if replaces %}
{% if document_html %}
{{ replaces|urlize_related_target_list:document_html|join:"<br>" }}
{% else %}
{{ replaces|urlize_related_target_list:document_html|join:", " }}
{% endif %}
{% else %}
<span class="text-body-secondary">(None)</span>
{% endif %}
</td>
</tr>
{% endif %}
{% if replaced_by %}
<tr>
<td></td>
<th scope="row">
Replaced by
</th>
<td class="edit">
</td>
<td>
{% if document_html %}
{{ replaced_by|urlize_related_source_list:document_html|join:"<br>" }}
{% else %}
{{ replaced_by|urlize_related_source_list:document_html|join:", " }}
{% endif %}
</td>
</tr>
{% endif %}
{% if can_view_possibly_replaces %}
{% if possibly_replaces %}
{% if doc.type_id != "rfc" %}
{% if replaces or not document_html and can_edit_stream_info %}
<tr>
<td></td>
<th scope="row">
Possibly Replaces
</th>
<th scope="row">Replaces</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
{% if can_edit_stream_info and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">
Edit
</a>
href="{% url 'ietf.doc.views_draft.replaces' name=doc.name %}">Edit</a>
{% endif %}
</td>
<td>
{% if document_html %}
{{ possibly_replaces|urlize_related_target_list:document_html|join:"<br>" }}
{% if replaces %}
{% if document_html %}
{{ replaces|urlize_related_target_list:document_html|join:"<br>" }}
{% else %}
{{ replaces|urlize_related_target_list:document_html|join:", " }}
{% endif %}
{% else %}
{{ possibly_replaces|urlize_related_target_list:document_html|join:", " }}
<span class="text-body-secondary">(None)</span>
{% endif %}
</td>
</tr>
{% endif %}
{% if possibly_replaced_by %}
{% if replaced_by %}
<tr>
<td></td>
<th scope="row">
Possibly Replaced By
Replaced by
</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
{% comment %}<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">Edit</a>{% endcomment %}
{% endif %}
</td>
<td>
{% if document_html %}
{{ possibly_replaced_by|urlize_related_source_list:document_html|join:"<br>" }}
{{ replaced_by|urlize_related_source_list:document_html|join:"<br>" }}
{% else %}
{{ possibly_replaced_by|urlize_related_source_list:document_html|join:", " }}
{{ replaced_by|urlize_related_source_list:document_html|join:", " }}
{% endif %}
</td>
</tr>
{% endif %}
{% if can_view_possibly_replaces %}
{% if possibly_replaces %}
<tr>
<td></td>
<th scope="row">
Possibly Replaces
</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td>
{% if document_html %}
{{ possibly_replaces|urlize_related_target_list:document_html|join:"<br>" }}
{% else %}
{{ possibly_replaces|urlize_related_target_list:document_html|join:", " }}
{% endif %}
</td>
</tr>
{% endif %}
{% if possibly_replaced_by %}
<tr>
<td></td>
<th scope="row">
Possibly Replaced By
</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
{% comment %}<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">Edit</a>{% endcomment %}
{% endif %}
</td>
<td>
{% if document_html %}
{{ possibly_replaced_by|urlize_related_source_list:document_html|join:"<br>" }}
{% else %}
{{ possibly_replaced_by|urlize_related_source_list:document_html|join:", " }}
{% endif %}
</td>
</tr>
{% endif %}
{% endif %}
{% endif %}
<tr>
<td></td>
@ -231,15 +232,15 @@ href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">E
</a>
{% endif %}
</td>
<td {% if stream_desc == "(None)" %}class="text-body-secondary"{%endif%}>
{% if stream_desc != "(None)" %}
<td {% if doc.stream is None %}class="text-body-secondary"{%endif%}>
{% if doc.stream is not None %}
{% if doc.stream.name|lower in 'iab,irtf,ise,editorial' %}
<a href="{% url 'ietf.group.views.stream_documents' acronym=doc.stream.name|lower %}">
{% endif %}
{% if document_html %}
{% if doc.stream.name|lower in 'iab,ietf,irtf' %}
<img alt="{{ doc.stream.name|upper }} Logo"
title="{{ stream_desc }}"
title="{{ doc.stream.desc }}"
class="w-25 mt-1"
{% if doc.stream.name|lower == 'iab' %}
src="{% static 'ietf/images/iab-logo.svg' %}"
@ -250,20 +251,20 @@ href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">E
{% endif %}
>
{% else %}
{{ stream_desc }}
{{ doc.stream.desc }}
{% endif %}
{% else %}
{{ stream_desc }}
{{ doc.stream.desc }}
{% endif %}
{% if doc.stream.name|lower in 'iab,irtf,ise,editorial' %}
</a>
{% endif %}
{% else %}
{{ stream_desc }}
(None)
{% endif %}
</td>
</tr>
{% if doc.get_state_slug != "rfc" and not snapshot %}
{% if doc.type_id != "rfc" and not snapshot %}
<tr>
<td></td>
<th scope="row">
@ -339,70 +340,72 @@ href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">E
</tr>
{% endif %}
{% endfor %}
{% if review_assignments or can_request_review %}
<tr>
<td></td>
<th scope="row">
Reviews
</th>
<td class="edit">
</td>
<td>
{% for review_assignment in review_assignments %}
{% if doc.type_id != "rfc" %}{# do not show reviews or conflict_reviews for RFCs, even if present #}
{% if review_assignments or can_request_review %}
<tr>
<td></td>
<th scope="row">
Reviews
</th>
<td class="edit">
</td>
<td>
{% for review_assignment in review_assignments %}
{% include "doc/review_assignment_summary.html" with current_doc_name=doc.name current_rev=doc.rev review_assignment=review_assignment only %}
{% endfor %}
{% for review_request in review_requests %}
{% include "doc/review_request_summary.html" with review_request=review_request only %}
{% endfor %}
{% if no_review_from_teams %}
{% for team in no_review_from_teams %}
{{ team.acronym.upper }}{% if not forloop.last %},{% endif %}
{% endfor %}
will not review this version
{% endif %}
{% if can_request_review or can_submit_unsolicited_review_for_teams %}
<div {% if review_assignments or no_review_from_teams %}class="mt-3"{% endif %}>
{% if can_request_review %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.request_review" doc.name %}">
<i class="bi bi-check-circle">
</i>
Request review
</a>
{% endfor %}
{% if no_review_from_teams %}
{% for team in no_review_from_teams %}
{{ team.acronym.upper }}{% if not forloop.last %},{% endif %}
{% endfor %}
will not review this version
{% endif %}
{% if can_submit_unsolicited_review_for_teams|length == 1 %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.complete_review" doc.name can_submit_unsolicited_review_for_teams.0.acronym %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% elif can_submit_unsolicited_review_for_teams %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.submit_unsolicited_review_choose_team" doc.name %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% if can_request_review or can_submit_unsolicited_review_for_teams %}
<div {% if review_assignments or no_review_from_teams %}class="mt-3"{% endif %}>
{% if can_request_review %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.request_review" doc.name %}">
<i class="bi bi-check-circle">
</i>
Request review
</a>
{% endif %}
{% if can_submit_unsolicited_review_for_teams|length == 1 %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.complete_review" doc.name can_submit_unsolicited_review_for_teams.0.acronym %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% elif can_submit_unsolicited_review_for_teams %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.submit_unsolicited_review_choose_team" doc.name %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
</td>
</tr>
{% endif %}
{% if conflict_reviews %}
<tr>
<td></td>
<th scope="row">
IETF conflict review
</th>
<td class="edit">
</td>
<td>
{{ conflict_reviews|join:", "|urlize_ietf_docs }}
</td>
</tr>
{% endif %}
</td>
</tr>
{% endif %}
{% if conflict_reviews %}
<tr>
<td></td>
<th scope="row">
IETF conflict review
</th>
<td class="edit">
</td>
<td>
{{ conflict_reviews|join:", "|urlize_ietf_docs }}
</td>
</tr>
{% endif %}
{% endif %}
{% endif %}
{% with doc.docextresource_set.all as resources %}
{% if resources or doc.group and doc.group.list_archive or can_edit_stream_info or can_edit_individual %}

View file

@ -64,7 +64,7 @@
</a>
</td>
<td>
{% if ref.source.get_state.slug == 'rfc' %}
{% if ref.source.type_id == "rfc" %}
{% with ref.source.std_level as lvl %}
{% if lvl %}{{ lvl }}{% endif %}
{% endwith %}

View file

@ -35,7 +35,7 @@
<a href="{% url 'ietf.doc.views_doc.document_main' name=name %}">{{ name|prettystdname }}</a>
</td>
<td>
<b>{{ ref.target.document.title }}</b>
<b>{{ ref.target.title }}</b>
<br>
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_doc.document_references' name %}"
@ -51,12 +51,12 @@
</a>
</td>
<td>
{% if ref.target.document.get_state.slug == 'rfc' %}
{% with ref.target.document.std_level as lvl %}
{% if ref.target.type_id == "rfc" %}
{% with ref.target.std_level as lvl %}
{% if lvl %}{{ lvl }}{% endif %}
{% endwith %}
{% else %}
{% with ref.target.document.intended_std_level as lvl %}
{% with ref.target.intended_std_level as lvl %}
{% if lvl %}{{ lvl }}{% endif %}
{% endwith %}
{% endif %}

View file

@ -0,0 +1,339 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2016-2020, All Rights Reserved #}
{% load origin %}
{% load static %}
{% load ietf_filters %}
{% load person_filters %}
{% load textfilters %}
{% block html_attrs %}prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#"{% endblock %}
{% block pagehead %}
{% include "doc/opengraph.html" %}
<link rel="alternate"
type="application/atom+xml"
title="Document changes"
href="/feed/document-changes/{{ name }}/">
<meta name="description"
content="{{ doc.title }} {% if doc.get_state_slug == 'rfc' and not snapshot %}(RFC {{ rfc_number }}{% if published %}, {{ doc.pub_date|date:'F Y' }}{% endif %}{% if obsoleted_by %}; obsoleted by {% for rel in obsoleted_by %}{{ rel.source.canonical_name|prettystdname}}{% if not forloop.last%}, {% endif %}{% endfor %}{% endif %}){% endif %}">
{% endblock %}
{% block morecss %}.inline { display: inline; }{% endblock %}
{% block title %}
RFC {{ rfc_number }} - {{ doc.title }}
{% endblock %}
{% block content %}
{% origin %}
{{ top|safe }}
<div id="timeline"></div>
{% if doc.rev != latest_rev %}
<div class="alert alert-warning my-3">The information below is for an old version of the document.</div>
{% endif %}
<table class="table table-sm table-borderless">
{% include "doc/document_info.html" %}
<tbody class="meta border-top">
{% if milestones %}
<tr>
<td></td>
<th scope="row">
Associated
{% if doc.stream_id == 'ietf' %}
WG
{% else %}
{{ doc.stream }}
{% endif %} milestone{{ milestones|pluralize }}</th>
<td class="edit"></td>
<td>
<dl class="row">
{% for m in milestones %}
<dt class="col-sm-2 my-0">{{ m.due|date:"M Y" }}</dt>
<dd class="col-sm-10 my-0">{{ m.desc }}</dd>
{% endfor %}
</dl>
</td>
</tr>
{% endif %}
</tbody>
{% if doc.stream_id != 'iab' %}
<tbody class="meta border-top">
<tr>
<th scope="row">
IESG
</th>
<th scope="row">
Responsible AD
</th>
<td class="edit">
{% if can_edit %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.edit_ad' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td>
{% if doc.ad %}
{% person_link doc.ad %}
{% else %}
<span class="text-body-secondary">
(None)
</span>
{% endif %}
</td>
</tr>
<tr>
<td></td>
<th scope="row">
Send notices to
</th>
<td class="edit">
{% if can_edit_notify and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_doc.edit_notify' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td>
{% if doc.notify %}
{{ doc.notify|linkify }}
{% else %}
<span class="text-body-secondary">
(None)
</span>
{% endif %}
</td>
</tr>
</tbody>
{% endif %}
{% if rfc_editor_state %}
<tbody class="meta border-top">
<tr>
<th scope="row">
RFC Editor
</th>
<th scope="row">
<a href="{% url "ietf.help.views.state" doc=doc.type.slug type="rfceditor" %}">
RFC Editor state
</a>
</th>
<td class="edit">
</td>
<td>
{{ rfc_editor_state }}
</td>
</tr>
<tr>
<td></td>
<th scope="row">
Details
</th>
<td class="edit">
</td>
<td>
<div>
<a href="https://www.rfc-editor.org/queue2.html#{{ doc.name }}">
Publication queue entry
</a>
</div>
{% if rfc_editor_auth48_url %}
<div>
<a href="{{ rfc_editor_auth48_url }}">
Auth48 status
</a>
</div>
{% endif %}
</td>
</tr>
</tbody>
{% endif %}
</table>
<div class="buttonlist">
<a class="btn btn-primary btn-sm"
href="mailto:{{ doc.name }}@ietf.org?subject=Mail%20regarding%20{{ doc.name }}">
<i class="bi bi-envelope">
</i>
Email authors
</a>
{% if doc.group.type.slug == 'wg' or doc.group.type.slug == 'rg' %}
<a class="btn btn-primary btn-sm"
href="mailto:{{ doc.group.list_email }}?subject=Mail%20regarding%20{{ doc.name }}">
<i class="bi bi-envelope">
</i>
Email {{ doc.group.type }}
</a>
{% endif %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.ipr.views.search" %}?submit=draft&amp;id={{ doc.name }}"
rel="nofollow">
<i class="bi bi-lightning">
</i>
IPR
{% if doc.related_ipr %}
<span class="badge rounded-pill">
{{ doc.related_ipr|length }}
</span>
{% endif %}
</a>
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_doc.document_references' doc.canonical_name %}"
rel="nofollow">
<i class="bi bi-arrow-left">
</i>
References
</a>
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_doc.document_referenced_by' doc.canonical_name %}"
rel="nofollow">
<i class="bi bi-arrow-right">
</i>
Referenced by
</a>
{# document_draft shows Nits here, excluded for RFCs #}
<div class="dropdown inline">
<button class="btn btn-primary btn-sm dropdown-toggle"
type="button"
id="ddSearchMenu"
data-bs-toggle="dropdown"
aria-expanded="true">
<i class="bi bi-search">
</i>
Search lists
</button>
<ul class="dropdown-menu" role="menu">
<li role="presentation">
<a class="dropdown-item"
href="https://mailarchive.ietf.org/arch/search?q=%22{{ doc.name }}%22"
rel="nofollow"
target="_blank">
IETF Mail Archive
</a>
</li>
<li role="presentation">
<a class="dropdown-item"
href="https://www.google.com/search?as_q={{ doc.name }}&amp;as_sitesearch={{ search_archive }}"
rel="nofollow"
target="_blank">
Google
</a>
</li>
{% if user|has_role:"Area Director" %}
<li role="presentation">
<a class="dropdown-item"
href="https://www.iesg.org/bin/c5i?mid=6&amp;rid=77&amp;target={{ doc.name }}"
rel="nofollow"
target="_blank">
ARO
</a>
</li>
{% endif %}
</ul>
</div>
{% if user.is_authenticated %}
<a class="btn btn-primary btn-sm track-untrack-doc {% if not doc.tracked_in_personal_community_list %}hide{% endif %}"
href="{% url "ietf.community.views.untrack_document" username=user.username name=doc.name %}"
title="Remove from your personal I-D list">
<i class="bi bi-bookmark-check-fill">
</i>
Untrack
</a>
<a class="btn btn-primary btn-sm track-untrack-doc {% if doc.tracked_in_personal_community_list %}hide{% endif %}"
href="{% url "ietf.community.views.track_document" username=user.username name=doc.name %}"
title="Add to your personal I-D list">
<i class="bi bi-bookmark">
</i>
Track
</a>
{% endif %}
{% if user.review_teams %}
<a class="btn btn-primary btn-sm review-wish-add-remove-doc ajax {% if not doc.has_review_wish %}hide{% endif %}"
href="{% url "ietf.doc.views_review.review_wishes_remove" name=doc.name %}?next={{ request.get_full_path|urlencode }}"
title="Remove from your review wishes for all teams">
<i class="bi bi-chat-left-heart-fill">
</i>
Remove review wishes
</a>
<a class="btn btn-primary btn-sm review-wish-add-remove-doc {% if user.review_teams|length_is:"1" %}ajax {% endif %}{% if doc.has_review_wish %}hide{% endif %}"
href="{% url "ietf.doc.views_review.review_wish_add" name=doc.name %}?next={{ request.get_full_path|urlencode }}"
title="Add to your review wishes">
<i class="bi bi-chat-left-heart">
</i>
Add review wish
</a>
{% endif %}
{% if can_edit and iesg_state.slug != 'idexists' %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_ballot.lastcalltext' name=doc.name %}">
Last call text
</a>
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_ballot.ballot_writeupnotes' name=doc.name %}">
Ballot text
</a>
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_ballot.ballot_approvaltext' name=doc.name %}">
Announcement text
</a>
{% endif %}
{% if actions %}
{% for label, url in actions %}
<a class="btn btn-primary btn-sm" href="{{ url }}">
{{ label|capfirst_allcaps }}
</a>
{% endfor %}
{% endif %}
</div>
{% if doc.get_state_slug == "active" or doc.get_state_slug == "rfc" %}
<div class="card mt-5">
<div class="card-header">
{% if doc.get_state_slug == "rfc" and not snapshot %}
RFC {{ rfc_number }}
{% else %}
{{ name }}-{{ doc.rev }}
{% endif %}
</div>
<div class="card-body">
<pre>{{ content|sanitize|safe|default:"(Unavailable)" }}</pre>
</div>
</div>
{% if split_content %}
<a class="btn btn-primary my-3" href="?include_text=1">
<i class="bi bi-caret-down">
</i>
Show full document
</a>
{% endif %}
{% else %}
<div class="card border-warning mt-5">
<div class="card-header bg-warning">
<p><b>This Internet-Draft is no longer active. A copy of
the expired Internet-Draft is available in these formats:</b></p>
{% include "doc/document_format_buttons.html" %}
</div>
<div class="card-body">
<p class="h5 card-title">
Abstract
</p>
<p class="card-text">
{{ doc.abstract }}
</p>
<p class="h5 card-title">
Authors
</p>
<p class="card-text">
{% for author in doc.documentauthor_set.all %}
{% person_link author.person %}
{% if not forloop.last %}<br>{% endif %}
{% endfor %}
</p>
<p class="text-body-secondary card-text">
(Note: The e-mail addresses provided for the authors of this Internet-Draft may no longer be valid.)
</p>
</div>
</div>
{% endif %}
{% endblock %}
{% block js %}
<script src="{% static 'ietf/js/d3.js' %}">
</script>
<script src="{% static 'ietf/js/document_timeline.js' %}">
</script>
{% endblock %}

View file

@ -52,7 +52,7 @@
<td class="edit"></td>
<td>
{% for rel in relation_group.list %}
{{ rel.target.document.canonical_name|upper|urlize_ietf_docs }}{% if not forloop.last %},{% endif %}
{{ rel.target.canonical_name|upper|urlize_ietf_docs }}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
</tr>

View file

@ -1,7 +1,7 @@
{% load ietf_filters %}{% filter linebreaks_lf %}{% comment %}
{% endcomment %}Doc-tag: {{doc.name}};datatracker{% if doc.rfcnum %}
Doc-rfcnum: {{doc.rfcnum}}{% endif %}
{% endcomment %}Doc-tag: {{doc.name}};datatracker{% if doc.type_id == "rfc" %}
Doc-rfcnum: {{doc.rfc_number}}{% endif %}
Doc-created: {{doc.created|date:"Y-m-d"}};datatracker{% if doc.deststatus %}
Doc-deststatus: {{doc.deststatus}};datatracker{% endif %}
Doc-rev: {{doc.rev}};datatracker
{% endfilter %}
{% endfilter %}

View file

@ -33,7 +33,7 @@ No IPR declarations have been submitted directly on this I-D.
{% if downrefs %}
The document contains these normative downward references.
See RFC 3967 for additional information:
{% for ref in downrefs %} {{ref.target.document.canonical_name}}: {{ref.target.document.title}} ({{ref.target.document.std_level}} - {{ref.target.document.stream.desc}})
{% for ref in downrefs %} {{ref.target.canonical_name}}: {{ref.target.title}} ({{ref.target.std_level}} - {{ref.target.stream.desc}})
{% endfor %}{%endif%}
{% endautoescape %}

View file

@ -49,13 +49,13 @@
{% if doc.pages %}<small class="float-end text-body-secondary d-none d-sm-block">{{ doc.pages }} page{{ doc.pages|pluralize }}</small>{% endif %}
<div>
<a href="{{ doc.get_absolute_url }}">
{% if doc.get_state_slug == "rfc" %}
{% if doc.type_id == "rfc" %}
RFC {{ doc.rfc_number }}
{% else %}
{{ doc.name }}-{{ doc.rev }}
{% endif %}
</a>
{% if doc.get_state_slug == "rfc" and "draft" in doc.name %}<i class="text-body-secondary">(was {{ doc.name }})</i>{% endif %}
{# {% if doc.type_id == "rfc" and "draft" in doc.name %}<i class="text-body-secondary">(was {{ doc.name }})</i>{% endif %} TODO drop this or look up the corresponding draft name#}
<br>
{% comment %}
<div class="float-end">
@ -106,19 +106,19 @@
{% endif %}
</td>
<td>
{% if doc.latest_revision_date|timesince_days|new_enough:request and doc.get_state_slug != "rfc" %}
{% if doc.latest_revision_date|timesince_days|new_enough:request and doc.type_id != "rfc" %}
{% if doc.rev != "00" %}
<a href="{{ rfcdiff_base_url }}?url2={{ doc.name }}-{{ doc.rev }}">
{% elif doc.replaces %}
<a href="{{ rfcdiff_base_url }}?url1={{ doc.replaces_canonical_name }}&amp;url2={{ doc.name }}-{{ doc.rev }}">
{% endif %}
{% endif %}
{% if doc.get_state_slug == "rfc" %}
{% if doc.type_id == "rfc" %}
{{ doc.latest_revision_date|date:"Y-m" }}
{% else %}
{{ doc.latest_revision_date|date:"Y-m-d" }}
{% endif %}
{% if doc.latest_revision_date|timesince_days|new_enough:request and doc.get_state_slug != "rfc" %}
{% if doc.latest_revision_date|timesince_days|new_enough:request and doc.type_id != "rfc" %}
{% if doc.rev != "00" or doc.replaces %}</a>{% endif %}
{% endif %}
{% if doc.latest_revision_date|timesince_days|new_enough:request %}
@ -127,7 +127,7 @@
<span class="badge rounded-pill bg-success">New</span>
</div>
{% endif %}
{% if doc.get_state_slug == "active" and doc.expirable and doc.expires|timesince_days|expires_soon:request %}
{% if doc.type_id == "draft" and doc.get_state_slug == "active" and doc.expirable and doc.expires|timesince_days|expires_soon:request %}
<br>
<span class="badge rounded-pill bg-warning">Expires soon</span>
{% endif %}

View file

@ -1,11 +1,11 @@
{% load ietf_filters %}{% load mail_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
To: {{ to }}{% if cc %}
Cc: {{ cc }}{% endif %}
Subject: {{action}}: {{relateddoc.target.document.title}} to {{newstatus}}
Subject: {{action}}: {{relateddoc.target.title}} to {{newstatus}}
{% filter wordwrap:78 %}The IESG has approved changing the status of the following document:
- {{relateddoc.target.document.title }}
({{relateddoc.target.document.canonical_name }}) to {{ newstatus }}
- {{relateddoc.target.title }}
({{relateddoc.target.canonical_name }}) to {{ newstatus }}
This {{action|lower}} is documented at:
{{status_change_url}}

View file

@ -14,8 +14,8 @@
<a class="btn btn-primary" href="{{ group.about_url }}">{{ group.acronym }} {{ group.type.name }}</a>
{% if group.charter %}
<a class="btn btn-primary"
href="{% url "ietf.doc.views_doc.document_main" name=group.charter.canonical_name %}">
{{ group.charter.canonical_name }}
href="{% url "ietf.doc.views_doc.document_main" name=group.charter.name %}">
{{ group.charter.name }}
</a>
{% endif %}
{% if can_change_uses_milestone_dates %}
@ -106,7 +106,7 @@
</div>
</div>
<a class="btn btn-secondary float-end"
href="{% if milestone_set == "charter" %}{% url "ietf.doc.views_doc.document_main" name=group.charter.canonical_name %}{% else %}{{ group.about_url }}{% endif %}">
href="{% if milestone_set == "charter" %}{% url "ietf.doc.views_doc.document_main" name=group.charter.name %}{% else %}{{ group.about_url }}{% endif %}">
Cancel
</a>
<button class="btn btn-primary hidden"

View file

@ -165,13 +165,13 @@ Parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{% if downrefs %}
<p>If APPROVED - The Secretariat will add to the downref registry:<br>
{% for ref in downrefs %}
+ Add {{ref.target.document.canonical_name}}
({{ref.target.document.std_level}} - {{ref.target.document.stream.desc}})
+ Add {{ref.target.canonical_name}}
({{ref.target.std_level}} - {{ref.target.stream.desc}})
to downref registry.<br>
{% if not ref.target.document.std_level %}
{% if not ref.target.std_level %}
+++ Warning: The standards level has not been set yet!!!<br>
{% endif %}
{% if not ref.target.document.stream %}
{% if not ref.target.stream %}
+++ Warning: document stream has not been set yet!!!<br>
{% endif %}
{% endfor %}</p>

View file

@ -54,7 +54,7 @@ class Command(BaseCommand):
doc = getattr(obj, docattr)
time = getattr(obj, timeattr)
if not obj.rev:
if not doc.is_rfc():
if doc.type_id != "rfc":
self.stdout.write("Bad revision number: %-52s: '%s'" % (doc.name, obj.rev))
continue
rev = int(obj.rev.lstrip('0') or '0')

View file

@ -12,11 +12,12 @@ from django.utils.encoding import smart_str
import debug # pyflakes:ignore
from ietf.doc.models import Document, DocAlias, State, DocumentAuthor, DocEvent, RelatedDocument, NewRevisionDocEvent
from ietf.doc.factories import IndividualDraftFactory, ConflictReviewFactory, StatusChangeFactory, WgDraftFactory, WgRfcFactory
from ietf.group.models import Group, GroupHistory, Role, RoleHistory
from ietf.iesg.models import TelechatDate
from ietf.ipr.models import HolderIprDisclosure, IprDocRel, IprDisclosureStateName, IprLicenseTypeName
from ietf.meeting.models import Meeting, ResourceAssociation
from ietf.name.models import StreamName, DocRelationshipName, RoomResourceName, ConstraintName
from ietf.name.models import DocRelationshipName, RoomResourceName, ConstraintName
from ietf.person.models import Person, Email
from ietf.group.utils import setup_default_community_list_for_group
from ietf.review.models import (ReviewRequest, ReviewerSettings, ReviewResultName, ReviewTypeName, ReviewTeamSettings )
@ -316,7 +317,7 @@ def make_test_data():
doc_alias = DocAlias.objects.create(name=draft.name)
doc_alias.docs.add(draft)
RelatedDocument.objects.create(source=draft, target=old_alias, relationship=DocRelationshipName.objects.get(slug='replaces'))
RelatedDocument.objects.create(source=draft, target=old_draft, relationship=DocRelationshipName.objects.get(slug='replaces'))
old_draft.set_state(State.objects.get(type='draft', slug='repl'))
DocumentAuthor.objects.create(
@ -391,37 +392,27 @@ def make_test_data():
)
# an independent submission before review
doc = Document.objects.create(name='draft-imaginary-independent-submission',type_id='draft',rev='00',
title="Some Independent Notes on Imagination")
doc.set_state(State.objects.get(used=True, type="draft", slug="active"))
DocAlias.objects.create(name=doc.name).docs.add(doc)
IndividualDraftFactory(title="Some Independent Notes on Imagination")
# an irtf submission mid review
doc = Document.objects.create(name='draft-imaginary-irtf-submission', type_id='draft',rev='00',
stream=StreamName.objects.get(slug='irtf'), title="The Importance of Research Imagination")
docalias = DocAlias.objects.create(name=doc.name)
docalias.docs.add(doc)
doc.set_state(State.objects.get(type="draft", slug="active"))
crdoc = Document.objects.create(name='conflict-review-imaginary-irtf-submission', type_id='conflrev',
rev='00', notify="fsm@ietf.org", title="Conflict Review of IRTF Imagination Document")
DocAlias.objects.create(name=crdoc.name).docs.add(crdoc)
crdoc.set_state(State.objects.get(name='Needs Shepherd', type__slug='conflrev'))
crdoc.relateddocument_set.create(target=docalias,relationship_id='conflrev')
doc = IndividualDraftFactory(name="draft-imaginary-irtf-submission", stream_id="irtf", title="The Importance of Research Imagination")
ConflictReviewFactory(name="conflict-review-imaginary-irtf-submission", review_of=doc, notify="fsm@ietf.org", title="Conflict Review of IRTF Imagination Document")
# A status change mid review
iesg = Group.objects.get(acronym='iesg')
doc = Document.objects.create(name='status-change-imaginary-mid-review',type_id='statchg', rev='00',
notify="fsm@ietf.org", group=iesg, title="Status Change Review without Imagination")
doc.set_state(State.objects.get(slug='needshep',type__slug='statchg'))
docalias = DocAlias.objects.create(name='status-change-imaginary-mid-review')
docalias.docs.add(doc)
doc = StatusChangeFactory(
name='status-change-imaginary-mid-review',
notify="fsm@ietf.org",
group=iesg,
title="Status Change Review without Imagination",
states= [State.objects.get(type_id="statchg",slug="needshep")]
)
# Some things for a status change to affect
def rfc_for_status_change_test_factory(name,rfc_num,std_level_id):
target_rfc = Document.objects.create(name=name, type_id='draft', std_level_id=std_level_id, notify="%s@ietf.org"%name)
target_rfc.set_state(State.objects.get(slug='rfc',type__slug='draft'))
DocAlias.objects.create(name=name).docs.add(target_rfc)
DocAlias.objects.create(name='rfc%d'%rfc_num).docs.add(target_rfc)
target_rfc = WgRfcFactory(rfc_number=rfc_num, std_level_id=std_level_id)
source_draft = WgDraftFactory(name=name, states=[("draft","rfc")], notify=f"{name}@ietf.org")
source_draft.relateddocument_set.create(relationship_id="became_rfc", target=target_rfc)
return target_rfc
rfc_for_status_change_test_factory('draft-ietf-random-thing',9999,'ps')
rfc_for_status_change_test_factory('draft-ietf-random-otherthing',9998,'inf')